Skip to content Skip to sidebar Skip to footer

How To Highlight Search Text From String Of Html Content Without Breaking

I am looking for some solution that help to search term from html string with highlight feature. I can do this by removing html content from string. But then issue is I will not ab

Solution 1:

I had exactly the same requirements on a WYSIWYG project.

How it works ?

  1. By mapping HTML elements to their plain-text version while keeping the original size of the element.

  2. Then for each match, select back the corresponding portion of each element included in the match, using the previous mapping and setSelectionRange

  3. Mark each of them using execCommand as I explained in this related question. You then can use getSelection().anchorNode.parentElement to get a ready-to-use wrapped selection, corresponding here to your match.

  4. Then you can apply any style you want on it !

Using this method, you'll have some serious advantages:

  • Support of multiple selections (I don't know a single browser allowing multiple selections with their native search)

  • Support of search spread on multiple elements (here also, most used browsers don't have this feature)

  • Support of regular expressions :)

  • Manipulate the matches as you want, no restriction on the style, you can even modify the content (for auto-correct purposes for instance)

  • Mapping to the Source Pane if you are also doing a WYSIWYG. (See how selecting in the Content Pane highlight the corresponding source in the ACE editor below)

Here is a quick showcase:

enter image description here

TIP I seriously advise you to put all execCommand calls inside requestAnimationFrame(), because each call will trigger a forced reflow, which will make you app loose performances. Usually, a browser triggers 60 reflows/s but here, if let's say you have 300 matches, you will add 300 extra reflows. By calling execCommand inside requestAnimationFrame(), you will tell the browser to use a planned reflow and not to add tons more.


Solution 2:

One thing you can use is getClientRects method of the Range object: https://developer.mozilla.org/en-US/docs/Web/API/range/getClientRects

This allows you to add divs with the coordinates of your search, allowing you to highlight text without having to manipulate the DOM.

Finding the nodes isn't that straighforward (especially if you have a complicated structure), but you can iterate through all text nodes to match the index of the search in the textContent of the element in which you want to search.

So first you match the search result to the DOM. In the example I use a recursive generator, but any recursive loop will do. Basically what you need to do is go through every text node to match the index of a search. So you go through every descendant node and count the texts length so you can match your search to a node.

Once this is done, you can create a Range from these results, and then you add elements with the coordinates of the rectangles you get with getClientRects. By giving then a z-index negative and an absolute position, they will appear under the text at the right place. So you'll have a highlight effect, but without touching the HTML you are searching. Like this:

document.querySelector('#a').onclick = (e) => {

  let topParent = document.querySelector('#b');
  let s, range;
  let strToSearch = document.querySelector('#search').value
  let re = RegExp(strToSearch, 'g')

  removeHighlight()
  s = window.getSelection();
  s.removeAllRanges()
  // to handle multiple result you need to go through all matches
  while (match = re.exec(topParent.textContent)) {

    let it = iterateNode(topParent);
    let currentIndex = 0;
    // the result is the text node, so you can iterate and compare the index you are searching to all text nodes length
    let result = it.next();

    while (!result.done) {
      if (match.index >= currentIndex && match.index < currentIndex + result.value.length) {
        // when we have the correct node and index we add a range
        range = new Range();
        range.setStart(result.value, match.index - currentIndex)

      }
      if (match.index + strToSearch.length >= currentIndex && match.index + strToSearch.length < currentIndex + result.value.length) {
        // when we find the end node, we can set the range end
        range.setEnd(result.value, match.index + strToSearch.length - currentIndex)
        s.addRange(range)

        // this is where we add the divs based on the client rects of the range
        addHighlightDiv(range.getClientRects())


      }
      currentIndex += result.value.length;
      result = it.next();
    }
  }
  s.removeAllRanges()

}


function* iterateNode(topNode) {
  // this iterate through all descendants of the topnode
  let childNodes = topNode.childNodes;
  for (let i = 0; i < childNodes.length; i++) {
    let node = childNodes[i]
    if (node.nodeType === 3) {
      yield node;
    } else {
      yield* iterateNode(node);
    }
  }

}

function addHighlightDiv(rects) {
  for (let i = 0; i < rects.length; i++) {

    let rect = rects[i];
    let highlightRect = document.createElement('DIV')
    document.body.appendChild(highlightRect)
    highlightRect.classList.add('hl')
    highlightRect.style.top = rect.y + window.scrollY + 'px'
    highlightRect.style.left = rect.x + 'px'
    highlightRect.style.height = rect.height + 'px'
    highlightRect.style.width = rect.width + 'px'

  }

}

function removeHighlight() {
  let highlights = document.querySelectorAll('.hl');
  for (let i = 0; i < highlights.length; i++) {
    highlights[i].remove();
  }
}
.hl {
  background-color: red;
  position: absolute;
  z-index: -1;
}
<input type="text" id="search" /><button id="a">search</button>
<div id="b">
  <h1>Lorem ipsum dolor sit amet</h1>, consectetur
  <h2>adipiscing elit, sed do</h2> eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud <strong>exercitation <span>ullamco laboris</span> nisi ut aliquip ex ea commodo</strong> consequat. Duis aute irure dolor
  in reprehenderit in voluptate velit <em>esse cillum dolore eu fugiat nulla pariatur.</em> Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>

Solution 3:

So instead of re-creating that HTML part and doing some re-render, I'd probably do it on the client side. By googling I have found:

Or for more detail 'bout implementation, check out this: How to highlight text using javascript


Solution 4:

There are several ways to do this. Here is a pretty nifty implementation from another similar question:

How wrap part of a text in a node


Post a Comment for "How To Highlight Search Text From String Of Html Content Without Breaking"