How do you get and set the caret position in a contenteditable?

后端 未结 1 844
小鲜肉
小鲜肉 2021-01-29 04:59

This question has some answers here but there\'s a few problems.

Basically I want to do the following:

  1. get caret position
  2. set innerHTML of the conte
相关标签:
1条回答
  • 2021-01-29 05:38

    This seems to work for me but I've only tested it for my use cases.

    GET

    function getCaretIndex(win, contentEditable) {
        var index = 0;
        var selection = win.getSelection();
        var textNodes = textNodesUnder(contentEditable);
    
        for(var i = 0; i < textNodes.length; i++) {
            var node = textNodes[i];
            var isSelectedNode = node === selection.focusNode;
    
            if(isSelectedNode) {
                index += selection.focusOffset;
                break;
            }
            else {
                index += node.textContent.length;
            }
        }
    
        return index;
    }
    

    SET

    function setCaretIndex(win, contentEditable, newCaretIndex) {
        var cumulativeIndex = 0;
        var relativeIndex = 0;
        var targetNode = null;
    
        var textNodes = textNodesUnder(contentEditable);
    
        for(var i = 0; i < textNodes.length; i++) {
            var node = textNodes[i];
            
            if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
                targetNode = node;
                relativeIndex = newCaretIndex - cumulativeIndex;
                break;
            }
    
            cumulativeIndex += node.textContent.length;
        }
    
        var range = win.document.createRange();
        range.setStart(targetNode, relativeIndex);
        range.setEnd(targetNode, relativeIndex);
        range.collapse();
    
        var sel = win.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
    

    REQUIRED HELPER

    function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
        var all = [];
        for (node=node.firstChild;node;node=node.nextSibling){
            if (node.nodeType==3) {
                all.push(node);
            }
            else {
                all = all.concat(textNodesUnder(node));
            }
        }
        return all;
    }
    

    TEST (just call this function)

    It loops through the text in a contenteditable, sets the caret index, then reads it. Console output is: (setIndex | getIndex)

    function testContentEditable() {
        document.body.innerHTML = "<div contenteditable></div>"
        var ce = document.querySelector("[contenteditable]");
        ce.focus();
        ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";
        
        var i = 0;
        var intv = setInterval(function() {
            if(i == ce.innerText.length) {
                clearInterval(intv);
            }
    
            setCaretIndex(window, ce, i);
    
            var currentIndex = getCaretIndex(window, ce);
            console.log(i + " | " + currentIndex);
    
            i++;
        }, 100);
    }
    






    FIDDLE

    function getCaretIndex(win, contentEditable) {
        var index = 0;
        var selection = win.getSelection();
        var textNodes = textNodesUnder(contentEditable);
    
        for(var i = 0; i < textNodes.length; i++) {
            var node = textNodes[i];
            var isSelectedNode = node === selection.focusNode;
    
            if(isSelectedNode) {
                index += selection.focusOffset;
                break;
            }
            else {
                index += node.textContent.length;
            }
        }
    
        return index;
    }
    
    function setCaretIndex(win, contentEditable, newCaretIndex) {
        var cumulativeIndex = 0;
        var relativeIndex = 0;
        var targetNode = null;
    
        var textNodes = textNodesUnder(contentEditable);
    
        for(var i = 0; i < textNodes.length; i++) {
            var node = textNodes[i];
            
            if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
                targetNode = node;
                relativeIndex = newCaretIndex - cumulativeIndex;
                break;
            }
    
            cumulativeIndex += node.textContent.length;
        }
    
        var range = win.document.createRange();
        range.setStart(targetNode, relativeIndex);
        range.setEnd(targetNode, relativeIndex);
        range.collapse();
    
        var sel = win.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
    
    function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
        var all = [];
        for (node=node.firstChild;node;node=node.nextSibling){
            if (node.nodeType==3) {
                all.push(node);
            }
            else {
                all = all.concat(textNodesUnder(node));
            }
        }
        return all;
    }
    
    function testContentEditable() {
        document.body.innerHTML = "<div contenteditable></div>"
        var ce = document.querySelector("[contenteditable]");
        ce.focus();
        ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";
        
        var i = 0;
        var intv = setInterval(function() {
            if(i == ce.innerText.length) {
                clearInterval(intv);
            }
    
            setCaretIndex(window, ce, i);
    
            var currentIndex = getCaretIndex(window, ce);
            console.log(i + " | " + currentIndex);
    
            i++;
        }, 100);
    }
    testContentEditable();

    0 讨论(0)
提交回复
热议问题