Jak wyróżnić tekst za pomocą javascript

103

Czy ktoś może mi pomóc z funkcją javascript, która może podświetlać tekst na stronie internetowej. A wymaganiem jest - zaznaczenie tylko raz, a nie zaznaczanie wszystkich wystąpień tekstu, jak robimy to w przypadku wyszukiwania.

Ankit
źródło
4
Jeśli prześlesz kod funkcji, będziemy w stanie pomóc. Jeśli poprosisz nas o utworzenie takiej funkcji dla Ciebie ... to jest mniej prawdopodobne. Musisz coś zrobić sam. Zacznij coś robić i wróć, gdy utkniesz.
Felix Kling
7
TAK Przeczytałem Jak zapytać i zrobiłem coś sam, ale utknąłem i dlatego zapytałem. Pracuję na Androidzie i mam niewielką wiedzę na temat javasript, dlatego nie jestem w stanie zrobić tego samodzielnie. Wcześniej używałem innego javascript, który działał, ale nie bez pewnych ograniczeń. Mogłem nie użyć właściwych słów, zadając to pytanie i przepraszam za to, ale proszę nie myśleć inaczej.
Ankit,
1
Ta wtyczka może Cię zainteresować: github.com/julmot/jmHighlight . Może wyróżniać słowa kluczowe osobno lub jako termin, może podświetlić dopasowanie za pomocą elementu niestandardowego i nazwy klasy, a także może wyszukiwać znaki diakrytyczne. Ponadto pozwala filtrować kontekst, w którym można szukać dopasowań.
koleś
1
Do kasy zgodnie z wyrażeniem regularnym ... stackoverflow.com/a/45519242/2792959
Przygotowałem artykuł na ten temat tutaj, exhesham.com/2017/11/20/ ...
Hesham Yassin

Odpowiedzi:

106

Możesz użyć efektu podświetlenia jQuery .

Ale jeśli jesteś zainteresowany surowym kodem javascript, spójrz na to, co mam. Po prostu skopiuj wklej do HTML, otwórz plik i kliknij "podświetl" - powinno to podświetlić słowo "lis". Pod względem wydajności myślę, że wystarczyłoby to dla małego tekstu i pojedynczego powtórzenia (tak jak określiłeś)

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>

Edycje:

Za pomocą replace

Widzę, że ta odpowiedź zyskała popularność, pomyślałem, że mógłbym ją dodać. Możesz także łatwo skorzystać z wymiany

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

Lub w przypadku wielu wystąpień (nie dotyczy pytania, ale zadano w komentarzach), po prostu dodaj globalwyrażenie regularne zastępowania.

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

Mam nadzieję, że to pomoże zaintrygowanym komentatorom.

Zastąpienie kodu HTML całej strony internetowej

Aby zamienić kod HTML na całą stronę internetową, należy odwołać się do treści innerHTMLdokumentu.

document.body.innerHTML

facet mograbi
źródło
Bardzo dziękuję za odpowiedź, ale czy możesz mi również powiedzieć, jak określić kolor w samym javascript
Ankit
Można wymienić "<span class='highlight'>"z "<span style='color: " + color + ";'>", kolor powinien być coś podobnegovar color = "#ff0000";
Yaniro
a co jeśli chcę zaznaczyć wszystkie wystąpienia słowa na całej stronie? @guy mograbi
Naqvi
4
Używanie prostego „zamiany” to zły pomysł . Opisałem dlaczego tutaj: stackoverflow.com/a/32758672/3894981
koleś
2
To nie jest dobry pomysł, ponieważ będzie to próba wyróżnienia znaczników / atrybutów HTML / itp. Na przykład, co by się stało w przypadku: <img src="fox.jpg" /> Otrzymasz nieprawidłowy kod HTML, który będzie wyglądał następująco: <img src="<span class='highlight'>fox</span>.jpg" /> Niezbyt dobry
dcporter7
51

Oferowane tutaj rozwiązania są dość złe.

  1. Nie możesz używać wyrażenia regularnego, ponieważ w ten sposób wyszukujesz / zaznaczasz w tagach HTML.
  2. Nie możesz użyć wyrażenia regularnego, ponieważ nie działa poprawnie z UTF * (nic ze znakami innymi niż łacińskie / angielskie).
  3. Nie możesz po prostu wykonać innerHTML.replace, ponieważ to nie działa, gdy znaki mają specjalną notację HTML, np. &amp;Dla &, &lt;dla <, &gt;dla>, &auml;dla ä, &ouml;dla ö &uuml;dla ü &szlig;dla ß itp.

Co musisz zrobić:

Zapętlaj się po dokumencie HTML, znajdź wszystkie węzły tekstowe, pobierz textContent, uzyskaj położenie podświetlonego tekstu za pomocą indexOf(z opcjonalnym, toLowerCasejeśli nie ma rozróżniania wielkości liter), dołącz wszystko wcześniej indexofjako textNode, dołącz dopasowany tekst z podświetleniem, i powtórz dla pozostałej części węzła tekstowego (ciąg podświetlenia może wystąpić w textContentciągu wielokrotnie ).

Oto kod:

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

};

Następnie możesz go użyć w ten sposób:

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

Oto przykładowy dokument 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>

Nawiasem mówiąc, jeśli szukać w bazie danych z LIKE,
np WHERE textField LIKE CONCAT('%', @query, '%')[czego nie powinien robić, trzeba zastosować pełny-przeszukania lub Lucene], a następnie można uciec każdy znak z \ i dodaj SQL-Escape-oświadczenie, w ten sposób znajdziesz znaki specjalne, które są wyrażeniami LIKE.

na przykład

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

a wartość @query nie jest '%completed%'ale'%\c\o\m\p\l\e\t\e\d%'

(przetestowany, działa z SQL-Server i PostgreSQL oraz każdym innym systemem RDBMS obsługującym ESCAPE)


Poprawiona wersja maszynopisu:

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 

Stosowanie:

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
Stefan Steiger
źródło
Świetna odpowiedź. Metoda wygląda na przesadną, ale zwięzła! Będzie zdecydowanie zainteresowany w tym test prędkości z tej metody, jak w moim przypadku wyniki są leniwi załadowana do DOM (jak tam MOŻE być tysiące wyników), ciekawy, czy ta metoda będzie dodać dużą latencję do leniwego obciążeniem.
Pogrindis
6
Przepraszamy, ale żaden z twoich argumentów nie jest prawdziwy. 1. Absolutnie możesz użyć RegExp, po prostu nie powinieneś szukać wewnątrz wartości HTML, ale wartość tekstową elementu. 2. Możesz absolutnie używać znaków diakrytycznych z RegExp, jak realizowane w mark.js . 3. Notacje HTML zostaną przekonwertowane na rzeczywiste znaki w DOM przeglądarki, więc absolutnie ich używasz!
koleś
1
@julmot; Do 1: Co oznacza, że ​​musisz iterować przez każdy element, co jest dokładnie tym, co robię. Chyba że nie zależy ci na utracie formatowania, w takim przypadku możesz przeszukać document.body.innerText, co będzie dość powolne. 3. Nie w DOM, ale we właściwości innerText lub textContent elementu tekstowego. Co znowu oznacza, że ​​musisz iterować przez elementy tekstowe; nie można zrobić za pomocą regEx AFAIK. 2: Nie wiem mark.js, ale unikałbym wszystkiego, co robi jQuery.each, ponieważ jest to cholernie wolne.
Stefan Steiger
1
@StefanSteiger 1. Następnie powinieneś poprawić relację decyzji, ponieważ mówi, że nie możemy w ogóle wyszukiwać za pomocą RegExp, co nie jest prawdą 2. Nie używa jQuery.each. Dlaczego tak sądzisz? 3. To nieprawda, przynajmniej w przeglądarce Firefox. &auml;np. zostanie przekonwertowany na właściwy znak, nawet jeśli jest używany innerHTML.
koleś,
1
Cześć @StefanSteiger Właściwie używam twoich rozwiązań. Ten jest doskonały. Ale jest pewien problem, taki jak, jeśli II ma P, w którym są dwa przęsła, a jeden zakres ma dane takie jak Dyplom MSBTE, a drugi zakres ma dane 2012. Teraz jeśli ciąg, który chcę podświetlić to Diploma MSBTE 2012, cały ten ciąg sprawdziłem, że nie działa.Jeśli wszystko, co ma być dopasowane, jest obecne w jednym zakresie, to działa, ale jeśli zawartość tekstu jest w tagach różnicowych to To nie działa. Czy możesz coś o tym powiedzieć?
ganeshk
42

Dlaczego używanie własnej funkcji podświetlania to zły pomysł

Powodem, dla którego prawdopodobnie rozpoczęcie tworzenia własnej funkcji podświetlania od zera jest prawdopodobnie zły pomysł, jest to, że z pewnością napotkasz problemy, które inni już rozwiązali. Wyzwania:

  • Będziesz musiał usunąć węzły tekstowe z elementami HTML, aby podświetlić swoje dopasowania bez niszczenia zdarzeń DOM i ciągłego uruchamiania regeneracji DOM (co byłoby w przypadku np. innerHTML)
  • Jeśli chcesz usunąć podświetlone elementy, będziesz musiał usunąć elementy HTML z ich zawartością, a także połączyć podzielone węzły tekstowe do dalszych wyszukiwań. Jest to konieczne, ponieważ każda wtyczka zakreślacza wyszukuje dopasowania wewnątrz węzłów tekstowych, a jeśli słowa kluczowe zostaną podzielone na kilka węzłów tekstowych, nie zostaną znalezione.
  • Będziesz także musiał zbudować testy, aby upewnić się, że wtyczka działa w sytuacjach, o których nie pomyślałeś. A ja mówię o testach w różnych przeglądarkach!

Brzmi skomplikowanie? Jeśli potrzebujesz funkcji, takich jak ignorowanie niektórych elementów z podświetlania, mapowanie znaków diakrytycznych, mapowanie synonimów, wyszukiwanie w ramkach iframe, wyszukiwanie oddzielnych słów itp., Staje się to coraz bardziej skomplikowane.

Użyj istniejącej wtyczki

Korzystając z istniejącej, dobrze zaimplementowanej wtyczki, nie musisz martwić się o powyższe rzeczy. Artykuł 10 wtyczek wyróżniających tekst jQuery w witrynie Sitepoint porównuje popularne wtyczki wyróżniające.

Spójrz na mark.js

mark.js to taka wtyczka, która jest napisana w czystym JavaScript, ale jest również dostępna jako wtyczka jQuery. Został opracowany, aby oferować więcej możliwości niż inne wtyczki z opcjami:

  • szukaj słów kluczowych oddzielnie zamiast całego terminu
  • odwzorowanie znaków diakrytycznych (na przykład jeśli „justo” powinno również pasować do „justò”)
  • ignoruj ​​dopasowania wewnątrz niestandardowych elementów
  • użyj niestandardowego elementu podświetlającego
  • użyj niestandardowej klasy podświetlania
  • mapować własne synonimy
  • szukaj także w ramkach iframe
  • otrzymać nie znalezione terminy

PRÓBNY

Alternatywnie możesz zobaczyć te skrzypce .

Przykład użycia :

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

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

Jest darmowy i opracowany jako open-source na GitHub ( odniesienie do projektu ).

koleś
źródło
4
Samo wyróżnianie tekstu nie jest wystarczającym powodem, aby dołączyć jQuery.
Roy
10
@Roy Wziąłem to sobie do serca. Dobra wiadomość, od wersji 6.0.0 mark.js zrezygnował z zależności jQuery i teraz opcjonalnie używa go jako wtyczki jQuery.
koleś
Wszystkie prawdziwe, z wyjątkiem: 1. punkt nie jest możliwy, ponieważ nie możesz pobrać zarejestrowanych programów obsługi zdarzeń, a nawet gdybyś mógł, nie możesz ustawić funkcji anonimowych ... 2.: mark.js również nie znajduje tekstu między dwoma tagami, np. <span> s </span> ed nie znajdzie sed ... Po trzecie: gdy pojawi się przeglądarka (w tym nowa wersja), której jeszcze nie przetestowałeś, może się zepsuć. To zawsze prawda, bez względu na to, ile testów napiszesz. Przy 17 kb liczba ocen jest za duża jak na to, co robi.
Stefan Steiger,
Jakie punkty odnosisz do @StefanSteiger? Nie mogę powiedzieć nic o pierwszym punkcie bez tych informacji. Jednak drugi komentarz jest błędny, mark.js może znaleźć dopasowania między tagami, używając acrossElementsopcji. I do trzeciego komentarza; mark.js nie jest duży w porównaniu z funkcjami, które oferuje. I nie, jest mało prawdopodobne, że coś się zepsuje w przyszłości, ponieważ mark.js był testowany np. Uruchamiając Chrome 30 i we wszystkich nowszych wersjach z testami jednostkowymi między przeglądarkami i nigdy nie było żadnych problemów z nadchodzącymi wersjami.
koleś,
@dude: Trzy punkty po pierwszym akapicie. Ach, ok, brakuje tej opcji w wersji demonstracyjnej, którą oglądałem. W takim przypadku może to mieć jakiś sens. Mimo to uważam, że jest za duży.
Stefan Steiger
10
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);
}
Mohit kumar
źródło
3
Mohit, witamy w SO. Przydałby się opis kodu!
Nippey
czy nie powinien istnieć sposób zaznaczania tekstu bez tworzenia kolejnego węzła?
Dave Gregory
@ user191433 pytanie dotyczy nie tylko zaznaczania tekstu, ale także stosowania stylów. Do tego potrzebny jest węzeł.
Christophe
Przypomnienie / wskazówka, że ​​JavaScript span.style.backgroundColor = "yellow";przekłada się na CSS style="background-color: yellow;"- ta subtelna różnica między camelCase a notacją przerywaną na początku mnie zaskoczyła.
MarkHu
1
Odpowiedź PS Mohita na stackoverflow.com/questions/7991474/… jest bardziej uproszczoną wersją tego kodu. (na przykład pomijając zmienne początkowe i końcowe, które są tutaj wyłącznie diagnostyczne / niefunkcjonalne.)
MarkHu
8

Oto moje rozwiązanie wyrażenia regularnego w czystym języku JavaScript:

function highlight(text) {
    document.body.innerHTML = document.body.innerHTML.replace(
        new RegExp(text + '(?!([^<]+)?<)', 'gi'),
        '<b style="background-color:#ff0;font-size:100%">$&</b>'
    );
}
Klemen Tušar
źródło
Działa to doskonale, gdy blok tekstu, który próbuję zaznaczyć, zawiera znaczniki HTML.
John Chapman
Możesz również dostosować funkcję, aby akceptowała wiele słów za pomocą symbolu potoku one|two|three
wyrażenia regularnego
Nie zastąpi tekstu, jeśli koniec tekstu ma >znak. Zmodyfikuj wyrażenie regularne, używając (?!([^<]+)?<), aby działało.
Archie Reyes
Zmodyfikowano zgodnie z żądaniem.
Klemen Tušar
Idealny! To jest dla mnie najlepsze
marco burrometo
6

Żadne z innych rozwiązań nie odpowiadało moim potrzebom i chociaż rozwiązanie Stefana Steigera działało tak, jak się spodziewałem, uznałem je za zbyt rozwlekłe.

Oto moja próba:

/**
 * 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>

Zalecałbym również użycie czegoś takiego jak escape-string-regexp, jeśli słowa kluczowe mogą zawierać znaki specjalne, które musiałyby zostać zmienione w wyrażeniach regularnych:

const keywordRegex = RegExp(keywords.map(escapeRegexp).join('|')), flags);
elclanrs
źródło
To działało dobrze, ale potrzebuje też sposobu na „
odznaczenie
5

Mam ten sam problem, mnóstwo tekstu przychodzi przez żądanie xmlhttp. Ten tekst jest w formacie HTML. Muszę podkreślić każde wystąpienie.

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

Problem w tym, że nie muszę wyróżniać tekstu w tagach. Na przykład muszę zaznaczyć lisa:

Teraz mogę go zastąpić:

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

Odpowiadając na pytanie: możesz pominąć g w opcjach wyrażenia regularnego i tylko pierwsze wystąpienie zostanie zastąpione, ale nadal jest to to we właściwości img src i niszczy tag obrazu:

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

Oto sposób, w jaki to rozwiązałem, ale zastanawiałem się, czy istnieje lepszy sposób, coś, czego brakuje mi w wyrażeniach regularnych:

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>");
});
HMR
źródło
Było to jedyne rozwiązanie regex, które działało dla mnie bez mieszania się z <img src="word">lub <a href="word">.
yvesmancera
1
Złota zasada: nigdy. Posługiwać się. Regularny. Wyrażenia. Do. Bałagan. O. Z. XML.
ScottMcGready
4

Prosty przykład języka TypeScript

UWAGA: Chociaż zgadzam się z @Stefanem w wielu sprawach, potrzebowałem tylko prostego podkreślenia dopasowania:

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));
        }
    }
}

A następnie konstruowanie rzeczywistego wyniku:

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;
        }
    }
}
Slavo Vojacek
źródło
3

Od HTML5 możesz używać <mark></mark>tagów do podświetlania tekstu. Możesz użyć javascript do zawijania tekstu / słów kluczowych między tymi tagami. Oto mały przykład zaznaczania i odznaczania tekstu.

JSFIDDLE DEMO

kasper Taeymans
źródło
innerHTMLjest niebezpieczny. Usunie wydarzenia.
koleś
2
To również nie działa poprawnie, ponieważ na przykład, jeśli wprowadzisz do JSFIDDLE "Lorem", oznacza to tylko pierwsze jego wystąpienie.
agm1984,
1
Wel, wystarczy zastąpić wszystkie wystąpienia słowa kluczowego. tutaj jest przykład z wyrażeniem regularnym globalnie jsfiddle.net/de5q704L/73
kasper Taeymans
2

Przewiń do 2019 r., Web API ma teraz natywną obsługę podświetlania tekstów:

const selection = document.getSelection();
selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);

I jesteś gotowy! anchorNodejest węzłem początkowym wyboru, focusNodejest węzłem końcowym wyboru. A jeśli są to węzły tekstowe, offsetjest indeksem początkowego i końcowego znaku w odpowiednich węzłach. Oto dokumentacja

Mają nawet demo na żywo

Jack Guo
źródło
och, to jest genialne. po prostu użyj go w ten sposób: selection.setBaseAndExtent (allowedNode, 0, pożądaneNode, 1); aby podświetlić jedyny potrzebny węzeł. i działa z Gutenbergiem
tonyAndr
1

Też się zastanawiałem, możesz spróbować tego, czego się nauczyłem w tym poście.

Użyłem:

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>

możesz też spróbować tutaj: http://henriquedonati.com/projects/Extension/extension.html

xc

Henrique Donati
źródło
0

Jeśli chcesz, aby był on również wyróżniany podczas ładowania strony, jest nowy sposób.

poprostu dodaj #:~:text=Highlight%20These

spróbuj uzyskać dostęp do tego łącza

/programming/38588721#:~:text=Highlight%20a%20text

Jovylle Bermudez
źródło
-1

Użycie metody surroundContents () w typie Range . Jej jedynym argumentem jest element, który zawinie ten Range.

function styleSelected() {
  bg = document.createElement("span");
  bg.style.backgroundColor = "yellow";
  window.getSelection().getRangeAt(0).surroundContents(bg);
}
arhoskins
źródło