Как получить позицию курсора в contentEditable, содержащем HTML?

Мне нужно получить позицию курсора в div contentEditable. Я получаю полезную функцию от Получить начальное и конечное смещение диапазона относительно его родительского контейнера и https://stackoverflow.com/a/4770562/2008261.

Но эти функции НЕ помогают, потому что мне нужна фактическая позиция в HTML. Вот что мне нужно.

если бы у меня был:

<div id="diva" contenteditable="true">Insert <b>text here</b></div>

и я ставлю курсор прямо перед словом «текст», он возвращает 7, поскольку имеет дело с текстом. Но мне нужно, чтобы он возвращал 10, как если бы он имел дело с внутренним HTML div contentEditabe.

Это функция, о которой я упоминал ранее:

function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
if (typeof window.getSelection != "undefined") {
    var range = window.getSelection().getRangeAt(0);
    var preCaretRange = range.cloneRange();
    preCaretRange.selectNodeContents(element);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    caretOffset = preCaretRange.toString().length;
} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
    var textRange = document.selection.createRange();
    var preCaretTextRange = document.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}

И :

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 Develop Smith    schedule 08.02.2013    source источник
comment
Пробовали ли вы использовать кроссбраузерную библиотеку, такую ​​как rangy?   -  person Vlad Magdalin    schedule 09.02.2013
comment
@VladMagdalin, Рэнджи классный. В нем было больше, чем мне нужно   -  person Develop Smith    schedule 09.02.2013


Ответы (1)


Мне приходилось решать эту проблему раньше (я создал веб-текстовый процессор): единственное жизнеспособное решение - пройтись по дереву DOM и подсчитать длину как innerHTML, так и externalHTML, а затем использовать смещение в пределах диапазона, если он есть.

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

Моя реализация представляла собой функцию, которая брала смещение диапазона и шла вверх, пока не достигла заданного граничного родителя. Следует помнить о некоторых вещах: предыдущих братьев и сестер легко добавить, но обернуть родителей сложно с атрибутами и т. Д., Чтобы учесть длину.

Мой расчет состоял в том, чтобы взять всю длину узла "<div id="" class="" foo="bar">sometext</div>", вычесть длину innerHTML, вычесть длину tagname и subtract 3, чтобы получить длину <div id="" class="" foo="bar">.

НО, если ВЫ не контролировали создание этого HTML, ваш подсчет все равно может быть неправильным, так как в зависимости от браузера несколько пробелов в строке возвращаются либо как 1 символ, либо как несколько символов при проверке длины.

person runspired    schedule 08.02.2013
comment
если я не использую реализации contentEditable, что мне использовать вместо этого. Я не могу реализовать textarea для своих нужд. Я согласен с вами, что у него много проблем и он очень сбивает с толку, поскольку я кодирую очень много кодов, но каково другое решение? - person Develop Smith; 09.02.2013
comment
Почему вы не можете реализовать текстовое поле? Скрытая текстовая область — гораздо более полезный трюк, чем iframe или редактируемые элементы контента. - person runspired; 09.02.2013
comment
на самом деле я использую оба из них, textarea и contentEditable. ContentEditable — это то, с чем взаимодействует пользователь, а текстовое поле скрыто. Когда пользователь пишет что-то или стилизует что-то в contentEditable, это отражается в скрытой текстовой области. Таким образом, текстовая область сохраняет разметку html, а код jQuery создает мост между ними обоими. Но я не могу использовать текстовое поле отдельно, потому что оно не поддерживает отображение текста в формате HTML. Я думаю, что ваш текстовый процессор реализует ту же концепцию. Не так ли? - person Develop Smith; 09.02.2013
comment
нет, мой активно редактирует фактический html с помощью javascript, используя текстовое поле для захвата ключа и вставки событий. - person runspired; 09.02.2013
comment
Для вашей реализации, если вы просматриваете DOM, зачем вам нужно смотреть на innerHTML и outerHTML? Поскольку браузер может создать множество потенциальных допустимых HTML-представлений дерева DOM, смещение внутри одного из них не имеет смысла. - person Tim Down; 10.02.2013
comment
Я также частично не согласен с вами по поводу того, чтобы избегать contenteditable: это действительно зависит от ваших требований. Для простого редактора вполне может быть нормально. Тем не менее, я использую вариант скрытого текстового поля в моем текущем проекте. - person Tim Down; 10.02.2013
comment
Вы должны смотреть на оба, потому что в традиционном OT вам нужно смещение символов независимо от того, видны эти символы или нет. И да, для некоторых базовых ситуаций contentEditable подходит, но я также смог создать гораздо лучшие приложения, избегая его. - person runspired; 12.02.2013