2

We need to add anchors and highlights for some keywords/sentences in the html page. It turns out the highlighting is really slow in Firefox.

In the following code, all ranges which need to be highlighted are stored in array hiliteRanges:

for (var i = 0; i < hiliteRanges.length; i++){
    document.designMode = "on";

    var selHilites = window.getSelection();

    if (selHilites.rangeCount > 0)
        selHilites.removeAllRanges();

    selHilites.addRange(hiliteRanges[i]);

    var anchorId = 'index'+i;
    var insertedHTML = '<span id="' + anchorId + '" style="background-color: #FF8C00;" >'+hiliteRanges[i].toString()+'</span>';

    document.execCommand('inserthtml', false, insertedHTML);                                                                                    
    document.designMode = "off";
}

Is there any way to speed up the processing? We could have hundreds of ranges in the array hiliteRanges. We once tried moving the designMode setting outside of the loop, but we can see some sections are editable in the html page when the loop is running.

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
  • Related: [Javascript: document.execCommand cross-browser?](http://stackoverflow.com/questions/1817441/javascript-document-execcommand-cross-browser) Why don't you use `contentEditable`? Or is this somehow related to the `designMode` approach? – Jared Farrish Sep 20 '12 at 03:15
  • Is this an appropriate demo of what you're doing? http://jsfiddle.net/userdude/cHCcm/1 – Jared Farrish Sep 20 '12 at 03:34
  • In fact, I first search the keywords using window.find method of FF and store all selected ranges into array hiliteRanges. Then I want to highlights all these selected ranges in FF. Can I use contentEditable on these selected ranges? –  Sep 20 '12 at 04:32
  • The javascript engine on firefox is slower than both ie and chrome, the problem you are having is purely that too much processing. Move as much code outside of the for loop, and look to refactor code as much as possible. – Mark Broadhurst Sep 20 '12 at 09:58
  • @SaintGerbil This has nothing to do with JavaScript; the real problem is turning all the editing machinery on and off repeatedly. On a separate note, the JS engine in Firefox is in fact faster than IE and about comparable to Chrome, depending on what you're doing with it. – Boris Zbarsky Sep 21 '12 at 05:24

2 Answers2

2

This is my default highlighting snippet and works fine in every browser. Try it out.

Demo: http://jsbin.com/adeneh/1/edit

function highlight(text, words, tag) {

  // Default tag if no tag is provided
  tag = tag || 'span';

  var i, len = words.length, re;
  for (i = 0; i < len; i++) {
    // Global regex to highlight all matches
    re = new RegExp(words[i], 'g');
    if (re.test(text)) {
      text = text.replace(re, '<'+ tag +' class="highlight">$&</'+ tag +'>');
    }
  }

  return text;
}

// Usage:
var el = document.getElementById('element');
el.innerHTML = highlight(
  el.innerHTML, 
  ['word1', 'word2', 'phrase one', 'phrase two', ...]
);

And to unhighlight:

function unhighlight(text, tag) {
  // Default tag if no tag is provided
  tag = tag || 'span';
  var re = new RegExp('(<'+ tag +'.+?>|<\/'+ tag +'>)', 'g');
  return text.replace(re, '');
}
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • Thanks. But the array contains all selected ranges, which are collected using window.find method, instead of strings. –  Sep 20 '12 at 04:47
  • It shouldn't be too hard to make an array of strings out of your data. – elclanrs Sep 20 '12 at 04:56
  • Yes. Perhaps just need to get the parentElement of each range and then replace its innerHTML. Will the modification affect other ranges? I'm not sure if the position of these ranges will be messed up... If so the parentelement will be wrong. For example, if we change the innerHTML of the container for range hiliteRanges[0], can we still get the correct container for range hiliteRanges[1000]? Basically ranges in array hiliteRanges are random... –  Sep 20 '12 at 05:07
  • The advantage of the `window.find()` approach is that you can match text that crosses element boundaries, which is not possible with this approach. However, `window.find()` may disappear in the future: https://bugzilla.mozilla.org/show_bug.cgi?id=672395 – Tim Down Sep 20 '12 at 08:45
  • The advantage of choosing find() method is that we don't need to worry about the html tags and js tags. It is much easier to find/search keywords. Don't want it disappear... –  Sep 20 '12 at 23:06
0

There's no need to use document.execCommand() for this. Just use range methods instead, and then there's no need for designMode.

var anchorId, hiliteTextNode, hiliteSpan;
for (var i = 0; i < hiliteRanges.length; i++){
    // Create the highlight element
    hiliteSpan = document.createElement("span");
    hiliteSpan.id = anchorId;
    hiliteSpan.style.backgroundColor = "#FF8C00";

    hiliteTextNode = document.createTextNode(hiliteRanges[i].toString());
    hiliteSpan.appendChild(hiliteTextNode);

    // Replace the range content
    hiliteRanges[i].deleteContents();
    hiliteRanges[i].insertNode(hiliteSpan);
}

Also, since ranges are affected by DOM mutation, I would suggest doing this part at the same time as you collect the ranges with window.find(). Here's an example:

http://jsfiddle.net/YgFjT/

Tim Down
  • 318,141
  • 75
  • 454
  • 536