Получить позицию курсора (курсора) в области contentEditable, содержащей HTML-контент

У меня есть элемент contentEditable (может быть p, div, ...), и я хотел бы получить в нем позицию курсора (курсора). Обычно я могу добиться этого с помощью этого фрагмента кода:

var position = window.getSelection().getRangeAt(0).startOffset;

Это отлично работает, пока элемент содержит только текст. Но когда элемент содержит некоторое форматирование HTML, возвращаемая позиция относительно позиции курсора внутри включенного HTML-элемента.

Предположим, что содержимое элемента contentEditable таково:

AB<b>CD</b>EF

Если курсор находится внутри <b></b>, скажем между C и D, возвращенная позиция с приведенным выше кодом равна 1 вместо 3 (отсчитывается от начала содержимого элемента contentEditable)

Может ли кто-нибудь решить эту проблему?


person Frodik    schedule 22.01.2011    source источник
comment
Если вам нужно смещение символа в редактируемом элементе, могу я спросить, почему? Вероятно, есть лучший способ добиться того, чего вы хотите.   -  person Tim Down    schedule 23.01.2011
comment
У меня есть собственный редактор WYSIWYG, и он намеренно ведет себя немного иначе, чем все обычные редакторы. Для каждого <p> разрешено contentEditable. Теперь я пытаюсь решить проблему, когда пользователь хочет перейти от одного абзаца к другому, просто используя клавиши со стрелками. Поэтому мне нужно определить, где в абзаце находится курсор, чтобы я мог переместить его в соответствии с нажатой клавишей со стрелкой.   -  person Frodik    schedule 23.01.2011


Ответы (3)


ОБНОВИТЬ

Я написал более простую версию, которая также работает в IE ‹9:

https://stackoverflow.com/a/4812022/96100

Старый ответ

На самом деле это более полезный результат, чем смещение символа в тексте всего документа: свойство startOffset диапазона DOM (которое возвращает window.getSelection().getRangeAt()) является смещением относительно его свойства startContainer (которое не обязательно всегда является текстом узел, кстати). Однако, если вам действительно нужно смещение символа, вот функция, которая сделает это.

Вот живой пример: http://jsfiddle.net/timdown/2YcaX/

Вот функция:

function getCharacterOffsetWithin(range, node) {
    var treeWalker = document.createTreeWalker(
        node,
        NodeFilter.SHOW_TEXT,
        function(node) {
            var nodeRange = document.createRange();
            nodeRange.selectNode(node);
            return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
                NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        },
        false
    );

    var charCount = 0;
    while (treeWalker.nextNode()) {
        charCount += treeWalker.currentNode.length;
    }
    if (range.startContainer.nodeType == 3) {
        charCount += range.startOffset;
    }
    return charCount;
}
person Tim Down    schedule 22.01.2011
comment
Спасибо за ответ, выглядит неплохо, но есть небольшая ошибка. Не могли бы вы попытаться исправить это, поскольку я пробовал безуспешно. Я изменил ваш код, чтобы отображать позицию курсора в строке, а также он будет реагировать на событие нажатия клавиши. Код работает хорошо, но возвращает неверный результат, если курсор находится в начале или в конце тега. Попробуйте сами, просто нажмите на div contentEditable и продолжайте нажимать клавишу со стрелкой вправо, чтобы переместить курсор. jsfiddle.net/2YcaX/1 - person Frodik; 23.01.2011
comment
@Frodik: Готово. См. jsfiddle.net/timdown/2YcaX/3. Я также обновил ответ. - person Tim Down; 23.01.2011
comment
Большое спасибо, Тим, теперь все безупречно, именно то, что мне нужно. Еще раз спасибо. - person Frodik; 23.01.2011
comment
@Frodik: Поскольку я нигде не упоминал об этом, я упомяну здесь: этот ответ не будет работать для IE ‹= 8. Я не пытался предоставить решение для IE здесь, потому что в вашем исходном вопросе использовалось window.getSelection(), который не поддерживается в IE. - person Tim Down; 23.01.2011
comment
Да, я в курсе. Мое веб-приложение не поддерживает IE ‹= 8, потому что оно интенсивно использует функции HTML5. Так что это не вызывает беспокойства. - person Frodik; 24.01.2011
comment
Во-первых, спасибо за этот замечательный ответ. Но у меня проблема. Он не учитывает разрывы строк. Если я поставлю три разрыва строки в конце, позиция курсора, которую я получаю с помощью этой функции, все равно останется прежней. Вы можете мне с этим помочь? - person varunvs; 19.06.2013
comment
@varunvs: Я написал более сложную версию этого для моей библиотеки Rangy: код. google.com/p/rangy/wiki/TextRangeModule - person Tim Down; 19.06.2013
comment
Мне просто нужно получить позицию каретки, чтобы я восстановил ее перед вставкой ссылки. Разве Рэнги не переборщил? - person Ced; 25.02.2016
comment
@ Сид: Да. Я бы использовал код из связанного ответа (stackoverflow.com/a/4812022/96100) - person Tim Down; 26.02.2016

Это очень старый пост, но он все еще один из первых результатов поиска в Google, так что, возможно, он все еще полезен. Это работает для меня, чтобы получить правильную позицию, учитывая теги html и символы новой строки (проверено в Firefox):

function getCaretPosition (node) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange(),
        caretPosition,
        tmp = document.createElement("div");

    preCaretRange.selectNodeContents(node);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    tmp.appendChild(preCaretRange.cloneContents());
    caretPosition = tmp.innerHTML.length;
    return caretPosition;
}

Он использует функциональность cloneContents для получения фактического html и добавляет фрагмент документа во временный div, чтобы получить длину html.

person Andrea    schedule 24.10.2017
comment
Для меня это работает намного лучше, чем принятый в настоящее время ответ (как исходная, так и обновленная версия автора) для div contentEditable с форматированным HTML внутри. - person CFitz; 09.06.2018

Если вы хотите вставить элемент, вы можете попробовать сделать что-то вроде этого:

// Get range
var range = document.caretRangeFromPoint(event.clientX, event.clientY);
if (range)
  range.insertNode(elementWhichYouWantToAddToContentEditable);
person Antti    schedule 17.06.2014
comment
похоже, что caretRangeFromPoint специфичен для Firefox; это позор. - person Michael; 26.03.2015
comment
document.caretRangeFromPoint (x, y) с webkit и document.caretPositionFromPoint (x, y) с firefox? developer.mozilla.org/en-US/docs/Web/ API / Документ /. Незнайка выполняет эту работу: gist.github.com/unicornist/ac997a15bc3211ba1235 - person Antti; 08.04.2015