web-dev-qa-db-fra.com

Surligner la plage de texte en utilisant JavaScript

Je voudrais mettre en évidence (appliquer css à) une certaine plage de texte, indiquée par ses positions de début et de fin. C'est plus difficile qu'il n'y parait, car il peut y avoir d'autres balises dans le texte qui doivent être ignorées.

Exemple:

<div>abcd<em>efg</em>hij</div>

highlight(2, 6) doit mettre en surbrillance "cdef "sans supprimer le tag.

J'ai déjà essayé d'utiliser un objet TextRange, mais sans succès.

Merci d'avance!

36
Vincent

Vous trouverez ci-dessous une fonction permettant de définir la sélection sur une paire de décalages de caractères dans un élément particulier. Ceci est une implémentation naïve: elle ne prend en compte aucun texte pouvant être rendu invisible (par exemple par CSS ou en étant dans un élément <script> ou <style>, par exemple) et peut présenter des anomalies de navigateur (IE par rapport à tout le reste) et ne prend pas en compte les espaces réduits (tels que deux ou plusieurs caractères d'espace consécutifs se réduisant à un espace visible de la page). Cependant, cela fonctionne pour votre exemple dans tous les principaux navigateurs.

Pour l’autre partie, la surbrillance, je suggérerais d’utiliser document.execCommand() pour cela. Vous pouvez utiliser ma fonction ci-dessous pour définir la sélection, puis appeler document.execCommand(). Pour que la commande fonctionne, vous devez rendre le document modifiable temporairement dans des navigateurs autres qu'IE. Voir ma réponse ici pour le code: getSelection & SurroundContents entre plusieurs balises

Voici un exemple jsFiddle montrant le tout, fonctionnant dans tous les principaux navigateurs: http://jsfiddle.net/8mdX4/1211/

Et le code de réglage de la sélection:

function getTextNodesIn(node) {
    var textNodes = [];
    if (node.nodeType == 3) {
        textNodes.Push(node);
    } else {
        var children = node.childNodes;
        for (var i = 0, len = children.length; i < len; ++i) {
            textNodes.Push.apply(textNodes, getTextNodesIn(children[i]));
        }
    }
    return textNodes;
}

function setSelectionRange(el, start, end) {
    if (document.createRange && window.getSelection) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var textNodes = getTextNodesIn(el);
        var foundStart = false;
        var charCount = 0, endCharCount;

        for (var i = 0, textNode; textNode = textNodes[i++]; ) {
            endCharCount = charCount + textNode.length;
            if (!foundStart && start >= charCount
                    && (start < endCharCount ||
                    (start == endCharCount && i <= textNodes.length))) {
                range.setStart(textNode, start - charCount);
                foundStart = true;
            }
            if (foundStart && end <= endCharCount) {
                range.setEnd(textNode, end - charCount);
                break;
            }
            charCount = endCharCount;
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (document.selection && document.body.createTextRange) {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(true);
        textRange.moveEnd("character", end);
        textRange.moveStart("character", start);
        textRange.select();
    }
}
52
Tim Down

Vous pouvez voir comment fonctionne ce puissant utilitaire JavaScript qui prend en charge la sélection de plusieurs éléments DOM:

MASHA (abréviation de Mark & ​​Share) permet de marquer et de partager des parties intéressantes du contenu d'une page Web}

http://mashajs.com/index_eng.html

C'est aussi sur GitHub https://github.com/SmartTeleMax/MaSha

Fonctionne même sur Mobile Safari et IE! 

2
yumyo

La solution suivante ne fonctionne pas pour IE, vous devrez appliquer des objets TextRange, etc. pour cela. Comme cela utilise des sélections pour effectuer cela, il ne faut pas casser le code HTML dans des cas normaux, par exemple:

<div>abcd<span>efg</span>hij</div>

Avec highlight(3,6);

les sorties:

<div>abc<em>d<span>ef</span></em><span>g</span>hij</div>

Notez comment il enveloppe le premier caractère en dehors de la plage dans un em, puis le reste dans le span dans un nouveau. Où comme si cela ouvrait simplement le caractère 3 et se terminait au caractère 6, cela donnerait un balisage invalide comme:

<div>abc<em>d<span>ef</em>g</span>hij</div>

Le code:

var r = document.createRange();
var s = window.getSelection()

r.selectNode($('div')[0]);
s.removeAllRanges();
s.addRange(r);

// not quite sure why firefox has problems with this
if ($.browser.webkit) {
    s.modify("move", "backward", "documentboundary");
}

function highlight(start,end){
    for(var st=0;st<start;st++){
        s.modify("move", "forward", "character");
    }

    for(var st=0;st<(end-start);st++){
        s.modify("extend", "forward", "character");
    }
}

highlight(2,6);

var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents()); 
ra.insertNode(newNode);

Exemple: http://jsfiddle.net/niklasvh/4NDb9/

edit On dirait qu'au moins mon FF4 a eu quelques problèmes avec

s.modify("move", "backward", "documentboundary");

mais en même temps, il semble fonctionner sans, alors je l'ai changé pour

if ($.browser.webkit) {
        s.modify("move", "backward", "documentboundary");
}

éditer comme indiqué par Tim, la modification n'est disponible qu'à partir de FF4, j'ai donc adopté une approche différente pour obtenir la sélection, qui n'a pas besoin de la modification. méthode, dans l’espoir de le rendre un peu plus compatible avec les navigateurs (IE a toujours besoin de sa propre solution).

Le code:

var r = document.createRange();
var s = window.getSelection()

var pos = 0;

function Dig(el){
    $(el).contents().each(function(i,e){
        if (e.nodeType==1){
            // not a textnode
         Dig(e);   
        }else{
            if (pos<start){
               if (pos+e.length>=start){
                range.setStart(e, start-pos);
               }
            }

            if (pos<end){
               if (pos+e.length>=end){
                range.setEnd(e, end-pos);
               }
            }            

            pos = pos+e.length;
        }
    });  
}
var start,end, range;

function highlight(element,st,en){
    range = document.createRange();
    start = st;
    end = en;
    Dig(element);
    s.addRange(range);

}
highlight($('div'),3,6);

var ra = s.getRangeAt(0);

var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents()); 
ra.insertNode(newNode);

exemple: http://jsfiddle.net/niklasvh/4NDb9/

0
Niklas

Basé sur les idées du jQuery.highlight plugin.

    private highlightRange(selector: JQuery, start: number, end: number): void {
        let cur = 0;
        let replacements: { node: Text; pos: number; len: number }[] = [];

        let Dig = function (node: Node): void {
            if (node.nodeType === 3) {
                let nodeLen = (node as Text).data.length;
                let next = cur + nodeLen;
                if (next > start && cur < end) {
                    let pos = cur >= start ? cur : start;
                    let len = (next < end ? next : end) - pos;
                    if (len > 0) {
                        if (!(pos === cur && len === nodeLen && node.parentNode &&
                            node.parentNode.childNodes && node.parentNode.childNodes.length === 1 &&
                            (node.parentNode as Element).tagName === 'SPAN' && (node.parentNode as Element).className === 'highlight1')) {

                            replacements.Push({
                                node: node as Text,
                                pos: pos - cur,
                                len: len,
                            });
                        }
                    }
                }
                cur = next;
            }
            else if (node.nodeType === 1) {
                let childNodes = node.childNodes;
                if (childNodes && childNodes.length) {
                    for (let i = 0; i < childNodes.length; i++) {
                        Dig(childNodes[i]);
                        if (cur >= end) {
                            break;
                        }
                    }
                }
            }
        };

        selector.each(function (index, element): void {
            Dig(element);
        });

        for (let i = 0; i < replacements.length; i++) {
            let replacement = replacements[i];
            let highlight = document.createElement('span');
            highlight.className = 'highlight1';
            let wordNode = replacement.node.splitText(replacement.pos);
            wordNode.splitText(replacement.len);
            let wordClone = wordNode.cloneNode(true);
            highlight.appendChild(wordClone);
            wordNode.parentNode.replaceChild(highlight, wordNode);
        }
    }
0
Bill