skip to content

JavaScript: Search Keyword Highlighting

 Tweet4 Shares2 Tweets

If you've arrived here via a search engine you may have noticed that the keywords you searched for appeared highlighted on the landing page. Here we explain how this is done using JavaScript.

There are two components - firstly identifying the keywords, and then highlighting them on the page using JavaScript. This article covers only the second part - using JavaScript and DHTML to dynamically modify the DOM.

Unfortunately extracting search strings from search engine referrals is now nigh on impossible, but the highlighting code has proven extremely popular in other endeavours.

Working example

Enter some keywords in the box below and click the Apply button. Words in the page content that match the words you've entered will be instantly highlighted in different colours:

Highlight Words

To remove highlighting click the Remove button.

How to implement

First we take the form input (e.g. "search engine keywords") and convert it into a JavaScript regular expression (e.g. "/\b(search|engine|keywords)\b/"). This regular expression will match any of the entered words where they appear in the content area of the page.

In practice what we do is generate this keywords to highlight using the search engine referer string, and also exclude common stop words such as 'is', 'and', 'are', etc, but that's another story.

Here is the code we use to call the highlighting function:

<script type="text/javascript" src="hilitor.js"></script> <script type="text/javascript"> var myHilitor = new Hilitor("content"); myHilitor.apply("highlight words"); </script>

The first line creates an instance of the Hilitor class, specifying that the area to be scanned is the #content element (i.e. inside <div id="content">...</div>) on the page. If we don't supply a valid id then the script will scan document.body, but it's better to limit it to just your content area to avoid highlighting appearing in the page header, menu or footer.

The tag used by default to apply highlighting to matched words is the EM (emphasis) tag. We can change that if we want by passing a second parameter with a different tag name. You can use any tag that renders inline - so not a DIV or similar block element which would break the layout.

Calling the apply method passes the words to be highlighted to the just instantiated object which traverses the DOM inside the selected node looking for text that matches any of the keywords.

When a match is found, the text node in question is split up and a tag with style settings applied to the matching words. Each word is assigned a colour from the array supplied in the script which is then used throughout the document for that word.

To remove the highlighting from the page we simply call (from a link, script or button):

<script type="text/javascript"> myHilitor.remove(); </script>

If the colours used for highlighting look familiar, it's because they were copied from a now defunct version of Google Cache View.

The hilitor.js JavaScript class

The JavaScript code for this class can be downloaded or copied below. The two main methods are apply() and remove() which respectively apply word highlighting on the page and then remove it, restoring the DOM (more or less) to its original state.

In the variables section you may want to change the default tag to use for highlighting (currently EM), the list of tags to skip (SCRIPT and FORM) or the array of colours.

Source code for hilitor.js:

// Original JavaScript code by Chirp Internet: www.chirp.com.au // Please acknowledge use of this code by including this header. function Hilitor(id, tag) { var targetNode = document.getElementById(id) || document.body; var hiliteTag = tag || "EM"; var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM|SPAN)$"); var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"]; var wordColor = []; var colorIdx = 0; var matchRegex = ""; var openLeft = false; var openRight = false; // characters to strip from start and end of the input string var endCharRegex = new RegExp("^[^\\\w]+|[^\\\w]+$", "g"); // characters used to break up the input string into words var breakCharRegex = new RegExp("[^\\\w'-]+", "g"); this.setMatchType = function(type) { switch(type) { case "left": this.openLeft = false; this.openRight = true; break; case "right": this.openLeft = true; this.openRight = false; break; case "open": this.openLeft = this.openRight = true; break; default: this.openLeft = this.openRight = false; } }; this.setRegex = function(input) { input = input.replace(endCharRegex, ""); input = input.replace(breakCharRegex, "|"); input = input.replace(/^\||\|$/g, ""); if(input) { var re = "(" + input + ")"; if(!this.openLeft) re = "\\b" + re; if(!this.openRight) re = re + "\\b"; matchRegex = new RegExp(re, "i"); return true; } return false; }; this.getRegex = function() { var retval = matchRegex.toString(); retval = retval.replace(/(^\/(\\b)?|\(|\)|(\\b)?\/i$)/g, ""); retval = retval.replace(/\|/g, " "); return retval; }; // recursively apply word highlighting this.hiliteWords = function(node) { if(node === undefined || !node) return; if(!matchRegex) return; if(skipTags.test(node.nodeName)) return; if(node.hasChildNodes()) { for(var i=0; i < node.childNodes.length; i++) this.hiliteWords(node.childNodes[i]); } if(node.nodeType == 3) { // NODE_TEXT if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) { if(!wordColor[regs[0].toLowerCase()]) { wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length]; } var match = document.createElement(hiliteTag); match.appendChild(document.createTextNode(regs[0])); match.style.backgroundColor = wordColor[regs[0].toLowerCase()]; match.style.fontStyle = "inherit"; match.style.color = "#000"; var after = node.splitText(regs.index); after.nodeValue = after.nodeValue.substring(regs[0].length); node.parentNode.insertBefore(match, after); } }; }; // remove highlighting this.remove = function() { var arr = document.getElementsByTagName(hiliteTag); while(arr.length && (el = arr[0])) { var parent = el.parentNode; parent.replaceChild(el.firstChild, el); parent.normalize(); } }; // start highlighting at target node this.apply = function(input) { this.remove(); if(input === undefined || !input) return; if(this.setRegex(input)) { this.hiliteWords(targetNode); } }; }

expand code box

The setRegex() and getRegex() methods convert the input string of keywords into a JavaScript regular expression and vice versa. Note in particular the the regular expressions defined at the top of the script - endCharRegex and breakCharRegex.

When an input string is received. the endCharRegex RegExp is used to trim unwanted characters from the start and end. Then breakCharRegex is used to break the input string into separate words. In both cases "\\\w" is just an escaped "\w" (any alphanumeric character including the underscore).

If you want to be able to match words containing non-alphanumericcharacters, such as '@' and '.' for email addresses, you need to exclude them from breakCharRegex.

For example:

var breakCharRegex = new RegExp("[^\\\w'@.-]+", "g"); // expanded to include email addresses

Otherwise and email address will be broken into separate words at every '@' or '.'. The same approach applies if you want to match hashtags '#' as part of a word.

Under the hood

When the apply() method is called it generates a regular expression from the keywords, clears any existing highlighting on the page, and then calls the hiliteWords() method passing a reference to the selected start node. The hiliteWords() function then examines the node to see if it has any child nodes in which case it calls itself recursively once for each child.

When a text node is encountered its contents are tested using the regular expression to see if any of the keywords are present. If there are one or more matches the text node is cut in two at the point where the first match was found. The matched word itself is wrapped in an EM tag that also specifies the background colour. The process then continues.

It's a bit difficult to explain, but essentially what takes place is the following:

DOM before

Container > Text Node e.g. <p>Paragraph with highlighted word.</p>

DOM after

Container > Text Node // original text node, truncated > EM > Text Node // var match > Text Node // var after e.g. <p>Paragraph with <em>highlighted</em> word.</p>

As the script encounters and highlights different keywords the colour used for each is remembered so that the same keyword can be highlighted consistently down the page.

The for loop doesn't mind that there are suddenly three extra nodes. It moves to the next node, which is the EM, skips it and moves to the after Text Node containing the remainder of the node that was originally split in two.

Feel free to adapt and use this code as you see fit. We use it to highlight search engine query keywords after the keywords have been extracted from the HTTP Referer using a server-side script, but that's more than can be explained in a single article.

Using an onload event

Since it's now very unfasionable to call javascript inline, here is an example of how you can apply highlighting to the page using an event listener that only fires after the page has been completely rendered:

<script type="text/javascript" src="hilitor.js"></script> <script type="text/javascript"> var myHilitor; // global variable document.addEventListener("DOMContentLoaded", function(e) { myHilitor = new Hilitor("content"); myHilitor.apply("highlight these words"); }, false); </script>

The function call is identical to the example above, just delayed until the page has finished rendering rather than being executed as soon as the function call is encountered.

We've declared myHilitor as a global variable to enable later calls to remove or update keyword highlighting on the page using the remove() and apply() methods.

Link to turn off highlighting

You might be wondering how we generate the link that appears at the top of the page showing the words that have been highlighted alongside a link to remove the highlighting.

It's a bit of a hack, but the code is as follows:

(function() { var targetNode = document.getElementsByTagName("h1")[0]; // first h1 on page var el = document.createElement("p"); // create new p el.id = "remove_hilite"; el.style.fontSize = "10px"; // styles can better be applied using css el.innerHTML = "<b>Highlighted terms</b>: " + myHilitor.getRegex() + " | <a href=\"#\" onclick=\"myHilitor.remove(); document.getElementById('remove_hilite').style['display'] = 'none'; return false;\">remove</a>"; targetNode.parentNode.insertBefore(el, targetNode.nextSibling); // insert p element after first h1 })();

This inserts a new paragraph (P) following the first H1 heading on the page. The text displays the keywords and also a 'remove' link which calls the remove method. You can see an example here.

These commands can also be bundled into the onload method above, in which case you no longer need to wrap them in an anonymous function. The complete code then is as follows:

<script type="text/javascript" src="hilitor.js"></script> <script type="text/javascript"> var myHilitor; document.addEventListener("DOMContentLoaded", function(e) { // highlight search terms on page myHilitor = new Hilitor("content"); myHilitor.apply("highlight these words"); // display link to remove highlighting var targetNode = document.getElementsByTagName("h1")[0]; var el = document.createElement("p"); el.id = "remove_hilite"; el.style.fontSize = "10px"; el.innerHTML = "<b>Highlighted terms</b>: " + myHilitor.getRegex() + " | <a href=\"#\" onclick=\"myHilitor.remove(); document.getElementById('remove_hilite').style['display'] = 'none'; return false;\">remove</a>"; targetNode.parentNode.insertBefore(el, targetNode.nextSibling); }, false); </script>

Open ended matching

We've just updated the code to allow for open ended keyword matching. So "key" will match the start of "keyword". Here is a simple example:

Highlight keywords as you type:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus felis erat, facilisis vitae est sed, interdum luctus dui. Donec condimentum in neque ac consequat. Donec interdum quis massa molestie consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum malesuada quam quis sapien volutpat placerat. Phasellus varius, enim vel fringilla pellentesque, felis velit mollis lacus, ac scelerisque sem sem sed eros.

This example has been coded to apply only to it's own section of the page, unlike the main example which will highlight text throughout:

<div id="playground"> <form> <p>Highlight keywords as you type: <input id="keywords" size="24"></p> </form> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus felis erat, facilisis vitae est sed, interdum luctus dui. Donec condimentum in neque ac consequat. Donec interdum quis massa molestie consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum malesuada quam quis sapien volutpat placerat. Phasellus varius, enim vel fringilla pellentesque, felis velit mollis lacus, ac scelerisque sem sem sed eros.</p> </div> <script type="text/javascript" src="hilitor.js"></script> <script type="text/javascript"> var myHilitor2; document.addEventListener("DOMContentLoaded", function(e) { myHilitor2 = new Hilitor("playground"); myHilitor2.setMatchType("left"); }, false); document.getElementById("keywords").addEventListener("keyup", function(e) { myHilitor2.apply(this.value); }, false); </script>

This example introduces a new method for the Hilitor class setMatchType(). You can set this to 'left', 'right' or 'open'. Any other value will restore the default behaviour of only matching whole words.

Patch for accented characters

Following a request through our feedback form, here is a simple patch that will find works on the page even if they contain accented characters.

With this patch a search for cafe will also match café and cafè (French), and a search for Koln will highlight Köln as well as Koeln (German).

Internally the regular expression if you search for "Koln cafe" (for example) is changed from (Koln|cafe) to (K([oôö]|oe)ln|c([aàâä]|ae)f[eèéêë]).

Because of poor UTF-8 in JavaScript regular expressions, however, we can't use this technique if the input itself contains accented characters.

> function addAccents(input) > { > retval = input; > retval = retval.replace(/([ao])e/ig, "$1"); > retval = retval.replace(/e/ig, "[eèéêë]"); > retval = retval.replace(/a/ig, "([aàâä]|ae)"); > retval = retval.replace(/i/ig, "[iîï]"); > retval = retval.replace(/o/ig, "([oôö]|oe)"); > retval = retval.replace(/u/ig, "[uùûü]"); > retval = retval.replace(/y/ig, "[yÿ]"); > return retval; > } this.setRegex = function(input) { input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|"); | var re = "(" + addAccents(input) + ")"; if(!this.openLeft) re = "\\b" + re; if(!this.openRight) re = re + "\\b"; matchRegex = new RegExp(re, "i"); };

The code above will cater for most western european languages. To cover a broader range of languages you will need to add extra characters such as ș, ţ, and č (and many more).

For tips on adding UTF-8 support to the search highlighter, see the code from Yanosh Kunsh under Feedback below, or continue reading.

With full(?) UTF-8 support

Integrating the UTF-8 support supplied by Yanosh with the accented character wildcard search option above was challenging, but we seem to have it working now.

Adding support for extra characters beyond those included (àâäèéêëîïôöùûüÿß, ae and oe) is a matter of adding them in two lines in the addAccents method.

Here is the latest version of the code:

// Original JavaScript code by Chirp Internet: www.chirp.com.au // Please acknowledge use of this code by including this header. function Hilitor2(id, tag) { var targetNode = document.getElementById(id) || document.body; var hiliteTag = tag || "EM"; var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$"); var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"]; var wordColor = []; var colorIdx = 0; var matchRegex = ""; var openLeft = false; var openRight = false; this.setMatchType = function(type) { switch(type) { case "left": this.openLeft = false; this.openRight = true; break; case "right": this.openLeft = true; this.openRight = false; break; case "open": this.openLeft = this.openRight = true; break; default: this.openLeft = this.openRight = false; } }; function addAccents(input) { retval = input; retval = retval.replace(/([ao])e/ig, "$1"); retval = retval.replace(/\\u00E[024]/ig, "a"); retval = retval.replace(/\\u00E7/ig, "c"); retval = retval.replace(/\\u00E[89AB]/ig, "e"); retval = retval.replace(/\\u00E[EF]/ig, "i"); retval = retval.replace(/\\u00F[46]/ig, "o"); retval = retval.replace(/\\u00F[9BC]/ig, "u"); retval = retval.replace(/\\u00FF/ig, "y"); retval = retval.replace(/\\u00DF/ig, "s"); retval = retval.replace(/a/ig, "([aàâä]|ae)"); retval = retval.replace(/c/ig, "[cç]"); retval = retval.replace(/e/ig, "[eèéêë]"); retval = retval.replace(/i/ig, "[iîï]"); retval = retval.replace(/o/ig, "([oôö]|oe)"); retval = retval.replace(/u/ig, "[uùûü]"); retval = retval.replace(/y/ig, "[yÿ]"); retval = retval.replace(/s/ig, "(ss|[sß])"); return retval; } this.setRegex = function(input) { input = input.replace(/\\([^u]|$)/g, "$1"); input = input.replace(/[^\w\\\s']+/g, "").replace(/\s+/g, "|"); input = input.replace(/^\||\|$/g, ""); input = addAccents(input); if(input) { var re = "(" + input + ")"; if(!this.openLeft) re = "(?:^|[\\b\\s])" + re; if(!this.openRight) re = re + "(?:[\\b\\s]|$)"; matchRegex = new RegExp(re, "i"); return true; } return false; }; this.getRegex = function() { var retval = matchRegex.toString(); retval = retval.replace(/(^\/|\(\?:[^\)]+\)|\/i$)/g, ""); return retval; }; // recursively apply word highlighting this.hiliteWords = function(node) { if(node === undefined || !node) return; if(!matchRegex) return; if(skipTags.test(node.nodeName)) return; if(node.hasChildNodes()) { for(var i=0; i < node.childNodes.length; i++) this.hiliteWords(node.childNodes[i]); } if(node.nodeType == 3) { // NODE_TEXT if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) { if(!wordColor[regs[1].toLowerCase()]) { wordColor[regs[1].toLowerCase()] = colors[colorIdx++ % colors.length]; } var match = document.createElement(hiliteTag); match.appendChild(document.createTextNode(regs[1])); match.style.backgroundColor = wordColor[regs[1].toLowerCase()]; match.style.fontStyle = "inherit"; match.style.color = "#000"; var after; if(regs[0].match(/^\s/)) { // in case of leading whitespace after = node.splitText(regs.index + 1); } else { after = node.splitText(regs.index); } after.nodeValue = after.nodeValue.substring(regs[1].length); node.parentNode.insertBefore(match, after); } }; }; // remove highlighting this.remove = function() { var arr = document.getElementsByTagName(hiliteTag); while(arr.length && (el = arr[0])) { var parent = el.parentNode; parent.replaceChild(el.firstChild, el); parent.normalize(); } }; // start highlighting at target node this.apply = function(input) { this.remove(); if(input === undefined || !(input = input.replace(/(^\s+|\s+$)/g, ""))) return; input = convertCharStr2jEsc(input); if(this.setRegex(input)) { this.hiliteWords(targetNode); } }; // added by Yanosh Kunsh to include utf-8 string comparison function dec2hex4(textString) { var hexequiv = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"); return hexequiv[(textString >> 12) & 0xF] + hexequiv[(textString >> 8) & 0xF] + hexequiv[(textString >> 4) & 0xF] + hexequiv[textString & 0xF]; } function convertCharStr2jEsc(str, cstyle) { // Converts a string of characters to JavaScript escapes // str: sequence of Unicode characters var highsurrogate = 0; var suppCP; var pad; var n = 0; var outputString = ''; for(var i=0; i < str.length; i++) { var cc = str.charCodeAt(i); if(cc < 0 || cc > 0xFFFF) { outputString += '!Error in convertCharStr2UTF16: unexpected charCodeAt result, cc=' + cc + '!'; } if(highsurrogate != 0) { // this is a supp char, and cc contains the low surrogate if(0xDC00 <= cc && cc <= 0xDFFF) { suppCP = 0x10000 + ((highsurrogate - 0xD800) << 10) + (cc - 0xDC00); if(cstyle) { pad = suppCP.toString(16); while(pad.length < 8) { pad = '0' + pad; } outputString += '\\U' + pad; } else { suppCP -= 0x10000; outputString += '\\u' + dec2hex4(0xD800 | (suppCP >> 10)) + '\\u' + dec2hex4(0xDC00 | (suppCP & 0x3FF)); } highsurrogate = 0; continue; } else { outputString += 'Error in convertCharStr2UTF16: low surrogate expected, cc=' + cc + '!'; highsurrogate = 0; } } if(0xD800 <= cc && cc <= 0xDBFF) { // start of supplementary character highsurrogate = cc; } else { // this is a BMP character switch(cc) { case 0: outputString += '\\0'; break; case 8: outputString += '\\b'; break; case 9: outputString += '\\t'; break; case 10: outputString += '\\n'; break; case 13: outputString += '\\r'; break; case 11: outputString += '\\v'; break; case 12: outputString += '\\f'; break; case 34: outputString += '\\\"'; break; case 39: outputString += '\\\''; break; case 92: outputString += '\\\\'; break; default: if(cc > 0x1f && cc < 0x7F) { outputString += String.fromCharCode(cc); } else { pad = cc.toString(16).toUpperCase(); while(pad.length < 4) { pad = '0' + pad; } outputString += '\\u' + pad; } } } } return outputString; } }

expand code box

The trick is that search strings now arrive in the setRegex method with UNICODE encoding (\u00XX). We need to detect any unicode characters and convert them back to the closest 'normal' character. Then we modify the regex to turn those characters into wildcards as before.

A major problem is that \b is unreliable when trying to match words that contain UTF-8 characters as they are treated as non-word characters and thus create their own word breaks.

Here's some sample code you can try out on your own website, or experiment with in our demo:

<form> <p>Highlight keywords as you type: <input id="keywords" size="24" placeholder="start typing"></p> </form> <div id="playground" style="margin: 1em 0; padding: 0 1em; border: 1px solid #ccc;"> <p>Round and round the rugged rock the ragged rascal ran.</p> <p>Tu t'entêtes à tout tenter, tu t'uses et tu te tues à tant t'entêter.</p> <p>Wir Wiener Wäscheweiber wäschten weisse Wäsche, Wenn wir wüssten, wo warmes, weiches Wasser wär.</p> </div> <script type="text/javascript" src="/hilitor-utf8.js"></script> <script type="text/javascript"> var myHilitor; document.addEventListener("DOMContentLoaded", function(e) { myHilitor = new Hilitor2("playground"); myHilitor.setMatchType("left"); }, false); document.getElementById("keywords").addEventListener("keyup", function(e) { myHilitor.apply(this.value); }, false); </script>

Please note that any unmatched UNICODE characters containing 'A' or 'E' in their hex value will likely break the search. Certain punctuation may also cause problems.

Identifying search keywords

To identify search keywords you need to parse the browsers HTTP_REFERER string. Depending on which search engine was used the keywords will usually appear after q= or query=, but there are also other possibilities.

Since 2011, unfortunately, Google does not pass this information for logged in users making it impossible for us to identify their search keywords.

References

< JavaScript

Send a message to The Art of Web:


used only for us to reply, and to display your gravatar.

CAPTCHA

<- copy the digits from the image into this box

press <Esc> or click outside this box to close

User Comments

Most recent 20 of 61 comments:

Post your comment or question

9 February, 2017

Good work, I face one problem i am not able to search for Supscript or superscript text in the HTML. Any suggestions please

8 February, 2017

Nothing to congratulate you on your work. I have the same problem, I use it to search for phrases.

There is one problem that I have and that is if a searched word has a punctuation mark (period,comma colon, etc) immediately after it, It doen't find the word to highlight it.

Any suggestions?

You will need to modify "breakCharRegex" as described above. In your case, to include spaces and other punctuation in the [^...].

12 January, 2017

Thank you for your excellent script. I was wondering what is the regular expression for a case-sensitive match.

In this line:

matchRegex = new RegExp(re, "i");

It's the "i" that makes it case-insensitive.

See the documentation.

21 December, 2016

Would it be possible to count the words you highlight, based on the html elements?

For example:
keyword found in <h1>: 1
keyword found in <h2>: 3
keyword found in <p>: 4
keyword found in <a>: 1

With that you can create a simple keyword density tool to create better seo content

I would first run the Hilitor, and then a separate script to identify all the highlighted words and step up to their containing elements.

22 October, 2016

Hi im new to javascript so maybe im wrong but it seems it doesnt match words starting with #foo #bar or any word starting with # which is used in hashtags.

2 September, 2016

Could the code be easily modified to change another style value of the text? For example, adding a strikethrough. Even better, changing text colour and adding a strikethrough, or bold and underline. Etc.

31 August, 2016

It doesn't work with languages other than English??

29 August, 2016

I found this tool pretty helpful.
Thanks a lot for coming up with this.
How can I highlight opening html tags using this.

if I give "<div" , it only highlights and matches all occurrences of div.

25 August, 2016

Hi! I finally got it to work in my JSF web application and like it very much.
There is one problem that I have and that is if a searched word has a punctuation mark (period,comma colon, etc) immediately after it, It doen't find the word to highlight it.
Any suggestions?

25 July, 2016

I've put it alongside the search box, e.g. in the html, add a span, id='matches':

<span style='background-color:#ff6;'>Show:<input id='keywords'size='6'><span id='matches'></span></span>

Then modify hilitor.js:
Declare count as global:

function Hilitor(id, tag)
{
var count = 0;
....
}

Then do the counting:
In hiliteWords:
.....
match.style.color = "#000";
count++;

and in:
this.apply = function(input)
{
count = 0;
this.remove();
if(input === undefined || !input) return;
if(this.setRegex(input)) {
this.hiliteWords(targetNode);
}
document.getElementById('matches').innerHTML = count;
};

---------------------------------------
Kudos to the guy who wrote this stuff!!

29 June, 2016

Follow-up to the question I posted yesterday:
I find that this works with Opera and with IE...
var targetNode = document.getElementById(nameFrame).contentDocument.body;

Does anybody know if there is a convenient place to insert a counter, to report the number of instances highlighted?

27 June, 2016

Thank you for this helpful code. I found that to function with Internet Explorer 11, the targetNode must be set as:
targetNode = window.frames[nameIframe].document;
But I have been unable to get it to work with Opera 37/38.
Admittedly, my javascript skills are not much beyond "newbie".
Suggestions greatly appreciated!

17 May, 2016

Hi, thank you for this tool! I am wondering now how you would go about highlighting a multi-word phrase? For example, a search for "the cat" only highlighting that phrase and not every "the" and "cat" word.

The answer to this and the last 10 identical questions is to modify the regular expression which breaks up the input string:

....replace(/[^\w'-]+/g, "|");

Any characters which you DON'T want to represent a break need to be added to the highlighted section.

12 May, 2016

Hi, this is an excellent tool. Just wondering if the targetNode can be used to search and highlight within classes instead of an just and Id?

Yes, but it would require some changes. Instead of "this.hiliteWords(targetNode);", where targetNode is identified by id, you would need to call hiliteWords for each instance of the class.

It might be simpler to wrap everything in an element with an id and then set up rules to skip parts of the page you don't want highlighted.

11 May, 2016

Hi, this looks really useful.

I copied and pasted into my header the code in 7 above. Is this all that is required? I can see the text, but the search does not seem to work.

Am I must missing something?

Thank you

You need to copy the JavaScript file as well and make sure it's properly referenced by the SCRIPT tag. Check your browser consoler error messages.

11 May, 2016

Hi, good job! I have only one problem over my head. How to mark phrase with minus sign inside like "semi-full". regex is not for me

In the first line of "this.setRegex = function(input)" try removing the "-" from the regular expression.

12 April, 2016

Hi, thank you a lot for your work, very inspiring! I am still struggling with one thing: how do I add highlighting dynamically to a contenteditable textarea, as soon as it matches a pre-defined regex?

For example, I am looking for ways to get the same functionality as www.hemingwayapp.com/

Do you have an idea? Any hint would be much appreciated.

Thank you and all the best,

Jan

The Hemingway app uses two layers. The background layer includes the highlighting - very similar to our script - while only the foreground layer is 'ContentEditable'. The two layers are kept synchronised with JavaScript.

6 April, 2016

Thank you for all your hard work! This is a great tool I've implemented for a happy client. I'm using www.the-art-of-web.com/search-highlight-demo.html example and have one question. I would like to add a buffer of 50px from top of browser when Prev/Next are clicked. However, the container.scrollTop = 100; in the code below isn't being recognized. Is there an alternative to this method?

navCancel.addEventListener("click", function(e) {
searchEl.value = "";
searchTrigger();
regexEl.innerHTML = counterEl.innerHTML = "";
container.scrollTop = 100;
e.preventDefault();
}, false);

Thank you in advance!

31 March, 2016

I have just enter "'" in your demo and and my browser get stuck. can u help me to resolve this..

There was/is a bug in the old "hilltor.js". This has been fixed now in "hilitor-utf8.js" which also adds other features.

31 March, 2016

How to highlight special symbol?
i have list of emails in my page and
i am trying to search specific email address and highlight but it search only text part not search @ in email address

In the code we use "\w" which represents a-z, A-Z, 0-9 and the underscore character. To be able to match email addresses you would need to use "[\w@.]" or similar (untested).

top