Please do not reprint without permission

preface

My preface is all words of conscience, or read:

When others saw the title, they thought, “How is this another old toothless requirement? There are a lot of solutions on the Internet!” . That’s right! This demand is really old-fashioned can not be any more old-fashioned, I really do not want to write such a demand of the article, but! Helpless!

The reality is that I think so many old demand online information, although I know there may be a hole in the online information, but I always believe that I can not find a good comprehensive information. However, the reality is a slap face, I did not find, and a lot of information is a copy of a, uneven quality, I think a lot of people looking for information also have a deep experience

In order to let others do not follow my path, I hereby write this article to share with you

I can’t say that the quality of my writing is fantastic. But I will be here, objectively pointing out the shortcomings of my plan, not fooling others.

The purpose of this article is twofold:

  • Let the lack of experience in this respect can easily come up with a more comprehensive solution, relatively responsible for their own company, do not qa to mention a lot of bugs (I also come so, pure want to help small white)
  • Let more competent people supplement and improve my plan, or draw on my experience to create a stronger and more comprehensive plan, of course, I also hope that I can learn the best.

directory

  • demand
  • The simplest solution
    • Super simple processing
    • Simple Treatment 2
    • disadvantages
  • Using DOM node highlighting (Base version)
    • splitText
    • disadvantages
  • Multiple highlighting (single keyword highlighting completed version)
    • Only raw data is processed
    • Turn off old highlighting and turn on new highlighting
    • summary
  • Multiple keywords are highlighted simultaneously
  • Grouping of multiple keyword highlighting
  • A highlighting scheme that can return the number of matches
  • conclusion
  • I don’t want to read it. Just use the code

demand

But let’s talk about what the need is. You’ve all tried to find a match on a web page by pressing “CTRL + F” and typing in keywords.

Yes, it’s a similarly simple need. But such a simple requirement can be deadly. This requirement (or this form) is clearly described in words:

The page has a button, or an input box, that highlights certain keywords (any string, except newlines) on the page, noting that the page content is any possible page

It’s abstract, right? I’ll just have a specific need:

Implement a plugin that highlights the keywords you want on anyone else’s web page.

The implementation of the plug-in itself is not described here, only the highlighted scheme.

I’m going to step from simple requirements to complex requirements and show you exactly what you need to consider here.

The simplest solution

Find the matching text in the string, then surround it with an HTML element, add the class name, and highlight it with CSS! Right? Everything feels so natural. I’m not going to talk about what’s wrong with the plan, but what needs to be done when it’s actually done.

Super simple processing

// js
var keyword = 'Key word 1';    // Suppose the keyword here is "keyword 1"
var bodyContent = document.body.innerHTMl;  // Get the page content
var contentArray = bodyContent.split(keyword);
document.body.innerHTMl = contentArray.join('<span>' + keyword + '</span>');
Copy the code
// css
.highlight {
    background: yellow;
    color: red;
}
Copy the code

Simple Treatment 2

It’s not that easy, and the reason WHY I’m talking about this is because I’m going to use this knowledge in the complicated scenarios that I’m going to talk about.

Key word processing

STR. Match (‘keyword’); STR. Match (‘keyword’); . But are we going to do a general function, or are we going to do special processing for some escaped characters, such as keywords? The keyword ‘, ‘? keyword’.match(‘? keyword’); , an error will be reported.

I found a variety of special characters for testing, and finally formed the following method for processing various special characters.

// String is the keyword to be matched
// Result transformString is the keyword to be used for matching after processing
var transformString = string.replace(/[.[*?+^$|()/]|\]|\\/g.'\ \ $&');
Copy the code

Look not to understand? If you want to dig deeper, you can take a look at this article: this is an introduction to men and women proficient in the regular notes. Anyway, here means that various escaped characters into ordinary characters, so that you can match out.

Match the highlight

/ / js parts
var bodyContent = document.body.innerHTMl;  // Get the page content
var pattern = new RegExp(transformString, 'g'); // Generate a regular expression
// Match keywords and replace
document.body.innerHTMl = bodyContent.replace(pattern, '<span class="highlight">$&</span>');
Copy the code
// css
.highlight {
    background: yellow;
    color: red;
}
Copy the code

disadvantages

Treating the content of a page as if it were a string has many unexpected consequences.

  • The script tag contains matching text. An error was reported when highlighting HTML elements.
  • Tag attributes (especially custom attributes such as dats-*) have matching text, adding highlighting will break the original function
  • Matches the text exactly to an inline style text, as in<div style="width: 300px;" ></div>, the key word is justwidthThat’s where it gets awkward. The substitution is zero<div style="<span class="highlight">width</span>: 300px;" ><div. This is breaking the original style.
  • There’s another case where you haveThe < div > right < / div >Key words:> rightIn this case, the substitution result is< div < span class = "highlight" > > right < / span > < / div >It also destroys the structure.
  • And there are many, many more scenarios. The above are just a few of them. There are too many unknowns

Using DOM node highlighting (Base version)

Since the string method has too many drawbacks, it can only be discarded, find another method. This section tests everyone’s basic knowledge to tie not solid

The content of the page is composed of a DOM tree, in which there is a node called text node, which is the text that we can see on the page (except most, pictures, etc.), so we only need to find out whether there is a keyword we match in these text nodes, and the matched text node will be transformed.

Encapsulate a function to do the above (explained one by one in the comments).


/ / 1.
// String is the keyword to be matched
// Result transformString is the keyword to be used for matching after processing
var transformString = string.replace(/[.[*?+^$|()/]|\]|\\/g.'\ \ $&');
var pattern = new RegExp(transformString, 'i'); // This is case insensitive

/** * ② Highlight the keyword * @param node - node * @param pattern - regular expression for matching the above pattern */
function highlightKeyword(node, pattern) {
    // nodeType equals 3 to indicate a text node
    if (node.nodeType === 3) {
        // Node. data is the text content of the text node
        var matchResult = node.data.match(pattern);
        // There is a match
        if (matchResult) {
            // Create a SPAN node to wrap the matched keyword content
            var highlightEl = document.createElement('span');
            // Use a custom attribute data-* instead of the class name to control the highlighting.
            // Less likely to have the same name as the original content than using the class name, avoiding style overwriting
            highlightEl.dataset.highlight = 'yes';
            // splitText; // splitText
            // Truncate to the end of the original node from the initial position matched to generate a new text node
            var matchNode = node.splitText(matchResult.index);
            // Truncate again from the new text node, starting with the length of the matched keyword,
            // The text between 0 and length is the text content of matchNode
            matchNode.splitText(matchResult[0].length);
            // Create a new text node for the content of the matchNode text node (i.e. the matched keyword content)
            var highlightTextNode = document.createTextNode(matchNode.data);
            // Insert into the span node created
            highlightEl.appendChild(highlightTextNode);
            // Replace the original matchNode node with a span node for highlightingmatchNode.parentNode.replaceChild(highlightEl, matchNode); }}// If it is an element node and is not a script, style, and highlighted element
    // As for distinguishing between elements that you don't want to highlight, you can add that script and style are basic
    // The reason for not having highlighted elements as one of the conditions is to avoid going into an endless loop and wrapping the SPAN tag inside
    else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase())) && (node.dataset.highlight ! = ='yes')) {
        // Traverses all descendants of the node to highlight the text node
        var childNodes = node.childNodes;
        for (var i = 0; i < childNodes.length; i++) { highlightKeyword(childNodes[i], pattern); }}}Copy the code

Note that the pattern parameter is the regular expression after the above keyword processing

/** CSS highlight style Settings **/
[data-highlight=yes] {
    display: inline-block;
    background: #32a1ff;
}
Copy the code

I’m using a property selector here

splitText

This method is used for text nodes, and can be used by IE8+. It is used to separate a text node from another text node as its sibling, i.e. they are the same parent.

Even though the div originally had only one text node and then became two, it still looks the same to the actual page.

grammar

/** * @param offset Specifies the offset. The value is an integer from 0 to a string length. * @returns replacementNode - The new text node to be truncated, including the text at offset */
replacementNode = textnode.splitText(offset)
Copy the code

example

<body>
  <p id="p">example</p>

  <script type="text/javascript">
    var p = document.getElementById('p');
    var textnode = p.firstChild;

    // Split the original text node into two text nodes with the contents of exa and mple respectively
    var replacementNode = textnode.splitText(3);

    // Create a SPAN element that contains a text node with the content 'new span'
    var span = document.createElement('span');
    span.appendChild(document.createTextNode(' new span '));
    // Insert the SPAN element before the next text node ('bar')
    p.insertBefore(span, replacementNode);

    

exanew spanmple

</script> </body> Copy the code

In the last example, the insertion of the SPAN node makes it clear that the original text node “example” does become two “exa” and “mple”, otherwise the span node would not be in the middle of the two.

disadvantages

A basic version of the highlighting scheme has been developed that solves the problem with the string scheme. However, there are things that need to be dealt with or considered extra.

  • The solution here is fine to highlight once, but what about multiple different keyword highlights?
  • Other people’s pages are unpredictable. If there is some hidden text on the page that is hidden by color, such as a white background, the text color is also white, highlighting may also bring out the hidden information. (There’s nothing I can do about it.)

Multiple highlighting (single keyword highlighting completed version)

To achieve multiple highlighting, that is, to achieve the second highlight, the last highlight trace to erase, there will be two ideas:

  • Only raw data is processed at each highlighting.
  • You need one to turn off the old highlighting and then re-highlight the new keyword

Only raw data is processed

This is actually a good idea, because it feels like it’s going to be pretty easy to do every time you use the basic highlighting scheme, and there’s no DOM contamination (in this case, highlighting on top of already contaminated DOM). Main treatment means:

// Save the original DOM information when entering someone else's page
const originalDom = document.querySelector('body').innerHTML;
Copy the code
// Highlight the logic to start...
let bodyNode = document.querySelector('body');
// Reassign the original DOM information to the body
bodyNode.innerHTML = originalDom
// Convert the original DOM information into a node object again
let bodyChildren = bodyNode.childNodes;
// Highlight the content
for (var i = 0; i < bodyChildren.length; i++) {
    // In this case, pattern is the regex generated by the above processed keywords
    highlightKeyword(bodyChildren[i], pattern);
}
Copy the code

This is the main logic for doing one highlighting, so if you want to highlight it multiple times, run the logic over and over again, just change the key word. It is also important to understand that because the highlighted functions are handled with node objects, it is important to convert the saved DOM information (in this case, strings) back to node objects.

The solution is simple and seems perfect, but there are a few things you have to consider:

  • I don’t like the idea of converting an object to a string and then to an object, because I don’t know if the conversion will completely capture the information or lose some of it, as is often the case with deep copyJSON.parse(JSON.stringify())The disadvantages of the same. We never know how someone else’s site was generated, or whether it was generated from information that happened to be lost in the conversion process. We can’t be sure. Therefore, I do not recommend using this method. I did a simple test here this time, and found that some information would still be lost. The information of test was missing.
  • In practice, there are limitations, such as one scenario where using this approach is not a good idea: Chrome Extension is embedded as an iframe on someone else’s web page. Directly by using this method, because the body innerHTML to assign a value, the content of the page to brush again (browser performance it may see a flash of flash), and the plug-in iframe is not exceptional also, in this case, had not saved on the plug-in content or operation it will refresh to initial conditions, Anyway, if the plugin iframe situation is also changed, it is not good.

Turn off old highlighting and turn on new highlighting

In addition to the above method, there is another method here. You certainly think that off is not to set the highlighting style is gone, right, it is so, but the overall idea of summing up the idea, to implement the practice, to consider the place is often not as easy as imagined. The general idea is simple: find the node that is already highlighted (dataset. Highlight = ‘yes’) and remove the wrapper.

// Remember the name of this function
function closeHighlight() {
    let highlightNodeList = document.querySelectorAll('[data-highlight=yes]');
    for (let n = 0; n < highlightNodeList.length; n++) {
        let parentNode = highlightNodeList[n].parentNode;
        // Generate a new text node for the text in the highlighted wrapper
        let textNode = document.createTextNode(highlightNodeList[n].innerText);
        // Replace the highlighted node with a new text node
        parentNode.replaceChild(textNode, highlightNodeList[n]);
        // Combine adjacent text nodes into a single text nodeparentNode.normalize(); }}Copy the code

Then highlight the new keyword and run the highlighted function encapsulated above.

For an explanation of normalize, see:

Developer.mozilla.org/en-US/docs/…

Combine adjacent text nodes into a single text node to avoid truncating the text, and then highlighting other keywords will not work. Such as:

<div>Hello, hello,</div>
Copy the code

The first keyword, “hello”, is highlighted and closed. The original div had only one text child node, but now it has two, “Hello” and “hello everyone”. So if this matches the keyword “o large”, it will not match. Because it’s not on the same node.

summary

At this point, a solution for highlighting single keywords that can be used multiple times has come to an end. There is an option: turn on new highlighting only for raw data processing and turn off old highlighting. Each has its own advantages and disadvantages, we choose according to their actual project needs, or even lower requirements, directly adopt the top of each simple scheme.

Multiple keywords are highlighted simultaneously

The schemes here and below are based on DOM highlighting – turn off the old highlighting and turn on the new highlighting scheme. In fact, with the above foundation, the following requirements are icing on the cake, not too complex.

First of all, on the processing of keywords:

// Multiple keywords to be matched
let keywords = ['Hello'.'pekonChan'];
let wordMatchString = ' '; // It is used to form the final result of special character processing for multiple keywords
keywords.forEach(item= > {
    // Each keyword should be treated with special characters
    let transformString = item.replace(/[.[*?+^$|()/]|\]|\\/g.'\ \ $&');
    / / use '|' to represent or, regular
    wordMatchString += ` | (${transformString}) `;
});
wordMatchString = wordMatchString.substring(1);
// Form a regular expression that matches multiple keywords to turn on highlighting
let pattern = new RegExp(wordMatchString, 'i');
// Form a regular expression that matches multiple keywords (without inclusion) to turn off highlighting
let wholePattern = new RegExp(` ^${wordMatchString}$`.'i');
Copy the code

The next step is the same as the “turn off the old highlight to turn on the new highlight” procedure, except for the different processing of keywords.

disadvantages

Highlight the existence order. What do you mean? For example, if you have a set of keywords [‘ photo ‘, ‘photo ‘], highlight one of the following elements:

<div>Change background color for id photo</div>
Copy the code

After highlighting with the above method, the result is:

<div><span data-highlight="yes">Profile picture<span>Change the background color</div>
Copy the code

It turns out that only the id photo is highlighted because the ID photo comes first when the matching re is generated. If you change the order [‘ photo for ‘, ‘photo for ‘], the result would be:

<div> id <span data-highlight="yes"<span> Background color </div>Copy the code

This kind of problem, to be honest, I can do nothing to solve it now, if you have a better plan, please tell me to learn ~

Grouping of multiple keyword highlighting

Here I use examples to illustrate the requirements, as shown in the figure


The red box is a Chrome extension. The left side of the extension is any other person’s page (a highlighted page object).

  • Each row has a set of keywords,
  • There is an eye icon in the perspective word exposure column. One click turns on the keyword highlighting below the line, and another click turns off the highlighting.
  • Highlighting between rows can be done at the same time, independently of each other

Let’s first take a look at the shortcomings of our existing solution (based on the simultaneous highlighting of multiple keywords) in meeting the above requirements:

For example, if the first set of keywords is highlighted and set to yes, the text that the second set of keywords needs to be highlighted is in the first set of highlighted text and is included. Since the first set of keyword highlighting text has been set to YES, the second set of keywords in the highlighted mode will not continue through the first set of highlighted nodes. Unfortunately, this results in the second set of words being highlighted when the first set of words is turned off, but the text that should be in the first set of words is not highlighted because it has not been traversed

Hard to understand? For example, the first set of keywords (assuming they are all single) is “Coca-Cola” and the second set is “Cola”

Highlight mode is enabled in the first row of the table. Result:

<div>
    <span data-highlight="yes" data-highlightMatch="Hello">Coca-Cola</span>
</div>
Copy the code

Next, the second line also turns on the highlight mode, and executes the else if of the highlightKeyword function. Here, since the span around the Coca-Cola has been set to Yes, I don’t want to scroll down.

function highlightKeyword(node, pattern) {
    if (node.nodeType === 3) {... }else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase())) && (node.dataset.highlight ! = ='yes')) {... }}Copy the code

The result is still:

<div>
    <span data-highlight="yes" data-highlightMatch="Hello">Coca-Cola</span>
</div>
Copy the code

However, when highlighting mode is turned off on the first line, the result is:

<div>Coca-Cola</div>
Copy the code

However, I only turned off the highlight on the first line. The second line still shows the same highlighting mode, but the “Cola” keyword in the second line is not highlighted. That’s the downside!

Set up the group

To solve the above problem, you need to group the highlighted nodes as well. The highlightKeyword function needs to be modified to add an index parameter and bind it to the dataset. Else if criteria need to be modified as well.

/** ** highlight keywords * @param node node * @param pattern matching regular expression * @param index - indicates the group of keywords */
function highlightKeyword(node, pattern, index) {
    if (node.nodeType === 3) {
        let matchResult = node.data.match(pattern);
        if (matchResult) {
            let highlightEl = document.createElement('span');
            highlightEl.dataset.highlight = 'yes';
            // Stores the text of the matching result in the dataset to turn off highlighting. See below for details
            highlightEl.dataset.highlightMatch = matchResult[0];
            // Record the group of keywords
            highlightEl.dataset.highlightIndex = index; 
            letmatchNode = node.splitText(matchResult.index); . }}else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase()))) {
        // If this object is the iframe of the plug-in, it is not highlighted
        if (node.className === 'extension-iframe') {
            return;
        }
        // If this object is marked with yes and belongs to the group of keywords, no processing is done
        if (node.dataset.highlight === 'yes') {
            if (node.dataset.highlightIndex === index.toString()) {
                return; }}let childNodes = node.childNodes;
        for (let i = 0; i < childNodes.length; i++) { highlightKeyword(childNodes[i], pattern, index); }}}Copy the code

In this case, the other group keywords contained in the first group can also continue to be highlighted.

Have noticed that the added that code highlightEl. Dataset. HighlightMatch = matchResult [0].

This sentence is intended to be used for the group off highlighting described below. Highlignth =yes cannot be used to distinguish between the highlighted nodes that fit the content. For example, the highlighted node matching is “Hello”, then highlightEl. Dataset. HighlightMatch is “Hello”, want to close this because “Hello” to produce the highlighted node, To determine highlightEl. Dataset. HighlightMatch = = ‘Hello’

The innerText or firstChid text node in the dataset is the innerText or firstChid text node in the element.

<div>
    <span data-highlight="yes" data-highlight-index="1">Hel<span data-highlight="yes" data-highlight-index="2">lo</span></span>
    , I'm pekonChan
</div>
Copy the code

When the hello is split into several nodes, using innerText or firstChid doesn’t work.

Turn off highlighting in groups as well

CloseHighlight function closeHighlight function closeHighlight function closeHighlight function closeHighlight function closeHighlight function closeHighlight function closeHighlight

// String is the keyword to be matched
let transformString = string.replace(/[.[*?+^$|()/]|\]|\\/g.'\ \ $&');
// There is a distinction here, which is used for grouping off highlighting
let wholePattern = new RegExp(` ^${transformString}$`.'i');
// For highlighting matches
let pattern = new RegExp(transformString, 'i');
Copy the code

The reason why pattern is different from the previous one is that highlighting and closing of nodes can only be set when the key words are fully matched (not included). If you want to turn off highlighting with the keyword “Hello”, you should not turn it off in the following element, only if it conforms to “Hello” exactly

<div data-highlight="no" data-highlightMatch="showHello"></div>
Copy the code

Next, modify the original off highlighting function:

function closeHighlight(wholePattern, index) {
    let highlightNodeList = document.querySelectorAll('[data-highlight=yes]');
    for (let n = 0; n < highlightNodeList.length; n++) {
        const dataset = highlightNodeList[n].dataset;
        // If you do not need to group or group the wrong group, do not need to cancel
        if (index == null|| dataset.highlightIndex ! == index.toString()) {return;
        }
        WholePattern = wholePattern
        if (wholePattern.test(highlightNodeList[n].dataset.highlightMatch)) {
            // Record the parent of the highlighted node to close
            let parentNode = highlightNodeList[n].parentNode;
            // Records the children of the highlighted node to be closed
            let childNodes = highlightNodeList[n].childNodes;
            let childNodesLen = childNodes.length;
            // Records the next sibling of the highlighted node to close
            let nextSibling = highlightNodeList[n].nextSibling;
            // Move the child of the highlighted node to the front of its sibling
            for (let k = 0; k < childNodesLen; k++) {
                parentNode.insertBefore(childNodes[0], nextSibling);
            }
            // Create a blank text node and replace the original highlighted node
            let flagNode = document.createTextNode(' ');
            parentNode.replaceChild(flagNode, highlightNodeList[n]);
            // Merge adjacent text nodesparentNode.normalize(); }}}Copy the code

You can obviously see that the way we used to replace the highlighted node with only innerText is gone, because it doesn’t work, because it can happen like this:

<h1>
    <span data-highlight="yes" data-highlightIndex="1" data-highlight-match="Certificate photo">Profile picture<span data-highlight="yes" data-highlightIndex="2">lo</span>
    </span>
    , I'm pekonChan
</h1>
Copy the code

If we still use the original way then the inner layer of the second group of highlighting is also gone:

<h1>I'm pekonChan</h1>
Copy the code

Because we want to keep all the children of the highlighted node, we’re just removing a wrapper.

Note that the for loop changes every time it moves, because the insertBefore method adds an insert if there is no node to insert, and if there is, the insertBefore method cuts and moves the insert, and then the old node is gone. Therefore, childNodes will change, so we only use the initial length of childNodes to insert the first node of childNodes (since the first node is removed, the second node becomes the first node).

disadvantages

In fact, the disadvantage here is that the previous section’s multiple keyword highlighting is the same as the portal

A highlighting scheme that can return the number of matches

As you can see from the above requirement, there is a number next to the eye icon in the view words column, which is actually the number of keywords that can be highlighted. So here is also a small change to make it possible to calculate the number (change in the comment section) :

/** * highlight keywords * @param node node * @param pattern matching regular expression * @param index - indicates the number of keywords * @returns exposeCount - Number of exposeCount */
function highlightKeyword(node, pattern, index) {
    let exposeCount = 0;    // Expose the number of variables
    if (node.nodeType === 3) {
        let matchResult = node.data.match(pattern);
        if (matchResult) {
            let highlightEl = document.createElement('span');
            highlightEl.dataset.highlight = 'yes';
            highlightEl.dataset.highlightMatch = matchResult[0];
            highlightEl.dataset.highlightIndex = index;
            let matchNode = node.splitText(matchResult.index);
            matchNode.splitText(matchResult[0].length);
            let highlightTextNode = document.createTextNode(matchNode.data);
            highlightEl.appendChild(highlightTextNode);
            matchNode.parentNode.replaceChild(highlightEl, matchNode);
            exposeCount++;  // For each highlight, the number of exposures increases once}}else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase()))) {
        if (node.className === 'eod-extension-iframe') {
            return;
        }
        if (node.dataset.highlight === 'yes') {
            if (node.dataset.highlightIndex === index.toString()) {
                return; }}let childNodes = node.childNodes;
        for (let i = 0; i < childNodes.length; i++) { highlightKeyword(childNodes[i], pattern, index); }}return exposeCount; // Return the number of exposures
}
Copy the code

disadvantages

Because the number of statistical exposures is counted together with the actual highlighting, and as mentioned above, there is a problem with the order of highlighting in this highlighting scheme, so the number of statistics will be accurate.

If you don’t care that the number of highlights and statistics must be the same, IF you want a very accurate statistics, I can provide two ideas, but due to space problems, I will not write out, read this article will not be difficult to my ideas, just tedious:

  1. Using the above raw data only processing scheme, for each keyword, do a “fake” highlighting, number of times with the number of highlighting, but note that this is only for the number of statistics, do not really highlight the page (if you do not want this kind of highlighting), you can be statistically accurate.
  2. Do not use the “raw data only” scheme, in the original scheme, can be indata-highlight="yes"Under the same group of keywords, to judge whether the included perspective word exists, the number of exposure is increased by 1, but I still don’t know how to achieve it.

conclusion

I feel that I have written a lot, I think I should be more clear, which scheme is which disadvantages. But I want to be clear, there is no saying which one is better! Only the right solution to meet the needs is a good solution, if you just used to cut apples, not with a fruit knife, but with a pig knife, it can cut ah, but also can cut a lot of things. But do you think it’s a good idea?

Here is also this meaning, why don’t I directly write the most comprehensive plan, we directly copy and paste away without sending, but also so much, for is to let everyone find their own needs according to their own way!

At the end of this article, I provide the most comprehensive solution for the convenience of those who are really anxious to do the project and have no time to read my article in detail or don’t want to consider so many people.

If this article is helpful to you, please give a thumbs up. Please do not reprint it without permission. It is not easy to write an article

After the document was published, it was modified once, modified at 2018/12/28 12:16

The most complete plan for the time being

Highlighting function

/** * highlight keyword * @param node node * @param pattern matching regular expression * @param index - Optional. The specific requirements in this project represent the set of keywords * @returns exposeCount - number of exposures */
function highlightKeyword(node, pattern, index) {
    var exposeCount = 0;
    if (node.nodeType === 3) {
        var matchResult = node.data.match(pattern);
        if (matchResult) {
            var highlightEl = document.createElement('span');
            highlightEl.dataset.highlight = 'yes';
            highlightEl.dataset.highlightMatch = matchResult[0];
            (index == null) || highlightEl.dataset.highlightIndex = index;
            var matchNode = node.splitText(matchResult.index);
            matchNode.splitText(matchResult[0].length);
            var highlightTextNode = document.createTextNode(matchNode.data); highlightEl.appendChild(highlightTextNode); matchNode.parentNode.replaceChild(highlightEl, matchNode); exposeCount++; }}// Add the specific conditions. Here are the basic conditions
    else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase()))) {
        if (node.dataset.highlight === 'yes') {
            if (index == null) {
                return;
            }
            if (node.dataset.highlightIndex === index.toString()) {
                return; }}let childNodes = node.childNodes;
        for (var i = 0; i < childNodes.length; i++) { highlightKeyword(childNodes[i], pattern, index); }}return exposeCount;
}
Copy the code

Keywords are processed (special character escape) to form a matching regular expression

/ * * * @ param {String | Array} keywords - to highlight keywords or keyword * @ returns an Array of {Array} * /
function hanldeKeyword(keywords) {
    var wordMatchString = ' ';
    var words = [].concat(keywords);
    words.forEach(item= > {
        let transformString = item.replace(/[.[*?+^$|()/]|\]|\\/g.'\ \ $&');
        wordMatchString += ` | (${transformString}) `;
    });
    wordMatchString = wordMatchString.substring(1);
    // Match re for highlighting and closing keywords again as a whole
    var wholePattern = new RegExp(` ^${wordMatchString}$`.'i');
    // The keyword matching re used for the first highlighting
    var pattern = new RegExp(wordMatchString, 'i');
    return [pattern, wholePattern];
}
Copy the code

Turn off the highlighting function

/** * @param pattern matches the regular expression * @param index keyword group, i.e. the group of keywords */
function closeHighlight(pattern, index) {
    var highlightNodeList = document.querySelectorAll('[data-highlight=yes]');
    for (var n = 0; n < highlightNodeList.length; n++) {
        const dataset = highlightNodeList[n].dataset;
        // If you do not need to group or group the wrong group, do not need to cancel
        if (index == null|| dataset.highlightIndex ! == index.toString()) {return;
        }
        if (pattern.test(dataset.highlightMatch)) {
            var parentNode = highlightNodeList[n].parentNode;
            var childNodes = highlightNodeList[n].childNodes;
            var childNodesLen = childNodes.length;
            var nextSibling = highlightNodeList[n].nextSibling;
            for (var k = 0; k < childNodesLen; k++) {
                parentNode.insertBefore(childNodes[0], nextSibling);
            }
            var flagNode = document.createTextNode(' '); parentNode.replaceChild(flagNode, highlightNodeList[n]); parentNode.normalize(); }}}Copy the code

The base application

// Only highlight once
// The keyword to match
var keywords = 'Hello';
var patterns = hanldeKeyword(keywords);
// Highlight the body content
var bodyChildren = window.document.body.childNodes;
for (var i = 0; i < bodyChildren.length; i++) {
    highlightKeyword(bodyChildren[i], pattern[0]);
}


// Then highlight other keywords
// You may need to erase the ones that don't need to be highlighted before they are needed
keywords = 'World'; // New keyword
closeHighlight(patterns[1]);
patterns = hanldeKeyword(keywords);
// Highlight for new keywords
for (var i = 0; i < bodyChildren.length; i++) {
    highlightKeyword(bodyChildren[i], pattern[0]);
}
Copy the code
// css
.highlight {
    background: yellow;
    color: red;
}
Copy the code

Please do not reprint without permission