Sometimes it's good to re-invent the wheel (or at least part of it).
highliter was born out of my attempt to understand the Range API.
I took a lot of inspiration from alienzhou's web-highlighter library, and large potions of the code is directly modelled after his code. However, there are some differences in
the underlying implementation (look at getHighlightRanges()
in src/util/dom.js
for example) which (the author contends) has resulted in a major improvement in readability in most parts of the code. The main differences between highliter and web-highlighter are:
- it uses
TreeWalker
to retrieve the list of text nodes which overlap the selected range, resulting in much simpler code - ...uses Range API's
surroundContents()
method instead of the more verbose method of creating documentFragments - ...fixes a minor implementation error in web-highlighter in which an incorrect range is restored if the absolute offset of the saved range (relative to its parent element) is 0 (see the comment here for details)
- ...supports the creation and deletion of overlapping highlights in a fundamentally different (and much simpler) way than web-highlighter. The key insight here is that instead of partitioning partially overlapped highlights into separate highlights, we can allow nested highlights by storing the highlighted range relative to the nearest parent element which is not a highlight element. The absToRelativeOffset() method then handles the conversion to locate the correct startContainer and endContainer for the range the library is trying to restore
- ...but offers less customizability compared to web-highlighter
The library should be thought of as an experimental work, and as such has no test coverage. It is not recommended for use in a production environment.
npm i highliter
import Highliter from 'highliter';
const highliter = new Highliter();
The highlighting functionality is automatically enabled by default upon importing. To prevent this, immediately call .pause()
to temporarily disable it until further action, like so:
highliter.pause();
Check out the demo page here, or look inside the demo
folder for the source code.
To run the demo locally, clone the repo:
git clone https://github.com/sarckk/highliter.git
Change into the directory and install the dependencies:
npm i
Then run the following npm script:
npm start
...and visit http://localhost:8080/ to play around with the demo.
Pass in the options when initialising a new instance of the Highliter
class like so:
const highliter = new Highliter({
highlightColor: '#42f575',
hoverColor: '#f5d142',
customTagName: 'custom-element',
exclude: ['li', 'div']
});
All options:
name | type | description | required | default |
---|---|---|---|---|
highlightColor | string |
RGB/RGBA/HEX string of the initial highlight color | No | #FAFF60 |
hoverColor | string |
RGB/RGBA/HEX string of the initial highlight color | No | #F1F73B |
customTagName | string |
Kebab-case (dash in the name) string representing the name of the custom html element which will wrap around each text node in the highlight rangemore info | No | highlight-snippet |
exclude | string or array<string> |
A single string or an array of strings of the tag name of elements whose child text nodes will be excluded during highlights | No | ['SCRIPT','STYLE','NO-SCRIPT'] |
more info: Any names with dash-separated strings will do, except for the following reserved names:
annotation-xmli
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
See here for the full discussion. ↩
An object containing the property range
which is the the javascript Range object representing the currently selected range after clean-up, and isSelectionBackwards
which is the boolean value representing whether or not the selection was made in reverse direction (right to left).
Currently selected highlight color
Currently selected hover color (color of snippets when user hovers over it)
Object containing constants representing the names of events triggered by the Highliter
instance. Can be thought of as an enum. There are 10 possible events which the user can listen to, and some of the events will pass in arguments to the callback function. If the callback receives an argument, it will be an object with properties described under the table column argument property
under each event:
Events.HOVER
triggers when mouse enters a highlightargument property description type highlightID
Value of the highlightId
custom data attribute of the highlight element which the mouse has enteredstring( uuid
)Events.HOVER_OUT
triggers when mouse exits a highlightargument property description type highlightID
Value of the highlightId
custom data attribute of the highlight element which the mouse has leftstring( uuid
)Events.CLICKED
triggers when mouse clicks a highlightargument property description type highlightID
Value of the highlightId
custom data attribute of the highlight element which has been clickedstring( uuid
)Events.CLICKED_OUT
triggers when mouse clicks outside of the highlight which is in a clicked stateargument property description type highlightID
Value of the highlightId
custom data attribute of the currently clicked highlight element which the mouse has clicked out ofstring( uuid
)Events.CREATED
triggers when a highlight is createdargument property description type highlight
Object obtained from parsing a serialized highlight which has been successfully created from user input Object Events.LOADED
triggers when a highlight is loadedargument property description type highlight
Object obtained from parsing a serialized highlight which has been successfully restored in the DOM from another source Object Events.REMOVED
triggers when a highlight is removedargument property description type highlightID
Value of the highlightId
custom data attribute of the highlight element removedstring( uuid
)Events.ERROR_LOADING
triggers when a serialized highlight is malformed / invalid and cannot not be restoredargument property description type highlight
Object obtained from parsing a serialized highlight Object error
Error message explaining reason why the highlight failed to be restored string Events.SHOW_MENU
Triggers when user has selected a new valid range (to be used to position menu)No argument passed
Events.HIDE_MENU
Triggers when the menu has to be hiddenNo argument passed
Users can listen to any of one of these events like so:
highliter
.on(Highliter.Events.HOVER, ({ highlightID }) => {
// do something on hover
})
.on(Highliter.Events.HOVER_OUT, ({ highlightID }) => {
// do something on hover out
})
.on(Highliter.Events.CLICKED_OUT, ({ highlightID }) => {
// do something on click out
});
Adds event listeners to enable all core highlighting features. Text selected is not automatically highlighted, but instead it listens to a custom highlight
event to create a highlight. Call this method to restart the highlighter after .pause()
or .terminate()
Pauses ability to highlight by removing event listenings on mouseup
and highlight
. Existing highlights on the DOM can still be clicked and hovered on.
Completely removes all highlighter related functionalities and clears all highlights on the screen
Restores highlights from an array of js object(s) containing info about each highlight. The argument highlights
is an array of objects obtained from parsing the serialized JSON data of highlights. For each object in highlights
, it will emit Events.ERROR_LOADING
if it fails to restore the highlight, and Events.LOADED
if successful.
Changes the highlight color. The argument color
is a string representing either the RGB, RGBA or HEX value of the color of the highlight created.
Changes the hover color of the highlights. The argument color
is a string representing either the RGB, RGBA or HEX value of the color which a highlight takes on when a mouse enters it
Removes all highlight snippets with the highlightId
data attribute matching the argument passed in from the DOM. The argument highlightID
is a string representing the uuid given to each highlight.
Clears all highlights from the DOM.
By default, the Highliter
instance does not create a highlight whenever a user selects a new range. It does this because most use cases dictates a user to confirm the highlight, such as by pressing a button (the exposed events Highliter.Events.SHOW_MENU
and Highliter.Events.HIDE_MENU
are provided to make the hiding/showing and positioning of a menu easy to implement). Instead, it listens for the highlight
event, which is a custom event that needs to be dispatched by the script using this library. For example, in demo/menu.js
, this is done by adding a click
event listener to the highlight-menu
element, and dispatching a new CustomEvent
whenever it is clicked:
this.addEventListener('click', () => {
this.dispatchEvent(
new CustomEvent('highlight', {
bubbles: true,
composed: true // required here in order to cross ShadowDOM boundary
})
);
});
Note: In the particular example above, composed:true
has to be set because the event is being dispatched from within a shadow dom and thus the property needs to be set to true
so that the highlight
event can bubble past the shadow dom boundary and reach the event listener in the document
.
- Add support for mixing of colors for overlapping highlights