Skip to content Skip to sidebar Skip to footer

Highlight Substring In Element

What is the best way to highlight a specific substring in a div element in an event? By highlight, I mean apply some CSS style, like yellow background or something. Basically I ne

Solution 1:

You will need to wrap the text that you want to in it's own <span> tag so you can give that text its own style. Using your requested function definition, you could do it like this:

function (element, start, end) { 
    var str = element.innerHTML;
    str = str.substr(0, start) +
        '<span class="hilite">' + 
        str.substr(start, end - start + 1) +
        '</span>' +
        str.substr(end + 1);
    element.innerHTML = str;
}

You can then define CSS for the class hilite to control the style of that text.

.hilite {color: yellow;}

This assumes that start and end are indexes into the innerHTML of the first and last characters that you want highlighted.

If you want to be able to call it repeatedly on the same element (to move the higlight around), you could do it like this:

function (element, start, end) {
    var item = $(element);
    var str = item.data("origHTML");
    if (!str) {
        str = item.html();
        item.data("origHTML", str);
    }
    str = str.substr(0, start) +
        '<span class="hilite">' + 
        str.substr(start, end - start + 1) +
        '</span>' +
        str.substr(end + 1);
    item.html(str);
}

Solution 2:

If you have no attached events or complicated HTML, you can just do search and replace on the HTML:

element.innerHTML = element.innerHTML.replace(/search/gi, function(match) {
    return '<span class="highlight">' + match + '</span>'
});

If you want something better, you can manipulate the DOM directly without using innerHTML, which will preserve events and work for more complicated HTML:

/*
 * Takes in an array of consecutive TextNodes and returns a document fragment with `word` highlighted
 */
function highlight_text_nodes($nodes, word) {
    if (!$nodes.length) {
        return;
    }

    var text = '';

    // Concatenate the consecutive nodes to get the actual text
    for (var i = 0; i < $nodes.length; i++) {
        text += $nodes[i].textContent;
    }

    var $fragment = document.createDocumentFragment();

    while (true) {
        // Tweak this if you want to change the highlighting behavior
        var index = text.toLowerCase().indexOf(word.toLowerCase());

        if (index === -1) {
            break;
        }

        // Split the text into [before, match, after]
        var before = text.slice(0, index);
        var match = text.slice(index, index + word.length);
        text = text.slice(index + word.length);

        // Create the <mark>
        var $mark = document.createElement('mark');
        $mark.className = 'found';
        $mark.appendChild(document.createTextNode(match));

        // Append it to the fragment
        $fragment.appendChild(document.createTextNode(before));
        $fragment.appendChild($mark);
    }

    // If we have leftover text, just append it to the end
    if (text.length) {
        $fragment.appendChild(document.createTextNode(text));
    }

    // Replace the nodes with the fragment
    $nodes[0].parentNode.insertBefore($fragment, $nodes[0]);

    for (var i = 0; i < $nodes.length; i++) {
        var $node = $nodes[$nodes.length - i - 1];
        $node.parentNode.removeChild($node);
    }
}


/*
 * Highlights all instances of `word` in `$node` and its children
 */
function highlight($node, word) {
    var $children = $node.childNodes;
    var $current_run = [];

    for (var i = 0; i < $children.length; i++) {
        var $child = $children[i];

        if ($child.nodeType === Node.TEXT_NODE) {
            // Keep track of consecutive text nodes
            $current_run.push($child);
        } else {
            // If we hit a regular element, highlight what we have and start over
            highlight_text_nodes($current_run, word);
            $current_run = [];

            // Ignore text inside of our <mark>s
            if ($child.nodeType === Node.ELEMENT_NODE && $child.className !== 'found') {
                highlight($child, word);
            }
        }
    }

    // Just in case we have only text nodes as children
    if ($current_run.length) {
        highlight_text_nodes($current_run, word);
    }
}

/*
 * Removes all highlighted <mark>s from the given node
 */
function unhighlight($node) {
    var $marks = [].slice.call($node.querySelectorAll('mark.found'));

    for (var i = 0; i < $marks.length; i++) {
        var $mark = $marks[i];

        // Replace each <mark> with just a text node of its contents
        $mark.parentNode.replaceChild(document.createTextNode($mark.childNodes[0].textContent), $mark);
    }
}

Demo: https://jsfiddle.net/wLkbbo5m/4/

If you want even more features, just use a library (like mark.js). There's no point in reinventing the entire wheel.


Solution 3:

In a React project

I was worried about how other libraries' DOM-manipulation would work in a React project, so I used react-hightlight-words to solve this problem.

See the table of props for configuration.

Out of the box it takes search words instead of index ranges, but solves the same problem as in the question unless one is locked to indices. However, if that is the case, the findChunks property can be given a function to arbitrarily detect parts to hightlight. (See here how the default findChunks is defined.)

Simple example from the description on GitHub.

import React from "react";
import ReactDOM from "react-dom";
import Highlighter from "react-highlight-words";

ReactDOM.render(
    <Highlighter
        highlightClassName="YourHighlightClass"
        searchWords={["and", "or", "the"]}
        autoEscape={true}
        textToHighlight="The dog is chasing the cat. Or perhaps they're just playing?"
    />,
    document.getElementById("root")
);

Install with npm i --save react-highlight-words


Solution 4:

You can use mark.js which provides a simple and powerful plugin :

$('p').mark('sit');
mark {
  background: orange;
  color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mark.js@8.11.1/dist/jquery.mark.min.js"></script>

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing 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>

Solution 5:

<div id="myElement">Hello everyone! guess Who</div>

<script>    
HTMLElement.prototype.mark=function(Str=null){var T=this;
    if(Str === null){ 
        if(T.oV){ T.innerHTML=T.oV }
        return T
    }
    var V = T.oV = T.oV || T.innerText, s = V.indexOf(Str), e = Str.length + s;
    if(s < 0){return;}//substring not found
    V = V.substr(0,s) + `<span style="background-color:lightblue">` + V.substr(s,e - s + 1) + `</span>` + V.substr(e+1);
    T.innerHTML=V;
    return T;
}

var E = document.getElementById("myElement");
  E.mark("Hello");//finds the string and highlight
  // E.mark();// revert to original text
</script>

working example:

https://jsfiddle.net/xwyqth6k/


Post a Comment for "Highlight Substring In Element"