不想用浏览器自带的Ctrl+F搜索🔍文本,那就自己写一个

990 阅读2分钟

常常遇到项目中需要全局搜索🔍,但是不想用浏览器自带的搜索功能,这时候懒得✍️,一直没时间写, 抽出空来,还是自己实现一个吧,方便自己,也方便他人。

要能移动位置、以及修改样式,这两点比较重要

代码放在了github上面,需要自取,修改一下样式就行

功能

主要功能:

  1. 创建一个自定义搜索框 允许用户输入搜索内容。
  2. 高亮显示匹配的文本 并且能够通过上下按钮跳转到不同的匹配项。
  3. 支持拖拽功能 用户可以自由移动搜索框。
  4. 自定义高亮颜色 当前高亮项为橙色,其它高亮项为黄色。
  5. 快捷键支持 通过监听ctrl+f打开搜索框

实现步骤

1. 创建 TextSearch

首先,我们定义 TextSearch 类的构造函数,初始化默认配置,并在页面加载时自动创建搜索框。

class TextSearch {
  constructor(options = {}) {
    this.options = {
      backgroundColor: '#fff',
      textColor: '#000',
      highlightColor: 'yellow',  // 默认黄色
      currentHighlightColor: 'orange',  // 当前高亮为橙色
      searchBoxWidth: '96%',
      ...options,
    };

    this.matches = [];
    this.currentIndex = -1;

    this.createSearchBox();
    this.listenForSearch();
    this.clearHighlights();
  }
}
  • options:允许用户传入自定义配置,比如背景色、文字颜色和高亮颜色等。
  • matches:存储所有匹配的文本节点。
  • currentIndex:当前高亮的匹配项索引。

2. 创建搜索框

接下来,我们创建一个固定在页面上的搜索框,并为其添加输入框、搜索按钮和关闭按钮。 代码就不展示了,很简单

效果如下:

image.png

3. 高亮匹配文本

当用户输入搜索内容时,我们将遍历页面上的所有文本节点,使用正则表达式匹配符合条件的文本,并通过 span 标签包裹它们来实现高亮显示。

searchText(query) {
  this.clearHighlights();  // 清除之前的高亮

  if (!query.trim()) {
    return;
  }

  const regex = new RegExp(query, 'gi');
  const textNodes = this.getTextNodesUnder(document.body);

  textNodes.forEach((node) => {
    const matches = node.textContent.match(regex);
    if (matches) {
      this.highlightText(node, matches, query);
    }
  });
}

highlightText(node, matches, query) {
  const regex = new RegExp(`(${query})`, 'gi');
  const parts = node.textContent.split(regex);

  const parentNode = node.parentNode;
  const fragment = document.createDocumentFragment();

  parts.forEach((part) => {
    const span = document.createElement('span');
    if (part.toLowerCase() === query.toLowerCase()) {
      span.className = 'highlighted';
      span.style.backgroundColor = this.options.highlightColor;
      span.style.fontWeight = 'bold';
      span.textContent = part;
      fragment.appendChild(span);
      this.matches.push(span);
    } else {
      fragment.appendChild(document.createTextNode(part));
    }
  });

  parentNode.replaceChild(fragment, node);
}
  • getTextNodesUnder:获取页面中所有的文本节点。
  • highlightText:使用 span 标签包裹匹配的文本,并为其添加背景色。

4. 跳转到下一个/上一个匹配项

通过上下按钮,用户可以在所有匹配项中进行跳转。通过维护一个 currentIndex 来记录当前高亮项的位置。

highlightNext() {
  if (this.matches.length === 0) return;

  this.currentIndex = (this.currentIndex + 1) % this.matches.length;
  this.updateHighlightColors();
  this.scrollToMatch(this.matches[this.currentIndex]);
}

highlightPrevious() {
  if (this.matches.length === 0) return;

  this.currentIndex = (this.currentIndex - 1 + this.matches.length) % this.matches.length;
  this.updateHighlightColors();
  this.scrollToMatch(this.matches[this.currentIndex]);
}

updateHighlightColors() {
  this.matches.forEach((match, index) => {
    if (index === this.currentIndex) {
      match.style.backgroundColor = this.options.currentHighlightColor;
    } else {
      match.style.backgroundColor = this.options.highlightColor;
    }
  });
}

scrollToMatch(match) {
  match.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
  • highlightNext 和 highlightPrevious:分别用于切换到下一个和上一个匹配项。
  • updateHighlightColors:更新所有匹配项的高亮颜色,确保当前项为橙色,其它项为黄色。

5. 拖拽功能

为了让用户更加灵活地操作搜索框,为搜索框添加了拖拽功能。

makeDraggable(element) {
  let isDragging = false;
  let offsetX, offsetY;

  element.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - element.getBoundingClientRect().left;
    offsetY = e.clientY - element.getBoundingClientRect().top;
  });

  window.addEventListener('mousemove', (e) => {
    if (isDragging) {
      element.style.top = `${e.clientY - offsetY}px`;
      element.style.left = `${e.clientX - offsetX}px`;
    }
  });

  window.addEventListener('mouseup', () => {
    isDragging = false;
  });
}

6. 监听 Ctrl+F 快捷键

  listenForSearch() {
    window.addEventListener('keydown', (event) => {
      if ((event.ctrlKey || event.metaKey) && event.key === 'f') {
        event.preventDefault();
        this.createSearchBox();
      }
    });
  }

最终效果

20241218202050_rec_.gif

完整代码

github: ctrl-f