web-dev-qa-db-ja.com

JavaScriptを使用してテキスト範囲を強調表示する

開始位置と終了位置で示される特定のテキスト範囲を強調表示(CSSを適用)したいと思います。テキスト内には無視する必要のある他のタグがある可能性があるため、これは見かけよりも困難です。

例:

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

highlight(2, 6)は、タグを削除せずに_"cdef_ "を強調表示する必要があります。

TextRangeオブジェクトを既に使用しようとしましたが、成功しませんでした。

前もって感謝します!

38
Vincent

以下は、選択を特定の要素内の文字オフセットのペアに設定する関数です。これは単純な実装です。(CSSによって、または_<script>_または_<style>_要素内にあることによって)非表示にされる可能性のあるテキストを考慮せず、ブラウザーの不一致(IE改行を使用して、折りたたまれた空白(ページ上の1つの表示スペースに折りたたまれた2つ以上の連続したスペース文字など)を考慮しません。ただし、すべての主要なブラウザーの例では機能します。

他の部分、強調表示については、document.execCommand()を使用することをお勧めします。以下の関数を使用して選択を設定し、document.execCommand()を呼び出すことができます。コマンドを機能させるには、IE以外のブラウザーでドキュメントを一時的に編集可能にする必要があります。コードについては私の答えを参照してください: 複数のタグにわたるgetSelection&SurroundContents

以下に、すべての主要なブラウザーで動作する、全体を示すjsFiddleの例を示します。 http://jsfiddle.net/8mdX4/1211/

そして、選択設定コード:

_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();
    }
}
_
56
Tim Down

複数のDOM要素の選択をサポートするこの強力なJavaScriptユーティリティがどのように機能するかを見ることができます。

[〜#〜] masha [〜#〜](Mark&Shareの略)の興味深い部分をマークできるWebページのコンテンツと共有

http://mashajs.com/index_eng.html

GitHubにもあります https://github.com/SmartTeleMax/MaSha

Mobile SafariとIEでも動作します!

2
Nico Pernice

次の解決策はIEでは機能しません。そのためにはTextRangeオブジェクトなどを適用する必要があります。これは選択を使用してこれを実行するので、通常の場合にはHTMLを壊さないでください、例えば:

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

highlight(3,6);を使用

出力:

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

スパンの外側の最初の文字をemにラップし、次にspan内の残りを新しい文字にラップする方法に注意してください。文字3でそれを開き、文字6で終わるかのように、次のような無効なマークアップを与えます。

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

コード:

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

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

edit少なくとも私のFF4にはいくつかの問題があったようです

_s.modify("move", "backward", "documentboundary");
_

しかし同時に、それはそれなしで動作するようですので、私はそれを

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

editTimが指摘したように、変更はFF4以降でのみ使用できるため、変更を必要としない選択を取得するために別のアプローチを取りました。メソッドは、ブラウザの互換性を少し高めることを期待して(IEには独自のソリューションが必要です)。

コード:

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

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

0
Niklas

jQuery.highlight プラグインのアイデアに基づいています。

    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