我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用GPT写代码实现高亮页面关键字

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用GPT写代码实现高亮页面关键字

背景

最近用户跟我反馈了一个问题,他们使用的浏览器高亮关键字的插件在我们的网站不生效,我看了一下,因为使用了 webComponent 但插件没有进行兼容。他们说这个功能非常重要,因此,我们就要在系统内自己实现高亮页面关键字。

在做一个新功能的时候,首先要先调研一下方案。

但在网上找了一圈,发现基本上都是以下这种方案:

// js部分
var bodyContent = document.body.innerHTMl;  // 获取页面内容
var pattern = new RegExp('关键字', 'g'); // 生成正则表达式
// 匹配关键词并替换
document.body.innerHTMl = bodyContent.replace(pattern, '<span class="highlight">$&</span>');

在 innerHTML 匹配关键字,将关键字替换成高亮的 span,然后重新替换 innerHTML 这样实现虽然非常简单,但会导致 shadowRoot 和 iframe 失效。

于是我决定问问 GPT,看看它能不能给我一点惊喜

使用 GPT 编写代码

我:我要写一段 js 代码,传入指定的文本,在整个页面高亮

GPT: 你可以使用 JavaScript 中的 DOM 操作来实现这个功能。以下是一段示例代码:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  while ((match = regex.exec(body.innerText))) {
    const span = document.createElement('span');
    span.style.backgroundColor = 'yellow';
    span.textContent = match[0];
    const range = document.createRange();
    range.setStart(body, match.index);
    range.setEnd(body, regex.lastIndex);
    range.deleteContents();
    range.insertNode(span);
  }
}

这段代码会将页面中所有匹配指定文本的文本节点用黄色高亮显示。你可以将需要高亮的文本作为参数传入 highlightText 函数中,例如:

highlightText('JavaScript');

点评:代码是写出来了,用了一个我还没见过的 API。。。赶紧学习了一下 原理是:用 Range API 去选择对应的文本,然后使用 Range API 删除和新增高亮节点,思路看起来是没问题的,而且跟网上找到的不同

撤销高亮

我: 还要能够撤销高亮

GPT: 好的,为了实现撤销高亮的功能,我们可以在高亮文本时将每个被高亮的文本节点保存到一个数组中,然后在需要撤销高亮时,遍历这个数组,将每个高亮的文本节点恢复到原来的状态。

以下是修改后的代码:

let highlightedNodes = [];
function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  while ((match = regex.exec(body.innerText))) {
    const span = document.createElement('span');
    span.style.backgroundColor = 'yellow';
    span.textContent = match[0];
    const range = document.createRange();
    range.setStart(body, match.index);
    range.setEnd(body, regex.lastIndex);
    range.deleteContents();
    range.insertNode(span);
    regex.lastIndex -= match[0].length - span.textContent.length;
    highlightedNodes.push(span);
  }
}
function clearHighlight() {
  while (highlightedNodes.length > 0) {
    const node = highlightedNodes.pop();
    const parent = node.parentNode;
    parent.replaceChild(node.firstChild, node);
    parent.normalize();
  }
}

点评:牛逼,GPT 还是很聪明的,居然还会把 span 存起来,用于撤销高亮,这个思路也是非常的正确的。

Range 范围算不准

但是实际运行后,这段代码是会报错的,我排查了一下,发现计算出来的 Range 的 startend 的位置是错误的,于是我跟 GPT 说一下,看看它如何纠正

我:算不准 start 和 end 的位置

GPT: 如果 setStartsetEnd 方法的位置计算不准确,可能是因为在计算位置时没有考虑到文本节点之间的空格和换行符。在这种情况下,你可以使用 createTreeWalker 方法来遍历文档树中的所有文本节点,并计算出每个文本节点的起始和结束位置。

以下是一段示例代码:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
  while (walker.nextNode()) {
    const node = walker.currentNode;
    while ((match = regex.exec(node.nodeValue))) {
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      span.textContent = match[0];
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, regex.lastIndex);
      range.deleteContents();
      range.insertNode(span);
    }
  }
}

在这个修改后的代码中,我们使用 createTreeWalker 方法来遍历文档树中的所有文本节点。对于每个文本节点,我们使用 exec 方法来查找匹配指定文本的子串,并将每个匹配的子串用黄色高亮显示。在计算位置时,我们使用 setStartsetEnd 方法来设置范围的起始和结束位置,以确保高亮显示的文本范围正确。

点评:有点东西,我其实是不知道为什么算不准 startend 的位置,但 GPT 自己也能发现问题,并且给出了另一个解决方案,并且非常贴心解析了这段代码

根据 GPT 的提示,是空字符的文本节点导致的问题,跟 GPT 提出的假设类似。

页面卡死

但我把上面代码运行,发现页面卡死了,原因是,在 TreeWalker 遍历过程中,新增了高亮节点,然后又进入高亮节点进行遍历,然后又创建高亮节点。。。

因此应该在 TreeWalker 遍历节点之后再进行高亮节点替换,于是我让 GPT 继续改

我:在 TreeWalker 遍历过程中修改节点,会遍历到新节点,需要延迟修改节点

GPT: 是的,你说的很对。在 TreeWalker 遍历过程中修改节点,会导致遍历到新节点,从而可能会出现意外的结果。为了避免这种情况,我们可以将需要修改的节点保存到一个数组中,在遍历完成后再对这些节点进行修改。

以下是一段示例代码:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  const nodesToHighlight = [];
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
  while (walker.nextNode()) {
    const node = walker.currentNode;
    while ((match = regex.exec(node.nodeValue))) {
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      span.textContent = match[0];
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, regex.lastIndex);
      range.deleteContents();
      range.insertNode(span);
      nodesToHighlight.push(span);
    }
  }
  nodesToHighlight.forEach(node => {
    const parent = node.parentNode;
    parent.replaceChild(node.firstChild, node);
    parent.normalize();
  });
}

在这个修改后的代码中,我们新增了一个名为 nodesToHighlight 的数组,用于保存需要高亮的节点。在遍历过程中,每当找到一个需要高亮的节点时,我们将这个节点添加到 nodesToHighlight 数组中。在遍历完成后,我们遍历 nodesToHighlight 数组,对每个节点进行高亮处理。

这样做可以确保在遍历过程中不会修改节点,从而避免出现意外的结果。

点评:改了,但没完全改好,TreeWalker 中仍然有修改 DOM 的操作,于是我换了个说法

我:不能在遍历过程中调用 deleteContentsinsertNode

GPT: 是的,你说的很对。在遍历过程中调用 deleteContentsinsertNode 方法,会导致遍历到新节点,从而可能会出现意外的结果。为了避免这种情况,我们可以将需要修改的节点保存到一个数组中,在遍历完成后再对这些节点进行修改。

以下是一段示例代码:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  const nodesToHighlight = [];
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, {
    acceptNode: function(node) {
      if (node.parentNode.nodeName.match(/^(script|style)$/i)) {
        return NodeFilter.FILTER_REJECT;
      }
      return NodeFilter.FILTER_ACCEPT;
    }
  }, false);
  while (walker.nextNode()) {
    const node = walker.currentNode;
    while ((match = regex.exec(node.nodeValue))) {
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      span.textContent = match[0];
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, regex.lastIndex);
      nodesToHighlight.push({ range, span });
      regex.lastIndex -= match[0].length - span.textContent.length;
    }
  }
  setTimeout(() => {
    nodesToHighlight.forEach(({ range, span }) => {
      range.deleteContents();
      range.insertNode(span);
    });
  }, 0);
}

在这个修改后的代码中,我们新增了一个名为 nodesToHighlight 的数组,用于保存需要高亮的节点和它们对应的范围。在遍历过程中,每当找到一个需要高亮的节点时,我们将这个节点和它对应的范围添加到 nodesToHighlight 数组中。在遍历完成后,我们使用 setTimeout 方法将对节点的修改延迟到下一个事件循环中执行。

这样做可以确保在遍历过程中不会修改节点,从而避免出现意外的结果。同时,使用 setTimeout 方法可以将对节点的修改延迟到下一个事件循环中执行,从而确保在修改节点时不会影响遍历过程。

点评:这段代码终于能运行了,非常的优秀!

支持 shadowRoot

我:但这样还不能遍历 webComponent shadowRoot 里面的元素

GPT:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  const nodesToHighlight = [];
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, {
    acceptNode: function(node) {
      if (node.parentNode.nodeName.match(/^(script|style)$/i)) {
        return NodeFilter.FILTER_REJECT;
      }
      return NodeFilter.FILTER_ACCEPT;
    }
  }, false);
  while (walker.nextNode()) {
    const node = walker.currentNode;
    while ((match = regex.exec(node.nodeValue))) {
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      span.textContent = match[0];
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, regex.lastIndex);
      nodesToHighlight.push({ range, span });
      regex.lastIndex -= match[0].length - span.textContent.length;
    }
  }
  const shadowHosts = document.querySelectorAll('*');
  shadowHosts.forEach(shadowHost => {
    const shadowRoot = shadowHost.shadowRoot;
    if (shadowRoot) {
      const walker = document.createTreeWalker(shadowRoot, NodeFilter.SHOW_TEXT, {
        acceptNode: function(node) {
          if (node.parentNode.nodeName.match(/^(script|style)$/i)) {
            return NodeFilter.FILTER_REJECT;
          }
          return NodeFilter.FILTER_ACCEPT;
        }
      }, true);
      while (walker.nextNode()) {
        const node = walker.currentNode;
        while ((match = regex.exec(node.nodeValue))) {
          const span = document.createElement('span');
          span.style.backgroundColor = 'yellow';
          span.textContent = match[0];
          const range = document.createRange();
          range.setStart(node, match.index);
          range.setEnd(node, regex.lastIndex);
          nodesToHighlight.push({ range, span });
        }
      }
    }
  });
  setTimeout(() => {
    nodesToHighlight.forEach(({ range, span }) => {
      range.deleteContents();
      range.insertNode(span);
    });
  }, 0);
}

在这个修改后的代码中,我们使用 querySelectorAll 方法来获取所有元素,然后对于每个元素,我们检查它是否有 shadowRoot。如果有,我们使用 createTreeWalker 方法来遍历它的 shadowRoot 中的所有文本节点。在遍历过程中,我们使用与之前相同的方式来查找需要高亮的节点,并将它们添加到 nodesToHighlight 数组中。

这样做可以确保遍历到 shadowRoot 中的所有文本节点,并将需要高亮的节点保存到 nodesToHighlight 数组中。然后,我们使用 setTimeout 方法将对节点的修改延迟到下一个事件循环中执行,从而确保在修改节点时不会影响遍历过程。

点评:这段代码也是能运行的,很牛逼,不过没考虑代码的复用性,shadowRoot 的处理,可以复用普通的 DOM 的高亮逻辑

撤销操作支持 shadowRoot

我:撤销操作也要作用到 shadowRoot

GPT: 是的,你说的很对。如果需要对 shadowRoot 中的高亮文本进行撤销操作,我们可以使用 Range 对象的 deleteContents() 方法来删除高亮文本所在的节点,并将原始文本重新插入到文档中。

以下是一段示例代码:

function removeHighlight() {
  const highlightedSpans = document.querySelectorAll('span[style="background-color: yellow;"]');
  highlightedSpans.forEach(span => {
    const range = document.createRange();
    range.selectNode(span);
    range.deleteContents();
    range.insertNode(document.createTextNode(span.textContent));
  });
  const shadowHosts = document.querySelectorAll('*');
  shadowHosts.forEach(shadowHost => {
    if (shadowHost.shadowRoot) {
      const highlightedSpans = shadowHost.shadowRoot.querySelectorAll('span[style="background-color: yellow;"]');
      highlightedSpans.forEach(span => {
        const range = document.createRange();
        range.selectNode(span);
        range.deleteContents();
        range.insertNode(document.createTextNode(span.textContent));
      });
    }
  });
}

点评:代码可以运行,但有以下缺点:

  • 同样是复用性不足,
  • 高亮节点的选择器 span[style="background-color: yellow;"] 不太优雅,不够健壮 但问题不大,我自己能改hhh

highligh 函数需要支持重复调用

目前的代码会有一个问题,就是重复调用 highlightText,已高亮的节点,仍然会创建 span,这就会导致 span 重复嵌套。需要先判断是否已经高亮了。

我:highlightText 如果同一个值多次调用,会给文本节点再嵌套一层高亮节点,要支持重复调用不会重复嵌套节点

GPT: 好的,如果需要支持重复调用 highlightText 函数时不会重复嵌套高亮节点,我们可以在每次调用函数之前先检查文本节点是否已经被高亮过,如果已经被高亮过,则不再重复嵌套高亮节点。

以下是修改后的 highlightText 函数代码:

function highlightText(text) {
  const body = document.body;
  const regex = new RegExp(text, 'gi');
  let match;
  const nodesToHighlight = [];
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, {
    acceptNode: function(node) {
      if (node.parentNode.nodeName.match(/^(script|style)$/i)) {
        return NodeFilter.FILTER_REJECT;
      }
      return NodeFilter.FILTER_ACCEPT;
    }
  }, false);
  while (walker.nextNode()) {
    const node = walker.currentNode;
    if (node.parentNode.classList.contains('highlighted')) {
      continue;
    }
    while ((match = regex.exec(node.nodeValue))) {
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      span.textContent = match[0];
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, regex.lastIndex);
      nodesToHighlight.push({ range, span });
      regex.lastIndex -= match[0].length - span.textContent.length;
    }
  }
  const shadowHosts = document.querySelectorAll('*');
  shadowHosts.forEach(shadowHost => {
    if (shadowHost.shadowRoot) {
      const shadowRoot = shadowHost.shadowRoot;
      const walker = document.createTreeWalker(shadowRoot, NodeFilter.SHOW_TEXT, {
        acceptNode: function(node) {
          if (node.parentNode.nodeName.match(/^(script|style)$/i)) {
            return NodeFilter.FILTER_REJECT;
          }
          return NodeFilter.FILTER_ACCEPT;
        }
      }, true);
      while (walker.nextNode()) {
        const node = walker.currentNode;
        if (node.parentNode.classList.contains('highlighted')) {
          continue;
        }
        while ((match = regex.exec(node.nodeValue))) {
          const span = document.createElement('span');
          span.style.backgroundColor = 'yellow';
          span.textContent = match[0];
          const range = document.createRange();
          range.setStart(node, match.index);
          range.setEnd(node, regex.lastIndex);
          nodesToHighlight.push({ range, span });
          regex.lastIndex -= match[0].length - span.textContent.length;
        }
      }
    }
  });
  nodesToHighlight.forEach(nodeToHighlight => {
    const range = nodeToHighlight.range;
    const span = nodeToHighlight.span;
    range.deleteContents();
    span.classList.add('highlighted');
    range.insertNode(span);
  });
}

在这个代码中,我们添加了一个名为 highlighted 的 CSS 类,用于标记已经被高亮过的文本节点。在每次遍历文本节点时,我们检查当前节点的父节点是否已经被标记为 highlighted,如果是,则跳过当前节点,不再重复嵌套高亮节点。在插入高亮节点时,我们还将其添加到 highlighted 类中,以便下次遍历时可以正确地跳过已经被高亮过的节点。

点评:完美符合要求~我只需要稍微复用以下代码,进行一些小修改即可。

最终还是成为了一名代码美化工程师~~~

使用体验

GPT 确实非常强大,它知道的非常多,有我不知道的知识,如果不是 GPT 我的确不会知道可以使用 Range API 去操作 DOM 实现高亮。

它还能较好的根据提示,纠正自己的错误

我们可以让 GPT 提供思路,写一个简单的 Demo,然后自己进行代码优化。

目前这种使用方式的效率还有待提升。整个过程耗费了 1 个多小时,写了不到 100 行代码。在对话的过程中,还需要将代码复制到 IDE 中,这并没有将 GPT 和 IDE 串联起来。

不过,我们可以尝使用 Cursor 这个 IDE,它可以直接在 IDE 中使用 GPT,这样应该会有更好的编码体验。

以上就是使用GPT写代码实现高亮页面关键字的详细内容,更多关于GPT 高亮页面关键字的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用GPT写代码实现高亮页面关键字

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用GPT写代码实现高亮页面关键字

这篇文章主要为大家介绍了使用GPT写代码实现高亮页面关键字示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-16

Mysql使用concat函数实现关键字模糊查询功能(列表数据过滤含前后端代码)

目录前言页面布局sql编写后端代码接口测试前端代码测试效果总结前言不知道大家在开发中有没有这样的经历:根据条件过滤列表数据项。这种的条件少的还好,比如根据姓名或者性别过滤,这样不仅页面会稍微美观一些,对于sql的压力以及后端的代码量也是
2023-02-14

编程热搜

目录