Skip to content

March 24, 2011

4

Highlight words in text with jQuery

highlight words jqueryOn the right is an example of word-highlighting in a blog post, done with jQuery (my javascript library of choice).  I needed to highlight certain words in blocks of formatted text, so I went searching for an existing solution.  I found a pretty good starting point, posted by bjarlestam on Stackoverflow.  His highlighting function is below.  It wraps the word inside a span tag with a class of your choosing.

jQuery.fn.highlight = function (str, className) {
    var regex = new RegExp(str, "gi");
    return this.each(function () {
        this.innerHTML = this.innerHTML.replace(regex, function(matched) {
            return "<span class=\"" + className + "\">" + matched + "</span>";
        });
    });
};

This is nice, because it uses a case-insensitive regular expression to find the word to highlight.  However, there is a flaw.  If you call this function on an element with non-text child elements, and the tag/attribute names or attribute values match the word to highlight, this function will mess up your HTML.  We’d like it to apply only to text nodes.

Initially I thought this would be easy.  There must be a text selector in jQuery, allowing you to select only text nodes. But (as of this writing) there isn’t!  (There is a “:text” selector, but it’s for input tags.) So, maybe we could use the :contains() selector, which selects all elements with text containing a given string.  But (as of this writing), the :contains() selector is case-sensitive only, so I abandoned that idea.

It turns out that I had to use the .contents() and .filter() methods in combination to select all the text nodes.  .contents() returns all children including text nodes.  .filter() can narrow those down to just text nodes by checking this.nodeType == 3.  Now all we must do is set the value of these nodes to the output of the regex, right?  Nope, because this output may contain <span> tags, and text nodes cannot have HTML tags as children.  We must actually replace the text nodes with the output of the regex.  That’s where the .replaceWith() method comes in. The replacing content may be text-only or a mix of text and tags.

So here it is, my jQuery function for highlighting words in text, case-insensitive:

jQuery.fn.highlight = function (str, className) {
    var regex = new RegExp(str, "gi");
    return this.each(function () {
        $(this).contents().filter(function() {
            return this.nodeType == 3 && regex.test(this.nodeValue);
        }).replaceWith(function() {
            return (this.nodeValue || "").replace(regex, function(match) {
                return "<span class=\"" + className + "\">" + match + "</span>";
            });
        });
    });
};

** Requires jQuery 1.4.2 or higher! ** Will not work with older versions.

Note, I added regex.test(this.nodeValue) inside the filter. Doing so limits the number of regex replacement calls, and resulted in a 30% speed increase on Firefox.

Calling this function, for example on all descendants of elements with class “testHighlight”, looks like this:

$(".testHighlight *").highlight("afghanistan", "highlight");

All instances of the word “afghanistan” will be wrapped in a span tag with the class set to “highlight” (defined in CSS to have background-color: #ff2;). Again, this is case-insensitive. Tested on Firefox 3.6 and IE8. Please let me know if it breaks on other browsers, or if the code can be improved.

Quick demo: highlight the word jQuery.

Read more from Javascript
4 Comments Post a comment
  1. Sean
    May 25 2011

    Perfect! I’ve used it for filtering and highlighting unordered lists.

    Reply
  2. Malin
    Aug 28 2012

    Perfect! My only problem is that I would like to use this for highlighting search result on a page. The problem is if the user first enters e.g. “1″, all “1″ are highlighted in this text: 19740505-1234. Good!

    I then remove the highlighting with following: $(“.highlightWord”).replaceWith(function() { return $(this).contents(); }); which works fine!

    The problem is when I try to search again with a longer searchstring, e.g. “19″ nothing becomes highlighted. I guess the reason is that after removing the highlighting, the html-text still i splitted from the first call to highlight. Like this: text: 1, text: 9740505-, text: 1, text: 234.

    Any suggestions?

    Reply
  3. shaynes
    Nov 28 2012

    Awesome! It was exactly what I was looking for. Thank you for this post!

    Reply
  4. Dec 19 2012

    My knowledge of regular expressions is pretty lacking but I was able to adjust the regex to include some words with basic pluralisation.


    var regex = new RegExp(str+'([e\']?s|[^aiou ]s)|'+str, "gi");

    This lets me pick up “Simon’s” and “documents” when the singular word is supplied.

    Reply

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments