Как выделить текст с помощью JavaScript - PullRequest
76 голосов
/ 27 декабря 2011

Может ли кто-нибудь помочь мне с функцией JavaScript, которая может выделить текст на веб-странице. И требование - выделять только один раз, а не выделять все вхождения текста, как в случае поиска.

Ответы [ 12 ]

78 голосов
/ 27 декабря 2011

Вы можете использовать эффект выделения jquery .

Но если вас интересует сырой код JavaScript, посмотрите, что я получил. Просто скопируйте вставьте в HTML, откройте файли нажмите «выделить» - это должно выделить слово «лиса».По производительности я думаю, что это подойдет для небольшого текста и одного повторения (как вы указали)

function highlight(text) {
  var inputText = document.getElementById("inputText");
  var innerHTML = inputText.innerHTML;
  var index = innerHTML.indexOf(text);
  if (index >= 0) { 
   innerHTML = innerHTML.substring(0,index) + "<span class='highlight'>" + innerHTML.substring(index,index+text.length) + "</span>" + innerHTML.substring(index + text.length);
   inputText.innerHTML = innerHTML;
  }
}
.highlight {
  background-color: yellow;
}
<button onclick="highlight('fox')">Highlight</button>

<div id="inputText">
  The fox went over the fence
</div>

Правки:

Использование replace

Я вижу, этот ответ приобрел некоторую популярность, я думал, что ямог бы добавить на это.Вы также можете легко использовать replace

"the fox jumped over the fence".replace(/fox/,"<span>fox</span>");

. Или для нескольких вхождений (не относящихся к вопросу, но задаваемых в комментариях), вы просто добавляете global к регулярному выражению замены.

"the fox jumped over the other fox".replace(/fox/g,"<span>fox</span>");

Надеюсь, это поможет заинтригованным комментаторам.

Замена HTML-кода на всю веб-страницу

Для замены HTML-кода на всю веб-страницу, вам следует обратиться к innerHTML тела документа.

document.body.innerHTML

35 голосов
/ 22 апреля 2015

Предлагаемые здесь решения довольно плохие.

  1. Вы не можете использовать регулярные выражения, потому что таким образом вы будете искать / выделять в тегах html.
  2. Вы не можете использовать регулярное выражение, потому что оно не работает должным образом с UTF * (что-либо с нелатинскими / английскими символами).
  3. Вы не можете просто создать innerHTML.replace, потому что это не работает, когда символы имеют специальную HTML-нотацию, например &amp; для &, &lt; для <, <code>&gt; для>, &auml; для & auml ;, &ouml; для & ouml; &uuml; для & uuml; &szlig; для & szlig; и т. Д.

Что нужно сделать:

Циклический просмотр документа HTML, поиск всех текстовых узлов, получение textContent, получение позиции выделенного текста с помощью indexOf (с необязательным toLowerCase, если он должен быть без учета регистра), добавление всего перед indexof как textNode добавьте соответствующий текст с выделенным интервалом и повторите для остальной части текстового узла (строка выделения может встречаться несколько раз в строке textContent).

Вот код для этого:

var InstantSearch = {

    "highlight": function (container, highlightText)
    {
        var internalHighlighter = function (options)
        {

            var id = {
                container: "container",
                tokens: "tokens",
                all: "all",
                token: "token",
                className: "className",
                sensitiveSearch: "sensitiveSearch"
            },
            tokens = options[id.tokens],
            allClassName = options[id.all][id.className],
            allSensitiveSearch = options[id.all][id.sensitiveSearch];


            function checkAndReplace(node, tokenArr, classNameAll, sensitiveSearchAll)
            {
                var nodeVal = node.nodeValue, parentNode = node.parentNode,
                    i, j, curToken, myToken, myClassName, mySensitiveSearch,
                    finalClassName, finalSensitiveSearch,
                    foundIndex, begin, matched, end,
                    textNode, span, isFirst;

                for (i = 0, j = tokenArr.length; i < j; i++)
                {
                    curToken = tokenArr[i];
                    myToken = curToken[id.token];
                    myClassName = curToken[id.className];
                    mySensitiveSearch = curToken[id.sensitiveSearch];

                    finalClassName = (classNameAll ? myClassName + " " + classNameAll : myClassName);

                    finalSensitiveSearch = (typeof sensitiveSearchAll !== "undefined" ? sensitiveSearchAll : mySensitiveSearch);

                    isFirst = true;
                    while (true)
                    {
                        if (finalSensitiveSearch)
                            foundIndex = nodeVal.indexOf(myToken);
                        else
                            foundIndex = nodeVal.toLowerCase().indexOf(myToken.toLowerCase());

                        if (foundIndex < 0)
                        {
                            if (isFirst)
                                break;

                            if (nodeVal)
                            {
                                textNode = document.createTextNode(nodeVal);
                                parentNode.insertBefore(textNode, node);
                            } // End if (nodeVal)

                            parentNode.removeChild(node);
                            break;
                        } // End if (foundIndex < 0)

                        isFirst = false;


                        begin = nodeVal.substring(0, foundIndex);
                        matched = nodeVal.substr(foundIndex, myToken.length);

                        if (begin)
                        {
                            textNode = document.createTextNode(begin);
                            parentNode.insertBefore(textNode, node);
                        } // End if (begin)

                        span = document.createElement("span");
                        span.className += finalClassName;
                        span.appendChild(document.createTextNode(matched));
                        parentNode.insertBefore(span, node);

                        nodeVal = nodeVal.substring(foundIndex + myToken.length);
                    } // Whend

                } // Next i 
            }; // End Function checkAndReplace 

            function iterator(p)
            {
                if (p === null) return;

                var children = Array.prototype.slice.call(p.childNodes), i, cur;

                if (children.length)
                {
                    for (i = 0; i < children.length; i++)
                    {
                        cur = children[i];
                        if (cur.nodeType === 3)
                        {
                            checkAndReplace(cur, tokens, allClassName, allSensitiveSearch);
                        }
                        else if (cur.nodeType === 1)
                        {
                            iterator(cur);
                        }
                    }
                }
            }; // End Function iterator

            iterator(options[id.container]);
        } // End Function highlighter
        ;


        internalHighlighter(
            {
                container: container
                , all:
                    {
                        className: "highlighter"
                    }
                , tokens: [
                    {
                        token: highlightText
                        , className: "highlight"
                        , sensitiveSearch: false
                    }
                ]
            }
        ); // End Call internalHighlighter 

    } // End Function highlight

};

Тогда вы можете использовать его так:

function TestTextHighlighting(highlightText)
{
    var container = document.getElementById("testDocument");
    InstantSearch.highlight(container, highlightText);
}

Вот пример HTML-документа

<!DOCTYPE html>
<html>
    <head>
        <title>Example of Text Highlight</title>
        <style type="text/css" media="screen">
            .highlight{ background: #D3E18A;}
            .light{ background-color: yellow;}
        </style>
    </head>
    <body>
        <div id="testDocument">
            This is a test
            <span> This is another test</span>
            äöüÄÖÜäöüÄÖÜ
            <span>Test123&auml;&ouml;&uuml;&Auml;&Ouml;&Uuml;</span>
        </div>
    </body>
</html>

Кстати, если вы ищете в базе данных с LIKE,
например WHERE textField LIKE CONCAT('%', @query, '%') [что вы не должны делать, вы должны использовать полнотекстовый поиск или Lucene], тогда вы можете экранировать каждый символ с помощью \ и добавить SQL-escape-оператор, для которого вы найдете специальные символы, которые LIKE- выражения.

например.

WHERE textField LIKE CONCAT('%', @query, '%') ESCAPE '\'

и значение @query не '%completed%', а '%\c\o\m\p\l\e\t\e\d%'

(протестировано, работает с SQL-сервером и PostgreSQL и любой другой системой СУБД, поддерживающей ESCAPE)

<ч />

Пересмотренная машинописная версия:

namespace SearchTools 
{


    export interface IToken
    {
        token: string;
        className: string;
        sensitiveSearch: boolean;
    }


    export class InstantSearch 
    {

        protected m_container: Node;
        protected m_defaultClassName: string;
        protected m_defaultCaseSensitivity: boolean;
        protected m_highlightTokens: IToken[];


        constructor(container: Node, tokens: IToken[], defaultClassName?: string, defaultCaseSensitivity?: boolean)
        {
            this.iterator = this.iterator.bind(this);
            this.checkAndReplace = this.checkAndReplace.bind(this);
            this.highlight = this.highlight.bind(this);
            this.highlightNode = this.highlightNode.bind(this);    

            this.m_container = container;
            this.m_defaultClassName = defaultClassName || "highlight";
            this.m_defaultCaseSensitivity = defaultCaseSensitivity || false;
            this.m_highlightTokens = tokens || [{
                token: "test",
                className: this.m_defaultClassName,
                sensitiveSearch: this.m_defaultCaseSensitivity
            }];
        }


        protected checkAndReplace(node: Node)
        {
            let nodeVal: string = node.nodeValue;
            let parentNode: Node = node.parentNode;
            let textNode: Text = null;

            for (let i = 0, j = this.m_highlightTokens.length; i < j; i++)
            {
                let curToken: IToken = this.m_highlightTokens[i];
                let textToHighlight: string = curToken.token;
                let highlightClassName: string = curToken.className || this.m_defaultClassName;
                let caseSensitive: boolean = curToken.sensitiveSearch || this.m_defaultCaseSensitivity;

                let isFirst: boolean = true;
                while (true)
                {
                    let foundIndex: number = caseSensitive ?
                        nodeVal.indexOf(textToHighlight)
                        : nodeVal.toLowerCase().indexOf(textToHighlight.toLowerCase());

                    if (foundIndex < 0)
                    {
                        if (isFirst)
                            break;

                        if (nodeVal)
                        {
                            textNode = document.createTextNode(nodeVal);
                            parentNode.insertBefore(textNode, node);
                        } // End if (nodeVal)

                        parentNode.removeChild(node);
                        break;
                    } // End if (foundIndex < 0)

                    isFirst = false;


                    let begin: string = nodeVal.substring(0, foundIndex);
                    let matched: string = nodeVal.substr(foundIndex, textToHighlight.length);

                    if (begin)
                    {
                        textNode = document.createTextNode(begin);
                        parentNode.insertBefore(textNode, node);
                    } // End if (begin)

                    let span: HTMLSpanElement = document.createElement("span");

                    if (!span.classList.contains(highlightClassName))
                        span.classList.add(highlightClassName);

                    span.appendChild(document.createTextNode(matched));
                    parentNode.insertBefore(span, node);

                    nodeVal = nodeVal.substring(foundIndex + textToHighlight.length);
                } // Whend

            } // Next i 

        } // End Sub checkAndReplace 


        protected iterator(p: Node)
        {
            if (p == null)
                return;

            let children: Node[] = Array.prototype.slice.call(p.childNodes);

            if (children.length)
            {
                for (let i = 0; i < children.length; i++)
                {
                    let cur: Node = children[i];

                    // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
                    if (cur.nodeType === Node.TEXT_NODE) 
                    {
                        this.checkAndReplace(cur);
                    }
                    else if (cur.nodeType === Node.ELEMENT_NODE) 
                    {
                        this.iterator(cur);
                    }
                } // Next i 

            } // End if (children.length) 

        } // End Sub iterator


        public highlightNode(n:Node)
        {
            this.iterator(n);
        } // End Sub highlight 


        public highlight()
        {
            this.iterator(this.m_container);
        } // End Sub highlight 


    } // End Class InstantSearch 


} // End Namespace SearchTools 

Использование:

let searchText = document.getElementById("txtSearchText");
let searchContainer = document.body; // document.getElementById("someTable");
let highlighter = new SearchTools.InstantSearch(searchContainer, [
    {
        token: "this is the text to highlight" // searchText.value,
        className: "highlight", // this is the individual highlight class
        sensitiveSearch: false
    }
]);


// highlighter.highlight(); // this would highlight in the entire table
// foreach tr - for each td2 
highlighter.highlightNode(td2); // this highlights in the second column of table
28 голосов
/ 05 января 2016

Почему использование самодельной функции подсветки - плохая идея

Причина, по которой, вероятно, плохая идея начинать создание собственной функции подсветки с нуля, заключается в том, что вы наверняка столкнетесь с проблемами, которые уже решены другими.Задачи:

  • Вам потребуется удалить текстовые узлы с элементами HTML, чтобы выделить совпадения, не разрушая события DOM и не вызывая повторную генерацию DOM (что было бы, например, при innerHTML)
  • Если вы хотите удалить выделенные элементы, вам придется удалить элементы HTML с их содержимым, а также объединить разделенные текстовые узлы для дальнейшего поиска.Это необходимо, потому что каждый плагин подсветки ищет в текстовых узлах совпадения, и если ваши ключевые слова будут разбиты на несколько текстовых узлов, они не будут найдены.
  • Вам также необходимо создать тесты, чтобы убедиться, что ваш плагин работает вситуации, о которых вы не задумывались.И я говорю о кросс-браузерных тестах!

Звучит сложно?Если вам нужны такие функции, как игнорирование некоторых элементов при выделении, отображение диакритических знаков, отображение синонимов, поиск внутри фреймов, поиск по отдельным словам и т. Д., Это становится все более и более сложным.

Использование существующего плагина

При использовании существующего, хорошо реализованного плагина, вам не нужно беспокоиться о вышеперечисленных вещах.Статья 10 jQuery плагинов для подсветки текста на Sitepoint сравнивает популярные плагины для подсветки.

Посмотрите на mark.js

mark.js - это такой плагин, который написан на чистом JavaScript, но также доступен как плагин jQuery.Он был разработан, чтобы предложить больше возможностей, чем другие плагины, с возможностью:

  • искать ключевые слова отдельно вместо полного термина
  • map diacritics (Например, если «justo» также долженmatch "justò")
  • игнорировать совпадения внутри пользовательских элементов
  • использовать пользовательский элемент подсветки
  • использовать пользовательский класс подсветки
  • отображать пользовательские синонимы
  • искать также внутри iframes
  • получать не найденные термины

DEMO

В качестве альтернативы вы можете увидеть this fiddle .

Пример использования :

// Highlight "keyword" in the specified context
$(".context").mark("keyword");

// Highlight the custom regular expression in the specified context
$(".context").markRegExp(/Lorem/gmi);

Это бесплатный и разработанный с открытым исходным кодом на GitHub ( проектный справочник ).

9 голосов
/ 04 апреля 2012
function stylizeHighlightedString() {

    var text = window.getSelection();

    // For diagnostics
    var start = text.anchorOffset;
    var end = text.focusOffset - text.anchorOffset;

    range = window.getSelection().getRangeAt(0);

    var selectionContents = range.extractContents();
    var span = document.createElement("span");

    span.appendChild(selectionContents);

    span.style.backgroundColor = "yellow";
    span.style.color = "black";

    range.insertNode(span);
}
4 голосов
/ 30 августа 2015

Простой пример TypeScript

ПРИМЕЧАНИЕ. Хотя я во многих отношениях согласен с @Stefan, мне потребовалась только простая подсветка матча:

module myApp.Search {
    'use strict';

    export class Utils {
        private static regexFlags = 'gi';
        private static wrapper = 'mark';

        private static wrap(match: string): string {
            return '<' + Utils.wrapper + '>' + match + '</' + Utils.wrapper + '>';
        }

        static highlightSearchTerm(term: string, searchResult: string): string {
            let regex = new RegExp(term, Utils.regexFlags);

            return searchResult.replace(regex, match => Utils.wrap(match));
        }
    }
}

А затем построим фактический результат:

module myApp.Search {
    'use strict';

    export class SearchResult {
        id: string;
        title: string;

        constructor(result, term?: string) {
            this.id = result.id;
            this.title = term ? Utils.highlightSearchTerm(term, result.title) : result.title;
        }
    }
}
4 голосов
/ 20 января 2015

Вот мое регулярное выражение для чистого JavaScript:

function highlight(text) {
    document.body.innerHTML = document.body.innerHTML.replace(
        new RegExp(text + '(?!([^<]+)?<)', 'gi'),
        '<b style="background-color:#ff0;font-size:100%">$&</b>'
    );
}
4 голосов
/ 19 октября 2012

У меня та же проблема, через запрос xmlhttp приходит куча текста. Этот текст в формате HTML. Мне нужно выделить каждый случай.

str='<img src="brown fox.jpg" title="The brown fox" />'
    +'<p>some text containing fox.</p>'

Проблема в том, что мне не нужно выделять текст в тегах. Например мне нужно выделить лису:

Теперь я могу заменить его на:

var word="fox";
word="(\\b"+ 
    word.replace(/([{}()[\]\\.?*+^$|=!:~-])/g, "\\$1")
        + "\\b)";
var r = new RegExp(word,"igm");
str.replace(r,"<span class='hl'>$1</span>")

Чтобы ответить на ваш вопрос: вы можете не указывать g в опциях регулярного выражения, и будет заменено только первое вхождение, но оно все еще остается в свойстве img src и уничтожает тег изображения:

<img src="brown <span class='hl'>fox</span>.jpg" title="The brown <span 
class='hl'>fox</span> />

Так я решил, но мне было интересно, есть ли лучший способ, который я пропустил в регулярных выражениях:

str='<img src="brown fox.jpg" title="The brown fox" />'
    +'<p>some text containing fox.</p>'
var word="fox";
word="(\\b"+ 
    word.replace(/([{}()[\]\\.?*+^$|=!:~-])/g, "\\$1")
    + "\\b)";
var r = new RegExp(word,"igm");
str.replace(/(>[^<]+<)/igm,function(a){
    return a.replace(r,"<span class='hl'>$1</span>");
});
2 голосов
/ 04 марта 2018

Ни одно из других решений не соответствует моим потребностям, и хотя решение Стефана Штайгера сработало так, как я ожидал, я нашел его слишком многословным.

Вот моя попытка:

/**
 * Highlight keywords inside a DOM element
 * @param {string} elem Element to search for keywords in
 * @param {string[]} keywords Keywords to highlight
 * @param {boolean} caseSensitive Differenciate between capital and lowercase letters
 * @param {string} cls Class to apply to the highlighted keyword
 */
function highlight(elem, keywords, caseSensitive = false, cls = 'highlight') {
  const flags = caseSensitive ? 'gi' : 'g';
  // Sort longer matches first to avoid
  // highlighting keywords within keywords.
  keywords.sort((a, b) => b.length - a.length);
  Array.from(elem.childNodes).forEach(child => {
    const keywordRegex = RegExp(keywords.join('|'), flags);
    if (child.nodeType !== 3) { // not a text node
      highlight(child, keywords, caseSensitive, cls);
    } else if (keywordRegex.test(child.textContent)) {
      const frag = document.createDocumentFragment();
      let lastIdx = 0;
      child.textContent.replace(keywordRegex, (match, idx) => {
        const part = document.createTextNode(child.textContent.slice(lastIdx, idx));
        const highlighted = document.createElement('span');
        highlighted.textContent = match;
        highlighted.classList.add(cls);
        frag.appendChild(part);
        frag.appendChild(highlighted);
        lastIdx = idx + match.length;
      });
      const end = document.createTextNode(child.textContent.slice(lastIdx));
      frag.appendChild(end);
      child.parentNode.replaceChild(frag, child);
    }
  });
}

// Highlight all keywords found in the page
highlight(document.body, ['lorem', 'amet', 'autem']);
.highlight {
  background: lightpink;
}
<p>Hello world lorem ipsum dolor sit amet, consectetur adipisicing elit. Est vel accusantium totam, ipsum delectus et dignissimos mollitia!</p>
<p>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Numquam, corporis.
  <small>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium autem voluptas perferendis dolores ducimus velit error voluptatem, qui rerum modi?</small>
</p>

Я бы также рекомендовал использовать что-то вроде escape-string-regexp , если ваши ключевые слова могут иметь специальные символы, которые необходимо экранировать в регулярных выражениях:

const keywordRegex = RegExp(keywords.map(escapeRegexp).join('|')), flags);
1 голос
/ 31 июля 2016

Мне тоже было интересно, что вы можете попробовать то, что я узнал в этом сообщении.

Я использовал:

function highlightSelection() {
			var userSelection = window.getSelection();
			for(var i = 0; i < userSelection.rangeCount; i++) {
				highlightRange(userSelection.getRangeAt(i));
			}
			
		}
			
			function highlightRange(range) {
			    var newNode = document.createElement("span");
			    newNode.setAttribute(
			       "style",
			       "background-color: yellow; display: inline;"
			    );
			    range.surroundContents(newNode);
			}
<html>
	<body contextmenu="mymenu">

		<menu type="context" id="mymenu">
			<menuitem label="Highlight Yellow" onclick="highlightSelection()" icon="/images/comment_icon.gif"></menuitem>
		</menu>
		<p>this is text, select and right click to high light me! if you can`t see the option, please use this<button onclick="highlightSelection()">button </button><p>

Вы также можете попробовать это здесь: http://henriquedonati.com/projects/Extension/extension.html

xc

1 голос
/ 05 февраля 2015

Начиная с HTML5, вы можете использовать теги <mark></mark> для выделения текста. Вы можете использовать javascript для переноса текста / ключевого слова между этими тегами. Вот небольшой пример того, как пометить и снять пометку с текста.

JSFIDDLE DEMO

...