mark.js is a text highlighter written in JavaScript. It can be used to dynamically mark search terms or custom regular expressions and offers you built-in options like diacritics support, separate word search, custom synonyms, iframes support, custom filters, accuracy definition, custom element, custom class name and more.
1. Getting to Know mark.js
2. Examples
Have a look at the Configurator to see mark.js in action.
Other examples:
3. Getting Started
3.1 Download
You can either download the package using npm by running:
$ npm install mark.js --save-dev
Or using Bower:
$ bower install mark.js --save-dev
Or download the package manually.
3.2 CDN
If you prefer using a CDN instead of downloading, mark.js is available on:
3.3 Integration
mark.js ships with a few files – when using Bower they're located in ./dist/
:
- Files for usage with pure JavaScript:
mark.js
- Uncompressed ES5mark.min.js
- Compressed ES5 (recommended)mark.es6.js
- Uncompressed ES6mark.es6.min.js
- Compressed ES6
- Files for usage as jQuery plugin:
jquery.mark.js
- Uncompressed ES5jquery.mark.min.js
- Compressed ES5 (recommended)jquery.mark.es6.js
- Uncompressed ES6jquery.mark.es6.min.js
- Compressed ES6
If you don't know what ES5 or ES6 (ECMAScript) is, then simply go ahead with a ES5 file as it might be in the JavaScript syntax you are using in your project.
After you have chosen the appropriate file, you need to embed it, e.g. as follows:
<script src="vendor/mark.js/dist/mark.min.js"></script>
Alternatively you can load it with AMD (RequireJS) or CommonJS.
Note: mark.js requires UTF-8 encoding. If your server or environment serves files with ASCII, you can workaround this behavior by using the charset
attribute on above named <script>
tag, e.g.:
<script src="vendor/mark.js/dist/mark.min.js" charset="UTF-8"></script>
3.4 Styling
mark.js will wrap matches with a specified element and optionally with an assigned class. When not changing the default element mark
, browsers will ensure that it looks highlighted by default. However, you may want to customize the style of it. This can be done using e.g. the following CSS:
mark{
background: orange;
color: black;
}
If you've customized the default element or class, make sure to modify the selector.
3.5 Plugins
The following plugins are available to integrate mark.js into other components:
4. API
4.1 General
JavaScript
Each API method below can be called on an instance object. To initialize a new instance you have to use:
var instance = new Mark(context);
The variable context
defines where you want mark.js to search for matches. You can pass a single element (e.g. the return value of document.getElementById(...)
or document.querySelector(...)
), an array containing multiple single elements, or a NodeList (e.g. document.querySelectorAll(...)
). Alternatively you can pass a string selector.
If for example you'd like to highlight matches in a div with a class context
then you'd have to use:
var instance = new Mark(document.querySelector("div.context"));
// or
var instance = new Mark("div.context");
jQuery
Each API method below can be called on every jQuery element, e.g. $("div.test")
.
4.2 mark()
A method to highlight custom search terms.
Syntax
JavaScript:
var context = document.querySelector(".context");
var instance = new Mark(context);
instance.mark(keyword [, options]);
jQuery:
$(".context").mark(keyword [, options]);
Note: Even if this is a chaining method and therefore allows you to call further methods on the returning object, it's recommended to always use the done
callback as mark.js works asynchronous.
Parameters
keyword
Type: string
or array
of string
The keyword to be marked. Can also be an array with multiple keywords. Note that keywords will be escaped. If you need to mark unescaped keywords (e.g. containing a pattern), have a look at the markRegExp()
method below.
options
Type: object
Optional options:
Option | Type | Default | Description |
---|---|---|---|
element | string | "mark" | HTML element to wrap matches, e.g. span |
className | string | "" | A class name that will be appended to element |
exclude | array | [ ] | An array with exclusion selectors. Matches inside these elements will be ignored. Example: "filter": ["h1", ".ignore"] |
separateWordSearch | boolean | true | Whether to search for each word separated by a blank instead of the complete term |
accuracy | string or object | "partially" | Either one of the following string values:
|
diacritics | boolean | true | If diacritic characters should be matched. For example "piękny" would also match "piekny" and "doner" would also match "döner" |
synonyms | object | { } | An object with synonyms. The key will be a synonym for the value and the value for the key. Example: "synonyms": {"one": "1"} will add the synonym "1" for "one" and vice versa |
iframes | boolean | false | Whether to search also inside iframes. If you don't have permissions to some iframes (e.g. because they have a different origin) they will be silently skipped. If you don't want to search inside specific iframes (e.g. facebook share), you can pass an exclude selector that matches these iframes |
iframesTimeout | number | 5000 | The maximum ms to wait for a load event before skipping an iframe. Especially important when there's no internet connection or a browser "offline" mode is enabled and an iframe has an online src – then the load event is never fired |
acrossElements | boolean | false | Whether to search for matches across elements |
caseSensitive | boolean | false | Whether to search case sensitive |
ignoreJoiners | boolean | false | Whether to also find matches that contain soft hyphen, zero width space, zero width non-joiner and zero width joiner. They're used to indicate a point for a line break where there isn't enough space to show the full word |
ignorePunctuation | array | [ ] | An array of punctuation mark strings. These punctuation marks can be between any characters, e.g. setting this option to ["'"] would match "Worlds", "World's" and "Wo'rlds". One or more apostrophes between the letters would still produce a match (e.g. "W'o''r'l'd's"). A typical setting for this option could be as follows: ":;.,-–—‒_(){}[]!'\"+=".split("") |
wildcards | string | "disabled" | Set to any of the following string values:
|
each | function | A callback for each marked element. Receives the marked DOM element as a parameter | |
filter | function | A callback to filter or limit matches. It will be called for each match and receives the following parameters:
| |
noMatch | function | A callback function that will be called when there are no matches. Receives the not found term as a parameter | |
done | function | A callback function after all marks are done. Receives the total number of marks as a parameter | |
debug | boolean | false | Set this option to true if you want to log messages |
log | object | console | Log messages to a specific object (only if debug is true) |
Examples
var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.mark("test"); // will mark the keyword "test"
jQuery:$(".context").mark("test"); // will mark the keyword "test", requires an element with class "context" to exist
var options = {
"element": "mark",
"className": "",
"exclude": [],
"separateWordSearch": true,
"accuracy": "partially",
"diacritics": true,
"synonyms": {},
"iframes": false,
"iframesTimeout": 5000,
"acrossElements": false,
"caseSensitive": false,
"ignoreJoiners": false,
"ignorePunctuation": [],
"wildcards": "disabled",
"each": function(node){
// node is the marked DOM element
},
"filter": function(textNode, foundTerm, totalCounter, counter){
// textNode is the text node which contains the found term
// foundTerm is the found search term
// totalCounter is a counter indicating the total number of all marks
// at the time of the function call
// counter is a counter indicating the number of marks for the found term
return true; // must return either true or false
},
"noMatch": function(term){
// term is the not found term
},
"done": function(counter){
// counter is a counter indicating the total number of all marks
},
"debug": false,
"log": window.console
};
JavaScript:var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.mark("test", options); // will mark the keyword "test"
jQuery:$(".context").mark("test", options); // will mark the keyword "test", requires an element with class "context" to exist
4.3 markRegExp()
A method to highlight custom regular expressions.
Syntax
JavaScript:
var context = document.querySelector(".context");
var instance = new Mark(context);
instance.markRegExp(regexp [, options]);
jQuery:
$(".context").markRegExp(regexp [, options]);
Note: Even if this is a chaining method and therefore allows you to call further methods on the returning object, it's recommended to always use the done
callback as mark.js works asynchronous.
Parameters
regexp
Type: RegExp
The regular expression to be marked. Example: /Lor[^]?m/gmi
. Note that groups will be ignored and mark.js will always find all matches, regardless of the g
flag.
options
Type: object
Optional options:
Option | Type | Default | Description |
---|---|---|---|
element | string | "mark" | HTML element to wrap matches, e.g. span |
className | string | "" | A class name that will be appended to element |
exclude | array | [ ] | An array with exclusion selectors. Matches inside these elements will be ignored. Example: "filter": ["h1", ".ignore"] |
iframes | boolean | false | Whether to search also inside iframes. If you don't have permissions to some iframes (e.g. because they have a different origin) they will be silently skipped. If you don't want to search inside specific iframes (e.g. facebook share), you can pass an exclude selector that matches these iframes |
iframesTimeout | number | 5000 | The maximum ms to wait for a load event before skipping an iframe. Especially important when there's no internet connection or a browser "offline" mode is enabled and an iframe has an online src – then the load event is never fired |
acrossElements | boolean | false | Whether to search for matches across elements |
ignoreGroups | number | 0 | Indicates the number of matching groups to ignore in the replacement. Can be used e.g. to implement non-capturing lookbehind groups. Note that when the value is > 0 (when groups should be ignored), it expects a total amount of groups in the RegExp of ignoreGroups + 1 |
each | function | A callback for each marked element. Receives the marked DOM element as a parameter | |
filter | function | A callback to filter or limit matches. It will be called for each match and receives the following parameters:
| |
noMatch | function | A callback function that will be called when there are no matches. Receives the not found term as a parameter | |
done | function | A callback function after all marks are done. Receives the total number of marks as a parameter | |
debug | boolean | false | Set this option to true if you want to log messages |
log | object | console | Log messages to a specific object (only if debug is true) |
Examples
var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.markRegExp(/lorem/gmi);
jQuery:$(".context").markRegExp(/lorem/gmi); // requires an element with class "context" to exist
var options = {
"element": "mark",
"className": "",
"exclude": [],
"iframes": false,
"iframesTimeout": 5000,
"acrossElements": false,
"ignoreGroups": 0,
"each": function(node){
// node is the marked DOM element
},
"filter": function(textNode, foundTerm, totalCounter){
// textNode is the text node which contains the found term
// foundTerm is the found search term
// totalCounter is a counter indicating the total number of all marks
// at the time of the function call
return true; // must return either true or false
},
"noMatch": function(term){
// term is the not found term
},
"done": function(counter){
// counter is a counter indicating the total number of all marks
},
"debug": false,
"log": window.console
};
JavaScript:var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.markRegExp(/test/gmi, options);
jQuery:$(".context").markRegExp(/test/gmi, options); // requires an element with class "context" to exist
4.4 markRanges()
A method to mark ranges with a start position and length. They will be applied to text nodes in the specified context.
Syntax
JavaScript:
var context = document.querySelector(".context");
var instance = new Mark(context);
instance.markRanges(ranges [, options]);
jQuery:
$(".context").markRanges(ranges [, options]);
Note: Even if this is a chaining method and therefore allows you to call further methods on the returning object, it's recommended to always use the done
callback as mark.js works asynchronous.
Parameters
ranges
Type: array
An array of objects with a start
and length
property. Note that the start positions must be specified including whitespace characters.
options
Type: object
Optional options:
Option | Type | Default | Description |
---|---|---|---|
element | string | "mark" | HTML element to wrap matches, e.g. span |
className | string | "" | A class name that will be appended to element |
exclude | array | [ ] | An array with exclusion selectors. Matches inside these elements will be ignored. Example: "filter": ["h1", ".ignore"] |
iframes | boolean | false | Whether to search also inside iframes. If you don't have permissions to some iframes (e.g. because they have a [different origin][SOP]) they will be silently skipped. If you don't want to search inside specific iframes (e.g. facebook share), you can pass an exclude selector that matches these iframes |
iframesTimeout | number | 5000 | The maximum ms to wait for a load event before skipping an iframe. Especially important when there's no internet connection or a browser "offline" mode is enabled and an iframe has an online src – then the load event is never fired |
each | function | A callback for each marked element. Receives the marked DOM element and the corresponding range as a parameter | |
filter | function | A callback to filter or limit matches. It will be called for each match and receives the following parameters:
| |
noMatch | function | A callback function that will be called when there are no matches. Receives the not found range as a parameter | |
done | function | A callback function after all marks are done. Receives the total number of marks as a parameter | |
debug | boolean | false | Set this option to true if you want to log messages |
log | object | console | Log messages to a specific object (only if debug is true) |
Examples
var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.markRanges([{
start: 15,
length: 5
}, {
start: 25:
length: 8
}]); // marks matches with ranges 15-20 and 25-33
jQuery:$(".context").markRanges([{
start: 15,
length: 5
}, {
start: 25:
length: 8
}]); // marks matches with ranges 15-20 and 25-33
var options = {
"element": "mark",
"className": "",
"exclude": [],
"iframes": false,
"iframesTimeout": 5000,
"each": function(node, range){
// node is the marked DOM element
// range is the corresponding range
},
"filter": function(textNode, range, term, counter){
// textNode is the text node which contains the found term
// range is the found range
// term is the extracted term from the matching range
// counter is a counter indicating the number of marks for the found term
return true; // must return either true or false
},
"noMatch": function(range){
// the not found range
},
"done": function(counter){
// counter is a counter indicating the total number of all marks
},
"debug": false,
"log": window.console
};
JavaScript:var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.markRanges([{
start: 15,
length: 5
}, {
start: 25:
length: 8
}], options);
jQuery:$(".context").markRanges([{
start: 15,
length: 5
}, {
start: 25:
length: 8
}], options);
4.5 unmark()
A method to remove highlights created by mark.js.
Syntax
JavaScript:
var context = document.querySelector(".context");
var instance = new Mark(context);
instance.unmark(options);
jQuery:
$(".context").unmark(options);
Note: Even if this is a chaining method and therefore allows you to call further methods on the returning object, it's recommended to always use the done
callback as mark.js works asynchronous.
Parameters
options
Type: object
Optional options:
Option | Type | Default | Description |
---|---|---|---|
element | string | "" | Will remove only marked elements with this specific element |
className | string | "" | Will remove only marked elements with this specific class name |
exclude | array | [ ] | An array with exclusion selectors. These elements will be ignored. Example: "filter": ["h1", ".ignore"] |
iframes | boolean | false | Whether to search also inside iframes. If you don't have permissions to some iframes (e.g. because they have a different origin) they will be silently skipped. If you don't want to search inside specific iframes (e.g. facebook share), you can pass an exclude selector that matches these iframes |
iframesTimeout | number | 5000 | The maximum ms to wait for a load event before skipping an iframe. Especially important when there's no internet connection or a browser "offline" mode is enabled and an iframe has an online src – then the load event is never fired |
done | function | A callback function after all marked elements were removed | |
debug | boolean | false | Set this option to true if you want to log messages |
log | object | console | Log messages to a specific object (only if debug is true) |
var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.unmark();
jQuery:$(".context").unmark(); // requires an element with class "context" to exist
var options = {
"element": "",
"className": "",
"exclude": [],
"iframes": false,
"iframesTimeout": 5000,
"done": function(){},
"debug": false,
"log": window.console
};
JavaScript:var context = document.querySelector(".context"); // requires an element with class "context" to exist
var instance = new Mark(context);
instance.unmark(options);
jQuery:$(".context").unmark(options); // requires an element with class "context" to exist
5. Compatibility
mark.js works in all modern browsers.
It is intensively tested in:
- Firefox (30+)
- Chrome (30+)
- Safari (7+)
- Edge (13+)
- IE (9+)
6. Tutorials
Below is an explanation of options that might not be self explanatory.
6.1 Element and Class Name
You may ask yourself how mark.js works generally or how to wrap matches with a custom element and class name. Take this simple DOM fragment as an example:
<div class="context">
<p>My text content</p>
</div>
When you're calling mark.js on div.context
– whether by mark()
or by markRegExp()
– it will find matches and wraps them with a defined element (tag name) and optionally with a class assigned to that element. So assuming we'd like to highlight "text" with a span
tag and a class highlight
assigned, then this could be done e.g. as follows:
With JavaScript:
var instance = new Mark(document.querySelector("div.context"));
instance.mark("text", {
"element": "span",
"className": "highlight"
});
With jQuery:
$("div.context").mark("text", {
"element": "span",
"className": "highlight"
});
The generated DOM fragment would then look like:
<div class="context">
<p>My <span class="highlight">text</span> content</p>
</div>
6.2 Exclude
When having a context that contains multiple elements and you need to ignore matches in some of them, the option exclude
may be worth gold. You can pass an array of exclusion selectors and matches within these selectors will be ignored from highlighting.
Assuming you have the following DOM fragment:
<div class="context">
<article>
<header>
<h1>Article Text Headline</h1>
<div class="byline">
<address class="author">
By <a rel="author" href="demo/link">John Doe</a>
</address>
on
<time pubdate datetime="2016-30-05" title="May 30th, 2016">5/30/16</time>
</div>
</header>
<div class="article-content">
Article text content
</div>
</article>
</div>
And you want to highlight all "text" matches, but only in the actual article content. Then you can either initialize mark.js directly on the div
containing the article's content (in this case div.article-content
), or specify a exclude
array matching the elements that should be ignored, e.g. as follows:
With JavaScript:
var instance = new Mark(document.querySelector("div.context"));
instance.mark("text", {
"exclude": [
"h1",
".byline *"
]
});
With jQuery:
$("div.context").mark("text", {
"exclude": [
"h1",
".byline *"
]
});
This would ignore matches inside <h1>
elements and those having a class byline
assigned.
6.3 Separate Word Search
Constraint: Only available in the mark()
method
Regardless of whether you are highlighting an array of search terms or just a single one, it makes sense to search for each word separately. Assuming you have a search term "lorem ipsum dolor", then this option will make mark.js search for "lorem", "ipsum" and "dolor" separately.
6.4 Accuracy
Constraint: Only available in the mark()
method
As the name suggests, the accuracy
option allows you to specify the accuracy of mark.js.
To demonstrate this, we're taking the following DOM fragment as a basis for all examples below:
<div class="context">
<p>highlight high</p>
</div>
By default, the provided search term will be highlighted, regardless if it's a substring of a word or a own standing one. This option value is called partially
. When searching for "high" inside above named DOM fragment, the following will be generated:
<div class="context">
<p><mark>high</mark>light <mark>high</mark></p>
</div>
However, there might be cases where you want to ignore partial matches and only highlight matches for entire words with a word boundary (see what is a word boundary). Then the option value exactly
is what you're looking for. When searching for "high", only whole words "high" will be highlighted:
<div class="context">
<p>highlight <mark>high</mark></p>
</div>
Last but not least, the option value complementary
will complement matches. When searching for "high" inside above named DOM fragment, the following will be generated:
<div class="context">
<p><mark>highlight</mark> <mark>high</mark></p>
</div>
The option value complementary
highlights the search term and all surrounding characters until a blank or the start/end of the search context occurs. The option value exactly
highlights only whole words that match the specified search term separated by a blank or the start/end of the search context.
But there might be cases where you might want to use accuracy exactly
, but highlight also words that are followed by punctuation marks e.g. a ,
or a .
. Or when using accuracy complementary
you might not want to highlight a ,
or a .
following a word. In these cases you can pass an array of custom limiters, e.g. as follows:
var options = {
"accuracy": {
"value": "exactly",
"limiters": [",", "."]
}
};
This will highlight words that are preceded and followed by a blank, the end/start of the search context (default), .
or ,
(custom).
6.5 Diacritics
The option diacritics
will map normal and diacritic characters. Assuming you would like to highlight either "piekny" or "piękny" in the following DOM fragment:
<div class="context">
<p>My piekny or piękny text</p>
</div>
Then both "piękny" and "piekny" will be highlighted.
6.6 Synonyms
There might be cases where highlighting synonyms for words is helpful for users. Take "one" and "1" as an example. When a user searches for "one last todo", he might expect to highlight also "1 last todo". In German languages, you could also map umlauts, e.g. "ü" with "ue" or "ö" with "oe".
The following example will add the synonym "1" for "one", "2" for "two" and vice versa:
var options = {
"synonyms": {
"one": "1",
"two": "2"
}
};
6.7 Filter
The callback option filter
can be used to filter highlights yourself. For example you could use it to limit highlights for specific words or generally to a specific amount. Or you could check if the match is inside a complex HTML construct which can not be expressed in a exclusion selector. Just be creative!
Here is an example to limit matches for a word "the" to a maximum of 10:
var options = {
"filter": function(node, term, totalCounter, counter){
if(term === "the" && counter >= 10){
return false;
} else {
return true;
}
}
};
6.8 Across Elements
In some cases you need to highlight matches even if they occur in multiple elements. Let's say the following is the context where you'd like to highlight the search term "Highlight Text":
<div class="context">
<b>Highlight</b>
Text
</div>
Then "Highlight" and "Text" are only highlighted when you haven't disabled separateWordSearch
, as this will cause a separated search for words.
If you have disabled separateWordSearch
nothing will be highlighted. This appears due to the fact that the two words included in the search term aren't located within the same text node. As you can see above "Highlight" is wrapped in a separate <b>
element.
To make sure matches will be found across multiple elements – in this example across <b>
– you'll have to enable the acrossElements
option with the value true
.
This option acrossElements
will find matches even if the matching search term is located across multiple HTML elements or even iframes (if enabled).
7. Contribute
If you are having any questions, would like to submit a feature request or found a bug, please don't hesitate to open an issue on GitHub!
Before submitting pull requests or creating issues, please read the guidelines for contributing.