Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 90 题:实现模糊搜索结果的关键词高亮显示 #141

Open
yygmind opened this issue Jun 16, 2019 · 26 comments
Open

第 90 题:实现模糊搜索结果的关键词高亮显示 #141

yygmind opened this issue Jun 16, 2019 · 26 comments

Comments

@yygmind
Copy link
Contributor

yygmind commented Jun 16, 2019

@lhyt
Copy link

lhyt commented Jun 16, 2019

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

@5SSS
Copy link

5SSS commented Jun 17, 2019

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

@foolmadao
Copy link

foolmadao commented Jun 17, 2019

实现不难,写一个pipe做统一的转换会让代码看起来更加简洁

<ul>
  <li *ngFor="let data of mockData">
    <span innerHTML="{{data.name | highLight: target}}"></span>
  </li>
</ul>

image

@ouyinheng
Copy link

<div class="input">
    <input type="text" oninput="search(event)">
    <ul class="options"></ul>
</div>

<script>
    const list = ['上海', '上海市', '上海海昌海洋公园', '上海市徐汇区', '上海自来水来自海上'];

    function setList(value) {
      const ul = document.querySelector('.options');
      ul.innerHTML = '';
      if (!value) {
        ul.innerHTML = '';
        return;
      }
      list.forEach((item, index) => {
        if (item.indexOf(value) !== -1) {
          const li = document.createElement('li');
          const innerHtml = item.replace(value, `<span style="color:red">${value}</span>`);
          console.log(innerHtml)
          li.innerHTML = innerHtml;
          li.setAttribute('key', index);
          ul.appendChild(li);
        }
      })
    }

    function search(e) {
      const value = e.target.value;
      setList(value)
    }
</script>

@sailei1
Copy link

sailei1 commented Jul 18, 2019

直接把 输入的字符 变蓝, 服务端返回的数据 把输入字符替换掉,然后追加后面那部分 显示。
没有结果的时候做一下判断。

@qinshenxue
Copy link

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

new RegExp 要注意排除有正则含义的字符,比如用户输入 ^ $ 等。

@hviwen
Copy link

hviwen commented Jul 24, 2019

用关键词去切分搜索结果为3段
export const keyWordHeightLight = (item, value) => {
const mIdx = item.indexOf(value)
const first = mIdx >= 0 ? mIdx : item.length
const second = value.length

let _head = item.substr(0, first)
let _heightLight = item.substr(first, second)
let _foot = item.substr(second + mIdx, item.length)

return _head + <b>${_heightLight}</b> + _foot
}

@zzNire
Copy link

zzNire commented Aug 14, 2019

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='app'>   
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    var myitem = {
        template:`<div class='item'>
            <p v-html="info.name"></p>
        </div>`,
        props:{
            info:{
                type:Object,
                default:()=>{return {}},
            }
        }

    }
    var vm = new Vue({
        el:'#app',
        template:`
        <div>
            <input @input='inputSearchText'>
            <myitem v-for='info in results' :key='info.name' :info='info'></item>
        </div>
        `,
        data(){
            return {
                infos:[
                    {name:'地铁1',},
                    {name:'地铁6',},
                    {name:'地铁7',},
                    {name:'地铁10',},
                    {name:'地铁11',},
                    {name:'公交112',},
                    {name:'公交597',},
                    {name:'公交593',}, 
                ],
                results:[],
            }
        },
        created() {
            this.results = JSON.parse(JSON.stringify(this.infos));
        },
        methods: {
            inputSearchText : (function(timeout){
                var timer;
                return function(e){
                    if(timer){
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        this.search(e.target.value);
                        //this.search_text = e.target.value
                    }, timeout);
                }
            })(1000),
            search(text){
                var reg = RegExp(`(${text})`);
                var results = JSON.parse(JSON.stringify(this.infos));
                var matches = results.filter(info=>info.name.match(reg));
                matches.forEach(info=>{
                    info.name = info.name.replace(reg,`<span class='highlight'>$1</span>`
                )});
                this.results = matches;
                console.log(this.results);
            }
        },
        components:{
            myitem,
        },
    })
</script>
<style>
.highlight{
    color:red;
}
</style>
</html>

@pagemarks
Copy link

pagemarks commented Aug 15, 2019

export function addMark(q, val) {
    if (/^[\w\s\+\-]+$/.test(q)) {
        let reg = q;
        if (/(\s-|-\s)+[^\s-]/.test(q)) {
            reg = q.split(/(\s-|-\s)/)[0];
        } else if (/[\s+]+[^\s+]/.test(q)) {
            reg = q.replace(/([\s+]+)/, '|');
        }
        reg = new RegExp(`(${reg})`,'igm');
        return val.replace(reg,`<b>$1</b>`)
    } else {
        return val.replace(q,`<b>${q}</b>`)
    }
}

个人网站在我看到此题前实现了一个,为纯英文时,需要注意不区分大小写.返回值通过vue v-html渲染
搜索支持简单布尔运算

  • key1 key2: 搜索包含key1或key2的条目
  • key1 + key2: 搜索即包含key1又包含key2的条目
  • key1 - key2: 搜索只包含key1不包含key2的条目

image

@SoftwareEngineerPalace
Copy link

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

应用用节流还是防抖?

@callmezhenzhen
Copy link

debounce

debounce不是防抖么?

@fireairforce
Copy link

mark一下,这个问题真的有意思

@Jmingzi
Copy link

Jmingzi commented Nov 27, 2019

处理中文输入,再加个防抖。如果用数据驱动视图,可以“原地复用”,才是最优解。

<body>
    <input id="input" type="text" />
    <div id="box"></div>
    <script>
      let list = [
        { id: 1, name: "部门A", parentId: 0 },
        { id: 2, name: "部门B", parentId: 0 },
        { id: 3, name: "部门C", parentId: 1 },
        { id: 4, name: "部门D", parentId: 1 },
        { id: 5, name: "部门E", parentId: 2 },
        { id: 6, name: "部门F", parentId: 3 },
        { id: 7, name: "部门G", parentId: 2 },
        { id: 8, name: "部门H", parentId: 4 }
      ];
      input.addEventListener("input", handleSearch);
      input.addEventListener("compositionstart", handleStart);
      input.addEventListener("compositionend", handleEnd);

      function regLikeSearch(str) {
        let htmls = "";
        list.forEach(item => {
          const match = item.name.replace(
            new RegExp(str, "ig"),
            (all, match) => {
              return `<span style="color: red">${all}</span>`;
            }
          );
          htmls += `<p>${match}</p>`;
        });
        box.innerHTML = htmls;
      }

      let isZhcn = false;
      function handleSearch(e) {
        if (!isZhcn) {
          const val = e.target.value;
          console.log("handleSearch", val);
          regLikeSearch(val);
        }
      }
      function handleStart(e) {
        isZhcn = true;
      }
      function handleEnd(e) {
        isZhcn = false;
        handleSearch(e);
      }
    </script>
  </body>

@lightYourFire
Copy link

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~
缺点就是: v-html有XSS 攻击问题

@chenweihuan
Copy link

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~
缺点就是: v-html有XSS 攻击问题

v-html的xss攻击这样这样解决?

<template>
  <div id="app" v-html="fixVHTMLXSS(text)"></div>
</template>
<script>
export default {
  data () {
    return {
      text: '<span style="color:red">1111111</span>'
    }
  },
  methods: {
    fixVHTMLXSS (str) {
      return (str || '').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    }
  }
}
</script>

@hduhdc
Copy link

hduhdc commented Apr 3, 2020

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

厉害,但是有点就是我看到实现的debounce是防抖,确实是使用防抖不能使用节流,只是最上面的文案是要修改一手。赞

@xiaochen-01
Copy link

let list = [
{
id: 1,
address: '上海野生动物园'
},
{
id: 2,
address: '上饶野生动物园'
},
{
id: 3,
address: '北京巷子'
},
{
id: 4,
address: '上海中心'
},
{
id: 5,
address: '上海黄埔江'
},
{
id: 6,
address: '迪士尼上海'
},
{
id: 7,
address: '陆家嘴上海中心'
},
]
const input1 = document.querySelector('#input1');
const ul = document.querySelector('ul');
input1.addEventListener('keyup', debounceFunc(function() {
const result = getResult(this.target.value);
renderUl(result);
}, 500))

function debounceFunc (fn, delay) {
    let lastTime = 0;
    let timer = null;
    return function (event) {
        let now = +(new Date());
        clearTimeout(timer)
        timer = setTimeout(function () {
            fn.call(event);
        }, delay);
        if (now - lastTime < delay) {
            lastTime = now;
        }
    }
}

function getResult(str) {
    return str.trim() ? list.filter(item => item.address.includes(str))
                .map(item => {
                    return {
                        ...item,
                        address: item.address.replace(str, `<span style="color:#31b0d5">${str}</span>`)
                    }
                }) : [];
}

function renderUl(lis) {
    ul.innerHTML = lis.map(item => `<li>${item.address}</li>`).join('')

}

@myzhoulang
Copy link

function a(str, childStr){
const arr = str.split(childStr)
return arr.join(<span class="highlight">${childStr}</span>)
}

@523451928
Copy link

function queryStr(str, query) {
  let result
  const index = str.indexOf(query)
  if (index !== -1) {
    result = `${str.substr(0, index)}<span style="color: red;">${query}</span>${str.substr(index + query.length)}`
  } else {
    result = str
  }
  return result
}
queryStr('hello world', 'r')

@UzumakiHan
Copy link

这不是防抖吗? @lhyt

@LXhby
Copy link

LXhby commented May 18, 2021

结合输入中文:compisitionsart compositionend

<body>
  <input type="text" class="box">
  <ul class="txt"></ul>
</body>
<script>
  let ipt = document.querySelector('.box')
  let txt = document.querySelector('.txt')
  console.log('txt', txt)

  const list = ['上海', '上海市', '上海海昌海洋公园', '上海市徐汇区', '上海自来水来自海上'];
  ipt.addEventListener('input', function (e) {
    if (e.target.composing) {
      return
    }

    txt.innerHTML = '';
    list.forEach(item => {
      if (item.indexOf(ipt.value) != -1) {
        let li = document.createElement('li')
        const innerHtml = item.replace(ipt.value, `<span style="color:red">${ipt.value}</span>`)
        console.log('innerHtml', innerHtml)
        li.innerHTML = innerHtml;
        txt.appendChild(li)
      }
    })
  })
  ipt.addEventListener('compositionstart', function (e) {
    e.target.composing = true
  })
  ipt.addEventListener('compositionend', function (e) {
    e.target.composing = false;
    let event = document.createEvent('HTMLEvents')
    event.initEvent('input')
    e.target.dispatchEvent(event);
  })
</script>

@tustzdm
Copy link

tustzdm commented Jun 5, 2021

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

let panter = new RegExp(关键词, 'ig')
replace(panter, matchValue => <span style="xxx">${matchValue}</span>);
正则忽略大小写,然后replace第二个参数用函数更佳哈

@zhujianxiong
Copy link

在这里我有个问题,如果是富文本编辑器返回的内容,如何高亮选中,然后后端怎么在查找的时候,帅选掉那些标签呢

@q673115816
Copy link

'变蓝变红变绿变蓝变'.match(/(变蓝)|((?!变蓝).)+/g)

@goldEli
Copy link

goldEli commented Jan 27, 2022

React 版本

import { useEffect, useRef, useState } from "react";

const allData = ["asdew", "aedf", "123", "asdf"];

export default function App() {
  const [inputVal, setInputVal] = useState("");
  const data = useData(inputVal);

  return (
    <div className="App">
      <input
        value={inputVal}
        onChange={(e) => {
          const text = e.target.value;
          setInputVal(text);
        }}
      />
      <ul>
        {data.map((dataItem) => {
          return (
            <li>
              {dataItem.split("").map((item) => {
                return (
                  <span style={{ color: inputVal.includes(item) ? "red" : "" }}>
                    {item}
                  </span>
                );
              })}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

const useData = (value) => {
  const val = useDebounce(value, 500);
  const [data, setData] = useState([]);
  useEffect(() => {
    if (!val) {
      setData([]);
      return;
    }
    setData(allData.filter((item) => item.includes(val)));
  }, [val]);
  return data;
};

const useDebounce = (value, interval = 1000) => {
  const [data, setData] = useState(value);
  const timer = useRef(null);

  useEffect(() => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      setData(value);
    }, interval);
    return () => {
      clearTimeout(timer.current);
    };
  }, [value, interval]);

  return data;
};

@XW666
Copy link

XW666 commented May 30, 2023

 const datas = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const inputSearchText = (val) => {
    let reg = new RegExp(val)
    let arr = []
    for (let c of datas) {
      if (reg.test(c)) {
        let s = c.replace(val, `<span style="color:#31b0d5">${val}</span>`)
        arr.push(s)
      }
    }
    console.log('结果', arr)
  }
  inputSearchText('野生')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests