Mettre en évidence la plage de texte en utilisant JavaScript

je voudrais mettre en évidence (appliquer css à) une certaine plage de texte, indiquée par sa position de début et de fin. C'est la chose plus difficile qu'il n'y paraît, puisqu'il peut y avoir d'autres balises dans le texte, qui doivent être ignorés.

exemple:

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

highlight(2, 6) doit mettre en surbrillance "cdef " sans enlever l'étiquette.

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

Merci d'avance!

32
demandé sur Niklas 2011-06-05 04:03:43

4 réponses

ci-dessous est une fonction pour définir la sélection à une paire de décalages de caractères à l'intérieur d'un élément particulier. Il s'agit d'une implémentation naïve: elle ne prend pas en compte les textes qui peuvent être rendus invisibles (par CSS ou en étant à l'intérieur d'un élément <script> ou <style> , par exemple) et peut avoir des divergences de navigateur (C'est-à-dire par rapport à tout le reste) avec des sauts de ligne, et ne prend pas en compte les espaces blancs effondrés (tels que 2 ou plusieurs caractères d'espace consécutifs s'effondrant à un espace visible sur la page). Cependant, il fonctionne pour votre exemple dans tous les navigateurs importants.

pour l'autre partie, la mise en évidence, je suggère d'utiliser document.execCommand() pour cela. Vous pouvez utiliser ma fonction ci-dessous pour définir la sélection et ensuite appeler document.execCommand() . Vous aurez besoin de rendre le document Temporairement modifiable dans les navigateurs non-IE pour que la commande fonctionne. Voir ma réponse ici pour le code: getselection & surroundContents à travers plusieurs tags

voici un exemple jsFiddle montrant l'ensemble de la chose, travaillant dans tous les principaux navigateurs: http://jsfiddle.net/8mdX4/1211 /

et le code de réglage de 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();
    }
}
50
répondu Tim Down 2017-05-23 12:17:56

vous pouvez jeter un oeil à la façon dont fonctionne ce puissant utilitaire JavaScript qui supporte la sélection sur plusieurs éléments DOM:

MASHA (abréviation de Mark & Share) vous permettent de marquer des parties intéressantes du contenu de la page web et de le partager

http://mashajs.com/index_eng.html

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

fonctionne même sur Safari Mobile et IE!

2
répondu yumyo 2012-07-06 15:45:23

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

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

avec highlight(3,6);

sorties:

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

prendre note de la façon dont il enveloppe le premier caractère en dehors de la portée dans un em , puis le reste dans le span dans un nouveau. Où comme si elle l'ouvrait juste au caractère 3 et finirait au caractère 6, elle donnerait un markup 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 des problèmes avec

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

mais en même temps, il semble travailler sans lui, donc je l'ai changé en

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

modifier comme Tim L'a souligné, modify 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 méthode modify, dans l'espoir de la rendre un peu plus compatible avec le navigateur (C'est-à-dire qu'il a encore 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
répondu Niklas 2011-06-05 11:04:58

basé sur les idées du jQuery.surligner 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
répondu Bill 2018-03-19 14:17:49