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

发布于 2022-05-10 12:45:15 字数 357 浏览 959 评论 25

如果的效果显示,场景的搜索页面。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(25

貪欢 2022-05-04 13:55:51

考虑节流、缓存。其实还可以上列表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>
amp;</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>

应用用节流还是防抖?

情魔剑神 2022-05-04 13:55:51
debounce

debounce不是防抖么?

强辩 2022-05-04 13:55:51

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

成熟稳重的好男人 2022-05-04 13:55:51

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

<body>
    <input type="text" />
    <div></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>
黎夕旧梦 2022-05-04 13:55:51

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

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

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

℉絮湮 2022-05-04 13:55:51

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

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

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

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

<template>
  <div 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, '<').replace(/>/g, '>')
    }
  }
}
</script>
橙幽之幻 2022-05-04 13:55:51

考虑节流、缓存。其实还可以上列表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>
amp;</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是防抖,确实是使用防抖不能使用节流,只是最上面的文案是要修改一手。赞

你的背包 2022-05-04 13:55:51

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('')

}
不离久伴 2022-05-04 13:55:51

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

给不了的爱 2022-05-04 13:55:51
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')
缘字诀 2022-05-04 13:55:51

这不是防抖吗? @lhyt

↘人皮目录ツ 2022-05-04 13:55:51

结合输入中文: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>
枯〃寂 2022-05-04 13:55:51

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

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第二个参数用函数更佳哈

彩虹直至黑白 2022-05-04 13:55:51

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

甜点 2022-05-04 13:55:51

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

口干舌燥 2022-05-04 13:55:51

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;
};
┼──瘾|| 2022-05-04 13:55:50
<!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>
沩ん囻菔务 2022-05-04 13:55:50
export function addMark(q, val) {
    if (/^[ws+-]+$/.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的条目

葬花如无物 2022-05-04 13:55:42

用关键词去切分搜索结果为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
}

梦途 2022-05-04 13:37:26

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

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

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

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

横笛休吹塞上声 2022-05-04 13:35:52

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

万人眼中万个我 2022-05-04 13:28:19
<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>
哥,最终变帅啦 2022-05-04 13:27:54

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

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

烟雨扶苏゜ 2022-05-04 11:41:03

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

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

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

抚笙 2022-05-04 09:37:56

考虑节流、缓存。其实还可以上列表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>
amp;</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>
~没有更多了~

关于作者

0 文章
0 评论
22 人气
更多

推荐作者

策马西风

文章 0 评论 0

柠檬心

文章 0 评论 0

1331

文章 0 评论 0

七度光

文章 0 评论 0

qq_oc2LaO

文章 0 评论 0

野却迷人

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文