【问题标题】:Highlight search terms (select only leaf nodes)突出显示搜索词(仅选择叶节点)
【发布时间】:2011-03-15 13:06:10
【问题描述】:

我想突出显示页面上的搜索词,但不要弄乱任何 HTML 标记。我在想类似的东西:

$('.searchResult *').each(function() {
    $(this.html($(this).html().replace(new RegExp('(term)', 'gi'), '<span class="highlight">$1</span>'));
)};

但是,$('.searchResult *').each 匹配所有元素,而不仅仅是叶节点。换句话说,一些匹配的元素里面有 HTML。所以我有几个问题:

  1. 如何只匹配叶节点?
  2. 是否有一些内置的 jQuery RegEx 函数来简化事情?比如:$(this).wrap('term', $('&lt;span /&gt;', { 'class': 'highlight' }))
  3. 有没有办法进行简单的字符串替换而不是正则表达式?
  4. 还有其他更好/更快的方法吗?

非常感谢!

【问题讨论】:

  • 您可以使用例如标记.js

标签: jquery highlighting


【解决方案1】:

我的声誉不够高,无法发表评论或添加更多链接,所以很抱歉在没有所有引用的情况下写一个新答案。

我对上述解决方案的性能很感兴趣,并添加了一些代码进行测量。为了简单起见,我只添加了以下几行:

var start = new Date();
// hightlighting code goes here ...
var end = new Date();
var ms = end.getTime() - start.getTime();
jQuery("#time-ms").text(ms);

我已经用这些线分叉了 Anurag 的解决方案,这导致平均 40-60 毫秒。

所以我分叉了这个小提琴并进行了一些改进以满足我的需求。一件事是正则表达式转义(请参阅 Stackoverflow 中“escape-string-for-use-in-javascript-regex”中 CoolAJ86 的答案)。 另一点是防止第二个“新 RegExp()”,因为 RegExp.test 函数应该忽略全局标志并在第一次匹配时返回(请参阅 RegExp.test 上的 javascript 参考)。

在我的机器(铬、Linux)上,我的运行时间约为 30-50 毫秒。您可以在jsfiddle 中自行测试。

我还将我的计时器添加到评分最高的 galambalazs 解决方案中,您可以在 jsFiddle 中找到它。但是这个有 60-100 毫秒的运行时间。

以毫秒为单位的值在运行时变得更高,并且更加重要(例如在 Firefox 中大约四分之一秒)。

【讨论】:

    【解决方案2】:

    我已经做了一个纯 JavaScript 版本,并将其打包成一个谷歌浏览器插件,希望对某些人有所帮助。核心功能如下图:

    GitHub Page for In-page Highlighter

    function highlight(term){
        if(!term){
            return false;
        }
    
        //use treeWalker to find all text nodes that match selection
        //supported by Chrome(1.0+)
        //see more at https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
        var treeWalker = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            null,
            false
            );
        var node = null;
        var matches = [];
        while(node = treeWalker.nextNode()){
            if(node.nodeType === 3 && node.data.indexOf(term) !== -1){
                matches.push(node);
            }
        }
    
        //deal with those matched text nodes
        for(var i=0; i<matches.length; i++){
            node = matches[i];
            //empty the parent node
            var parent = node.parentNode;
            if(!parent){
                parent = node;
                parent.nodeValue = '';
            }
            //prevent duplicate highlighting
            else if(parent.className == "highlight"){
                continue;
            }
            else{
                while(parent && parent.firstChild){
                    parent.removeChild(parent.firstChild);
                }
            }
    
            //find every occurance using split function
            var parts = node.data.split(new RegExp('('+term+')'));
            for(var j=0; j<parts.length; j++){
                var part = parts[j];
                //continue if it's empty
                if(!part){
                    continue;
                }
                //create new element node to wrap selection
                else if(part == term){
                    var newNode = document.createElement("span");
                    newNode.className = "highlight";
                    newNode.innerText = part;
                    parent.appendChild(newNode);
                }
                //create new text node to place remaining text
                else{
                    var newTextNode = document.createTextNode(part);
                    parent.appendChild(newTextNode);
                }
            }
    
        }
    }
    

    【讨论】:

    • 嘿,您能帮我完成整个过程吗?为什么要清除父节点?
    【解决方案3】:

    我花了几个小时在网络上搜索可以在用户键入时突出显示搜索词的代码,但在我将一堆东西组合在一起之前,没有人可以做我想做的事情 (jsfiddle demo here):

    $.fn.replaceText = function(search, replace, text_only) {
        //http://stackoverflow.com/a/13918483/470749
        return this.each(function(){  
            var v1, v2, rem = [];
            $(this).find("*").andSelf().contents().each(function(){
                if(this.nodeType === 3) {
                    v1 = this.nodeValue;
                    v2 = v1.replace(search, replace);
                    if(v1 != v2) {
                        if(!text_only && /<.*>/.test(v2)) {  
                            $(this).before( v2 );  
                            rem.push(this);  
                        } else {
                            this.nodeValue = v2;  
                        }
                    }
                }
            });
            if(rem.length) {
                $(rem).remove();
            }
        });
    };
    
    function replaceParentsWithChildren(parentElements){
        parentElements.each(function() {
            var parent = this;
            var grandparent = parent.parentNode;
            $(parent).replaceWith(parent.childNodes);
            grandparent.normalize();//merge adjacent text nodes
        });
    }
    
    function highlightQuery(query, highlightClass, targetSelector, selectorToExclude){
        replaceParentsWithChildren($('.' + highlightClass));//Remove old highlight wrappers.
        $(targetSelector).replaceText(new RegExp(query, "gi"), function(match) {
            return '<span class="' + highlightClass + '">' + match + "</span>";
        }, false);
        replaceParentsWithChildren($(selectorToExclude + ' .' + highlightClass));//Do not highlight children of this selector.
    }
    

    【讨论】:

      【解决方案4】:

      [See it in action]

      // escape by Colin Snover
      // Note: if you don't care for (), you can remove it..
      RegExp.escape = function(text) {
          return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
      }
      
      function highlight(term, base) {
        if (!term) return;
        base = base || document.body;
        var re = new RegExp("(" + RegExp.escape(term) + ")", "gi"); //... just use term
        var replacement = "<span class='highlight'>" + term + "</span>";
        $("*", base).contents().each( function(i, el) {
          if (el.nodeType === 3) {
            var data = el.data;
            if (data = data.replace(re, replacement)) {
              var wrapper = $("<span>").html(data);
              $(el).before(wrapper.contents()).remove();
            }
          }
        });
      }
      
      function dehighlight(term, base) {
        var text = document.createTextNode(term);
        $('span.highlight', base).each(function () {
          this.parentNode.replaceChild(text.cloneNode(false), this);
        });
      }
      

      【讨论】:

      • See it in action 示例不适合我。但是,我忘记了 :contains 选择器,它应该有助于选择“叶”节点而不是进行不必要的替换。我试试看。
      • 我猜在each之前创建一次RegExp变量并在each中重用它会更有效?
      • 是的,它将被预编译并且应该更快。现在检查链接:)
      • @Nelson - contains 会进行不区分大小写的搜索,因此最好在 text() 上进行正则表达式搜索。此外,html 被覆盖的任何解决方案都会遇到两个问题 - 现有行为(例如事件)将被覆盖,并且搜索词可能与 html 冲突。见jsfiddle.net/BcsQG/1
      • 在 ajax 响应数据上使用“highlight(term, base) {}”函数...我必须在右大括号之前添加它:`return base;`,以使其工作。否则没有输出。
      【解决方案5】:

      使用contents()123获取包括文本节点在内的所有节点,过滤掉非文本节点,最后使用正则表达式替换每个剩余文本节点的nodeValue。这将保持 html 节点完好无损,并且只修改文本节点。您必须使用正则表达式而不是简单的字符串替换,因为不幸的是,当搜索词是字符串时,我们无法进行全局替换。

      function highlight(term) {
          var regex = new RegExp("(" + term + ")", "gi");
          var localRegex = new RegExp("(" + term + ")", "i");
          var replace = '<span class="highlight">$1</span>';
      
          $('body *').contents().each(function() {
              // skip all non-text nodes, and text nodes that don't contain term
              if(this.nodeType != 3 || !localRegex.test(this.nodeValue)) {
                  return;
              }
              // replace text node with new node(s)
              var wrapped = $('<div>').append(this.nodeValue.replace(regex, replace));
              $(this).before(wrapped.contents()).remove();
          });
      }
      

      我们现在不能轻易地把它做成单线和更短的,所以我更喜欢这样 :)

      See example here.

      【讨论】:

      • dis 有问题,我们无法设置文本节点的nodeValue,希望它能正常工作:)。必须用 span 元素替换文本节点。
      • 修复了错误,现在只替换文本节点。是否替换整个html
      • 它会因为(this)之类的东西而失败
      • @galambalazs - 你能详细说明为什么(this) 会是一个破坏性的输入吗?
      • 啊,我明白了,感谢您指出这一点。我很想使用您的RegExp.escape 解决方案,但会让这个错误通过:)
      【解决方案6】:

      我会试一试Highlight jQuery 插件。

      【讨论】:

      • 我看到了,但它只是暂时突出显示。我需要突出显示这些术语。此外,如果页面上有数十或数百个匹配项,淡出效果可能不是一个好主意。
      • 您可能在我编辑之前点击了它。我最初有错误的链接。 jQueryUI 中的那个确实是临时的,但是 johannburkard.de 上的那个是永久的,直到你调用 removeHighlight(),并且没有淡入淡出效果。
      • 新链接按预期工作。我最终可能最终会使用它,但 galambalazs 更直接地回答了我的问题。
      【解决方案7】:

      这是一个简单的实现,它只在 HTML 中爆炸以匹配任何匹配:

      <!DOCTYPE html>
      <html lang"en">
      <head>
          <title>Select Me</title>
          <style>
              .highlight {
                  background:#FF0;
              }
          </style>
          <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js"></script>
          <script type="text/javascript">
      
              $(function () {
      
                  hightlightKeyword('adipisicing');
      
              });
      
              function hightlightKeyword(keyword) {
      
                  var replacement = '<span class="highlight">' + keyword + '</span>';
                  var search = new RegExp(keyword, "gi");
                  var newHtml = $('body').html().replace(search, replacement);
                  $('body').html(newHtml);
              }
      
          </script>
      </head>
      <body>
          <div>
      
              <p>Lorem ipsum dolor sit amet, consectetur <b>adipisicing</b> elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
              <p>Lorem ipsum dolor sit amet, <em>consectetur adipisicing elit</em>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
              <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
      
          </div>
      </body>
      </html>
      

      【讨论】:

      • 是的,问题在于它可以匹配 html 标签。如果关键字是p(段落),则您的 HTML 已损坏。
      猜你喜欢
      • 2013-09-16
      • 1970-01-01
      • 2017-01-23
      • 2012-08-04
      • 2012-05-21
      • 1970-01-01
      • 2016-11-25
      相关资源
      最近更新 更多