更改 contenteditable 后恢复光标位置

17

有这样的html:

<div contenteditable="true" class="value research-form thumbnail">
 Some text here
 </div>
用户输入时,div的内容应动态地突出显示一些单词,例如制作某些东西:
 <div contenteditable="true" class="value research-form thumbnail">
 Some text here <span style="background-color: yellow">highlight</div> it
 </div>
 <script>
    $(document).ready(function () {
        var input = $('#textarea').on('input', function (event) {
            var newText = input.text().replace('highlight', '<span style="background-color: yellow">highlight</div>');
            input.html($.parseHTML(newText));
        });
    });
</script>

但是存在一个问题:当我刷新div中的文本时,光标会移动到输入框文本的开头。

有没有办法在更改contenteditable值后恢复光标位置?或者可能有其他方法可以达到相同的效果吗?


1
尝试保存光标位置(请参见https://dev59.com/mG445IYBdhLWcg3wnrvM),然后在input.html(...)之后将其设置回来(请参见https://dev59.com/jHM_5IYBdhLWcg3w4njb)。我认为您在这里还有其他问题:在您的示例中,“highlight”单词每次编辑文本时都会被包装在新的span中。您可能应该在添加新的span之前用占位符替换包装的单词,然后再将占位符替换回来。 - Qwerty
如果您有符合期望的解决方案,应将其添加为答案并选择它。 http://stackoverflow.com/help/self-answer - Jason Sperske
回到2016年,我找到了一个不错的div解决方案,并在这里发布以回答那个问题:https://dev59.com/dW455IYBdhLWcg3wCPjh#38479462 - pery mimon
1个回答

14

我找到了解决方案。

这是完整的代码:

<div class="container" style="margin-top: 10px">

    <div class="thumbnail value" contenteditable="true">

    </div>

</div>

<script>
    $(document).ready(function () {
        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;
        }

        function setCaretPosition(element, offset) {
            var range = document.createRange();
            var sel = window.getSelection();

            //select appropriate node
            var currentNode = null;
            var previousNode = null;

            for (var i = 0; i < element.childNodes.length; i++) {
                //save previous node
                previousNode = currentNode;

                //get current node
                currentNode = element.childNodes[i];
                //if we get span or something else then we should get child node
               while(currentNode.childNodes.length > 0){
                  currentNode = currentNode.childNodes[0];
               }

                //calc offset in current node
                if (previousNode != null) {
                    offset -= previousNode.length;
                }
                //check whether current node has enough length
                if (offset <= currentNode.length) {
                    break;
                }
            }
            //move caret to specified offset
            if (currentNode != null) {
                range.setStart(currentNode, offset);
                range.collapse(true);
                sel.removeAllRanges();
                sel.addRange(range);
            }
        }

        function onInput(event) {
            var position = getCaretCharacterOffsetWithin(input.get(0));
            var text = input.text();
            text = text.replace(new RegExp('\\btest\\b', 'ig'), '<span style="background-color: yellow">test</span>');
            input.html($.parseHTML(text));
            setCaretPosition(input.get(0), position);
        }

        var input = $('.value').on('input',onInput);

        //content should be updated manually to prevent aditional spaces
        input.html('simple input test example');
        //trigger event
        onInput();
    });
</script>

2
这是一个适用于你特殊情况的好解决方案:如果你在你的内容表格中禁用了换行符。否则,获取光标偏移量的脚本会忽略像<br>这样的换行符实体,并将光标位置设置在错误的位置(在<br>元素之前)。 - Mihaly KR
当输入字符 <> 时,也会出现问题。它会破坏整个内容。你应该先使用 &lt;&gt; 转义第一个字符。 - Slavik Meltser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接