Cómo resaltar texto usando javascript

Resuelto Ankit asked hace 12 años • 21 respuestas

¿Alguien puede ayudarme con una función de JavaScript que pueda resaltar texto en una página web? Y el requisito es resaltar solo una vez, no resaltar todas las apariciones del texto como lo hacemos en el caso de la búsqueda.

Ankit avatar Dec 27 '11 19:12 Ankit
Aceptado

Puede utilizar el efecto de resaltado jquery .

Pero si está interesado en el código javascript sin formato, eche un vistazo a lo que obtuve. Simplemente copie y pegue en un HTML, abra el archivo y haga clic en "resaltar"; esto debería resaltar la palabra "zorro". En cuanto al rendimiento, creo que esto sería suficiente para texto pequeño y una sola repetición (como usted especificó)

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>
Expandir fragmento

Ediciones:

Usandoreplace

Veo que esta respuesta ganó cierta popularidad, pensé que podría agregarla. También puedes usar reemplazar fácilmente

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

O para apariciones múltiples (no relevantes para la pregunta, pero se preguntó en los comentarios), simplemente agregue globalla expresión regular de reemplazo.

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

Reemplazo del HTML en toda la página web

Para reemplazar el HTML de una página web completa, debe consultar innerHTMLel cuerpo del documento.

document.body.innerHTML

guy mograbi avatar Dec 27 '2011 12:12 guy mograbi

Las soluciones que se ofrecen aquí son bastante malas.

  1. No puede usar expresiones regulares, porque de esa manera busca/resalta en las etiquetas html.
  2. No puede usar expresiones regulares porque no funciona correctamente con UTF* (cualquier cosa con caracteres no latinos/inglés).
  3. No puedes simplemente hacer un internalHTML.replace, porque esto no funciona cuando los caracteres tienen una notación HTML especial, por ejemplo &amp;, para &, &lt;para <, &gt;para >, &auml;para ä, &ouml;para ö , &uuml;para ü &szlig;, para ß, etc.

Qué necesitas hacer:

Recorra el documento HTML, busque todos los nodos de texto, obtenga textContent, obtenga la posición del texto resaltado con indexOf(con una opción toLowerCasesi no distingue entre mayúsculas y minúsculas), agregue todo lo anterior indexofcomo textNode, agregue el texto coincidente con un intervalo de resaltado, y repita para el resto del nodo de texto (la cadena resaltada puede aparecer varias veces en la textContentcadena).

Aquí está el código para esto:

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.substring(foundIndex, 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

};

Entonces puedes usarlo así:

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

Aquí hay un documento HTML de ejemplo.

<!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>

Por cierto, si busca en una base de datos con LIKE,
por ejemplo WHERE textField LIKE CONCAT('%', @query, '%'), [lo cual no debería hacer, debería usar la búsqueda de texto completo o Lucene], entonces puede escapar de cada carácter con \ y agregar una declaración de escape SQL, de esa manera Encontrarás caracteres especiales que son expresiones LIKE.

p.ej

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

y el valor de @query no es '%completed%'mas'%\c\o\m\p\l\e\t\e\d%'

(probado, funciona con SQL-Server y PostgreSQL, y cualquier otro sistema RDBMS que admita ESCAPE)


Una versión mecanografiada revisada:

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 

Uso:

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 avatar Apr 22 '2015 12:04 Stefan Steiger

Por qué utilizar una función de resaltado hecha por uno mismo es una mala idea

La razón por la que probablemente sea una mala idea empezar a crear su propia función de resaltado desde cero es porque seguramente se encontrará con problemas que otros ya han resuelto. Desafíos:

  • Necesitaría eliminar nodos de texto con elementos HTML para resaltar sus coincidencias sin destruir eventos DOM y desencadenar la regeneración de DOM una y otra vez (como sería el caso, por ejemplo innerHTML)
  • Si desea eliminar los elementos resaltados, deberá eliminar los elementos HTML con su contenido y también deberá combinar los nodos de texto divididos para realizar más búsquedas. Esto es necesario porque cada complemento de resaltado busca coincidencias dentro de los nodos de texto y, si sus palabras clave se dividen en varios nodos de texto, no se encontrarán.
  • También necesitarás crear pruebas para asegurarte de que tu complemento funcione en situaciones en las que no has pensado. ¡Y estoy hablando de pruebas entre navegadores!

¿Suena complicado? Si desea algunas funciones como ignorar algunos elementos del resaltado, mapeo de signos diacríticos, mapeo de sinónimos, búsqueda dentro de iframes, búsqueda de palabras separadas, etc., esto se vuelve cada vez más complicado.

Utilice un complemento existente

Cuando utiliza un complemento existente y bien implementado, no tiene que preocuparse por las cosas mencionadas anteriormente. El artículo 10 complementos de resaltado de texto de jQuery en Sitepoint compara los complementos de resaltado populares.

Eche un vistazo a mark.js

mark.js es un complemento escrito en JavaScript puro, pero también está disponible como complemento jQuery. Fue desarrollado para ofrecer más oportunidades que otros complementos con opciones para:

  • busque palabras clave por separado en lugar del término completo
  • signos diacríticos del mapa (por ejemplo, si "justo" también debe coincidir con "justò")
  • ignorar coincidencias dentro de elementos personalizados
  • utilizar elemento de resaltado personalizado
  • usar clase de resaltado personalizada
  • mapa sinónimos personalizados
  • buscar también dentro de iframes
  • recibir términos no encontrados

MANIFESTACIÓN

Alternativamente puedes ver este violín .

Ejemplo de uso :

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

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

Es gratuito y está desarrollado de código abierto en GitHub ( referencia del proyecto ).

Divulgación : soy el autor original de esta biblioteca.

dude avatar Jan 05 '2016 14:01 dude

Ninguna de las otras soluciones se ajusta realmente a mis necesidades y, aunque la solución de Stefan Steiger funcionó como esperaba, la encontré demasiado detallada.

El siguiente es mi intento:

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

También recomendaría usar algo como escape-string-regexp si sus palabras clave pueden tener caracteres especiales que deberían tener escape en las expresiones regulares:

const keywordRegex = RegExp(keywords.map(escapeRegexp).join('|')), flags);
elclanrs avatar Mar 04 '2018 04:03 elclanrs
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 avatar Apr 04 '2012 11:04 Mohit kumar