Получить начальное и конечное смещение диапазона относительно его родительского контейнера - PullRequest
61 голосов
/ 27 января 2011

Предположим, у меня есть этот элемент HTML:

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

И пользователь выбирает "дом" с помощью мыши.

Я хочу иметь возможность определить, сколько символов в #parentего выбор начинается (и сколько символов с конца #parent его выбор заканчивается).Это должно работать, даже если он выбирает тег HTML.(И мне нужно, чтобы он работал во всех браузерах)

range.startOffset выглядит многообещающе, но это смещение относительно только непосредственного контейнера диапазона и является символьным смещением, только еслиКонтейнер - это текстовый узел.

Ответы [ 3 ]

161 голосов
/ 27 января 2011

UPDATE

Как указано в комментариях, мой исходный ответ (ниже) возвращает только конец выбора или позицию каретки. Довольно легко адаптировать код для возврата начального и конечного смещения; Вот пример, который делает это:

function getSelectionCharacterOffsetWithin(element) {
    var start = 0;
    var end = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.startContainer, range.startOffset);
            start = preCaretRange.toString().length;
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            end = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToStart", textRange);
        start = preCaretTextRange.text.length;
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        end = preCaretTextRange.text.length;
    }
    return { start: start, end: end };
}

function reportSelection() {
  var selOffsets = getSelectionCharacterOffsetWithin( document.getElementById("editor") );
  document.getElementById("selectionLog").innerHTML = "Selection offsets: " + selOffsets.start + ", " + selOffsets.end;
}

window.onload = function() {
  document.addEventListener("selectionchange", reportSelection, false);
  document.addEventListener("mouseup", reportSelection, false);
  document.addEventListener("mousedown", reportSelection, false);
  document.addEventListener("keyup", reportSelection, false);
};
#editor {
  padding: 5px;
  border: solid green 1px;
}
Select something in the content below:

<div id="editor" contenteditable="true">A <i>wombat</i> is a marsupial native to <b>Australia</b></div>
<div id="selectionLog"></div>

Вот функция, которая получит смещение символа каретки в пределах указанного элемента; однако это наивная реализация, которая почти наверняка будет иметь несоответствия с переносами строк и не будет пытаться справиться с текстом, скрытым с помощью CSS (я подозреваю, что IE будет правильно игнорировать такой текст, в то время как другие браузеры этого не сделают). Правильно обращаться со всем этим было бы сложно. Я попытался для моей Rangy библиотеки.

Живой пример: http://jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}
20 голосов
/ 19 сентября 2012

Я знаю, что это год, но этот пост является лучшим результатом поиска для множества вопросов по поиску позиции в карете, и я нашел это полезным.

Я пытался использовать превосходный сценарий Тима вышенайти новую позицию курсора после перетаскивания элемента из одной позиции в другую в редактируемом элементе содержимого.Он отлично работал в FF и IE, но в Chrome действие перетаскивания выделило весь контент между началом и концом перетаскивания, в результате чего возвращаемый caretOffset был слишком большим или маленьким (по длине выделенной области).

Я добавил несколько строк в первый оператор if, чтобы проверить, был ли выбран текст, и соответствующим образом скорректировать результат.Новое заявление ниже.Простите, если неуместно добавлять это здесь, поскольку это не то, что пытался сделать ОП, но, как я уже сказал, несколько поисков информации, связанной с позицией Карет, привели меня к этому посту, так что (надеюсь), вероятно, поможет кому-то еще.

Первый оператор Тима if с добавленными строками (*):

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  if(selected){ // *
    caretOffset = preCaretRange.toString().length - selected; // *
  } else { // *
    caretOffset = preCaretRange.toString().length; 
  } // *
}
4 голосов
/ 24 января 2019

После нескольких дней экспериментов я нашел подход, который выглядит многообещающим. Поскольку selectNodeContents() неправильно обрабатывает теги <br>, я написал собственный алгоритм для определения длины текста для каждого node внутри contenteditable. Например, для расчета В начале выбора я суммирую длину текста всех предыдущих узлов. Таким образом, я могу обрабатывать (несколько) разрывов строк:

var editor = null;
var output = null;

const getTextSelection = function (editor) {
    const selection = window.getSelection();

    if (selection != null && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);

        return {
            start: getTextLength(editor, range.startContainer, range.startOffset),
            end: getTextLength(editor, range.endContainer, range.endOffset)
        };
    } else
        return null;
}

const getTextLength = function (parent, node, offset) {
    var textLength = 0;

    if (node.nodeName == '#text')
        textLength += offset;
    else for (var i = 0; i < offset; i++)
        textLength += getNodeTextLength(node.childNodes[i]);

    if (node != parent)
        textLength += getTextLength(parent, node.parentNode, getNodeOffset(node));

    return textLength;
}

const getNodeTextLength = function (node) {
    var textLength = 0;

    if (node.nodeName == 'BR')
        textLength = 1;
    else if (node.nodeName == '#text')
        textLength = node.nodeValue.length;
    else if (node.childNodes != null)
        for (var i = 0; i < node.childNodes.length; i++)
            textLength += getNodeTextLength(node.childNodes[i]);

    return textLength;
}

const getNodeOffset = function (node) {
    return node == null ? -1 : 1 + getNodeOffset(node.previousSibling);
}

window.onload = function () {
    editor = document.querySelector('.editor');
    output = document.querySelector('#output');

    document.addEventListener('selectionchange', handleSelectionChange);
}

const handleSelectionChange = function () {
    if (isEditor(document.activeElement)) {
        const textSelection = getTextSelection(document.activeElement);

        if (textSelection != null) {
            const text = document.activeElement.innerText;
            const selection = text.slice(textSelection.start, textSelection.end);
            print(`Selection: [${selection}] (Start: ${textSelection.start}, End: ${textSelection.end})`);
        } else
            print('Selection is null!');
    } else
        print('Select some text above');
}

const isEditor = function (element) {
    return element != null && element.classList.contains('editor');
}

const print = function (message) {
    if (output != null)
        output.innerText = message;
    else
        console.log('output is null!');
}
* {
    font-family: 'Georgia', sans-serif;
    padding: 0;
    margin: 0;
}

body {
    margin: 16px;
}

.p {
    font-size: 16px;
    line-height: 24px;
    padding: 0 2px;
}

.editor {
    border: 1px solid #0000001e;
    border-radius: 2px;
    white-space: pre-wrap;
}

#output {
    margin-top: 16px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./script.js" async></script>
    <link href="./stylesheet.css" rel="stylesheet">
    <title>Caret Position</title>
</head>
<body>
    <p class="editor" contenteditable="true"><em>Write<br></em><br>some <br>awesome <b><em>text </em></b>here...</p>
    <p id="output">Select some text above</p>
</body>
</html>
...