Выбранный Javascript текст

У меня есть html-страница с текстовым контентом. При выборе любого текста и нажатии кнопки выделения, я могу изменить стиль выделенного текста, чтобы выделить то же самое. Чтобы реализовать эту функцию, я написал следующий метод.

sel = window.getSelection();
var range = sel.getRangeAt(0);
var span = document.createElement('span');
span.className = "highlight" + color;
range.surroundContents(span);

Это отлично работает, если вы выберете текст без тега html, но когда текст имеет какой-либо тег html между ними, он дает ошибку

Не удалось выполнить "surroundContents" в "Range": диапазон частично выбрал нетекстовый node.

Как решить эту проблему. Можно ли выделить одно и то же отдельно для каждой части (разделенной тэгами html)?

Ответы

Ответ 1

if (window.getSelection) {
                var sel = window.getSelection();
                if (!sel) {
                    return;
                }
                var range = sel.getRangeAt(0);
                var start = range.startContainer;
                var end = range.endContainer;
                var commonAncestor = range.commonAncestorContainer;
                var nodes = [];
                var node;

                for (node = start.parentNode; node; node = node.parentNode){
                   var tempStr=node.nodeValue;
                   if(node.nodeValue!=null &&    tempStr.replace(/^\s+|\s+$/gm,'')!='')
                     nodes.push(node);
                   if (node == commonAncestor)
                     break;
                }
                nodes.reverse();

                for (node = start; node; node = getNextNode(node)){
                   var tempStr=node.nodeValue;
                   if(node.nodeValue!=null &&  tempStr.replace(/^\s+|\s+$/gm,'')!='')
                     nodes.push(node);
                   if (node == end)
                    break;
                }

                for(var i=0 ; i<nodes.length ; i++){

                   var sp1 = document.createElement("span");
                   sp1.setAttribute("class", "highlight"+color );
                   var sp1_content = document.createTextNode(nodes[i].nodeValue);
                   sp1.appendChild(sp1_content);
                   var parentNode = nodes[i].parentNode;
                   parentNode.replaceChild(sp1, nodes[i]);
                }
           }

Ответ 2

Смотрите Range.extractContents:

document.getElementById('execute').addEventListener('click', function() {
    var range = window.getSelection().getRangeAt(0),
        span = document.createElement('span');

    span.className = 'highlight';
    span.appendChild(range.extractContents());
    range.insertNode(span);
});
.highlight { background-color: yellow; }
<div id="test">
    Select any part of <b>this text and</b> then click 'Run'.
</div>

<button id="execute">Run</button>

Ответ 3

Вместо того, чтобы изобретать велосипед, я бы использовал Rangy возможности выделения.

Я разветкил скрипку RGraham created и создал новый скрипт, который показывает, как это работает. Вот как это делается:

var applier = rangy.createClassApplier("highlight");
var highlighter = rangy.createHighlighter();
highlighter.addClassApplier(applier);

document.getElementById('execute').addEventListener('click', function() {
    highlighter.removeAllHighlights();
    highlighter.highlightSelection("highlight");
});

Это создает ярлык, который будет устанавливать класс highlight на элементах, которые полностью находятся внутри выделения, и создавать промежутки с классом highlight по мере необходимости для элементов, которые охватывают выбор. Когда нажата кнопка с id execute, старые фокусы удаляются и применяются новые блики.

Функциональность ярлыка является частью выпуска Rangy, которые считаются "альфа". Тем не менее, Я уже несколько лет использую альфа-версии Rangy, но было очень редко, что я обнаружил проблему с моим приложением, чтобы я мог вернуться к Rangy. И несколько раз, когда я обнаружил проблему с Rangy, Тим Даун (ее автор) был довольно отзывчив.

Ответ 4

попробуйте следующее:

newNode.appendChild(range.extractContents())

в соответствии с MDN:

Частично выбранные узлы клонируются для включения родительских тегов необходимо сделать документ фрагмента действительным.

В то время как Range.surroundContents:

Исключение будет выдано, однако, если Range разбивает нетекстовый node только с одной из ее граничных точек. То есть, в отличие от альтернатива выше, если есть частично выбранные узлы, они будут не клонироваться, и вместо этого операция завершится неудачно.

Не тестировалось, но...

Ответ 5

Это решение немного сложно, но я считаю, что этого будет достаточно

Когда вы увидите внимательно объект выделения, который мы получаем через вызов window.getSelection(). getRangeAt (0)

Вы увидите, что есть 4 свойства: startContainer startOffset endContainer endOffset

Итак, теперь вам нужно начать с startContainer с помощью startOffset и начать размещать необходимые узлы диапазона. Если теперь endContainer отличается от node, тогда вам нужно начать перемещение узлов от startContainer до endContainer Для прохождения вам нужно проверить дочерние узлы и узлы-узлы, которые вы можете получить из объектов DOM, Итак, сначала запустите startContainer, пройдите через все его дочерние элементы и проверьте, не является ли дочерний элемент node встроенным элементом, а затем примените тег span вокруг него, а затем вам нужно написать небольшое кодирование для различных угловых случаев

Ответ 6

Решение действительно сложно. Я как-то нашел обходное решение. См. Мой fiddle

function highlight() {
    var range = window.getSelection().getRangeAt(0),
        parent = range.commonAncestorContainer,
        start = range.startContainer,
        end = range.endContainer;
    var startDOM = (start.parentElement == parent) ? start.nextSibling : start.parentElement;
    var currentDOM = startDOM.nextElementSibling;
    var endDOM = (end.parentElement == parent) ? end : end.parentElement;
    //Process Start Element
    highlightText(startDOM, 'START', range.startOffset);
    while (currentDOM != endDOM && currentDOM != null) {
        highlightText(currentDOM);
        currentDOM = currentDOM.nextElementSibling;
    }
    //Process End Element
    highlightText(endDOM, 'END', range.endOffset);
}

function highlightText(elem, offsetType, idx) {
    if (elem.nodeType == 3) {
        var span = document.createElement('span');
        span.setAttribute('class', 'highlight');
        var origText = elem.textContent, text, prevText, nextText;
        if (offsetType == 'START') {
            text = origText.substring(idx);
            prevText = origText.substring(0, idx);
        } else if (offsetType == 'END') {
            text = origText.substring(0, idx);
            nextText = origText.substring(idx);
        } else {
            text = origText;
        }
        span.textContent = text;

        var parent = elem.parentElement;
        parent.replaceChild(span, elem);
        if (prevText) { 
            var prevDOM = document.createTextNode(prevText);
            parent.insertBefore(prevDOM, span);
        }
        if (nextText) {
            var nextDOM = document.createTextNode(nextText);
            parent.appendChild(nextDOM);
        }
        return;
    }
    var childCount = elem.childNodes.length;
    for (var i = 0; i < childCount; i++) {
        if (offsetType == 'START' && i == 0) 
            highlightText(elem.childNodes[i], 'START', idx);
        else if (offsetType == 'END' && i == childCount - 1)
            highlightText(elem.childNodes[i], 'END', idx);
        else
            highlightText(elem.childNodes[i]);
    }
}