我有一个简单的HTML作为例子:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

我想要简单的东西-当我点击按钮时,我想在可编辑的div中放置插入符号(光标)到特定的地方。从网上搜索,我有这个JS附加到按钮点击,但它不起作用(FF, Chrome):

const range = document.createRange();
const myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

是否可以像这样手动设置插入符号的位置?


当前回答

我重构了@Liam的答案。我把它放在一个带有静态方法的类中,我让它的函数接收一个元素而不是#id,还有其他一些小调整。

这段代码特别适用于在富文本框中固定光标,例如<div contenteditable="true">。在得到下面的代码之前,我被这个问题困了好几天。

编辑:他的答案和这个答案有一个bug,涉及到按回车键。由于enter不能算作一个字符,所以按下enter键后光标的位置就乱了。如果我能够修复代码,我将更新我的答案。

edit2:为自己省去很多麻烦,确保<div contenteditable=true>为display: inline-block。这修复了一些有关Chrome浏览器把<div>而不是<br>当你按下enter的错误。

如何使用

let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// insert code here that does stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();

Code

// Credit to Liam (Stack Overflow)
// https://stackoverflow.com/a/41034697/3480193
class Cursor {
    static getCurrentCursorPosition(parentElement) {
        var selection = window.getSelection(),
            charCount = -1,
            node;
        
        if (selection.focusNode) {
            if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                node = selection.focusNode; 
                charCount = selection.focusOffset;
                
                while (node) {
                    if (node === parentElement) {
                        break;
                    }

                    if (node.previousSibling) {
                        node = node.previousSibling;
                        charCount += node.textContent.length;
                    } else {
                        node = node.parentNode;
                        if (node === null) {
                            break;
                        }
                    }
                }
            }
        }
        
        return charCount;
    }
    
    static setCurrentCursorPosition(chars, element) {
        if (chars >= 0) {
            var selection = window.getSelection();
            
            let range = Cursor._createRange(element, { count: chars });

            if (range) {
                range.collapse(false);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    
    static _createRange(node, chars, range) {
        if (!range) {
            range = document.createRange()
            range.selectNode(node);
            range.setStart(node, 0);
        }

        if (chars.count === 0) {
            range.setEnd(node, chars.count);
        } else if (node && chars.count >0) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent.length < chars.count) {
                    chars.count -= node.textContent.length;
                } else {
                    range.setEnd(node, chars.count);
                    chars.count = 0;
                }
            } else {
                for (var lp = 0; lp < node.childNodes.length; lp++) {
                    range = Cursor._createRange(node.childNodes[lp], chars, range);

                    if (chars.count === 0) {
                    break;
                    }
                }
            }
        } 

        return range;
    }
    
    static _isChildOf(node, parentElement) {
        while (node !== null) {
            if (node === parentElement) {
                return true;
            }
            node = node.parentNode;
        }

        return false;
    }
}

其他回答

您在可满足光标定位方面找到的大多数答案都相当简单,因为它们只满足带有普通文本的输入。一旦在容器中使用html元素,输入的文本就会被分割成节点,并在树结构中自由分布。

为了设置光标位置,我使用了这个函数,它在提供的节点内循环所有子文本节点,并设置从初始节点开始到字符的范围。数字符:

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

然后我用这个函数调用这个例程:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

range.collapse(false)将光标设置到范围的末尾。我用最新版本的Chrome、IE、Mozilla和Opera对它进行了测试,它们都运行良好。

PS.如果有人感兴趣,我得到当前的光标位置使用这段代码:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

代码的作用与set函数相反——它获取当前的window.getSelection()。focusNode和focusOffset,并向后计算遇到的所有文本字符,直到它到达id为containerId的父节点。isChildOf函数只是在运行之前检查所提供的节点实际上是所提供的parentId的子节点。

代码应该工作直接没有改变,但我刚刚从一个jQuery插件我已经开发了,所以已经黑出了一对这的一对-让我知道,如果有什么不工作!

我重构了@Liam的答案。我把它放在一个带有静态方法的类中,我让它的函数接收一个元素而不是#id,还有其他一些小调整。

这段代码特别适用于在富文本框中固定光标,例如<div contenteditable="true">。在得到下面的代码之前,我被这个问题困了好几天。

编辑:他的答案和这个答案有一个bug,涉及到按回车键。由于enter不能算作一个字符,所以按下enter键后光标的位置就乱了。如果我能够修复代码,我将更新我的答案。

edit2:为自己省去很多麻烦,确保<div contenteditable=true>为display: inline-block。这修复了一些有关Chrome浏览器把<div>而不是<br>当你按下enter的错误。

如何使用

let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// insert code here that does stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();

Code

// Credit to Liam (Stack Overflow)
// https://stackoverflow.com/a/41034697/3480193
class Cursor {
    static getCurrentCursorPosition(parentElement) {
        var selection = window.getSelection(),
            charCount = -1,
            node;
        
        if (selection.focusNode) {
            if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                node = selection.focusNode; 
                charCount = selection.focusOffset;
                
                while (node) {
                    if (node === parentElement) {
                        break;
                    }

                    if (node.previousSibling) {
                        node = node.previousSibling;
                        charCount += node.textContent.length;
                    } else {
                        node = node.parentNode;
                        if (node === null) {
                            break;
                        }
                    }
                }
            }
        }
        
        return charCount;
    }
    
    static setCurrentCursorPosition(chars, element) {
        if (chars >= 0) {
            var selection = window.getSelection();
            
            let range = Cursor._createRange(element, { count: chars });

            if (range) {
                range.collapse(false);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    
    static _createRange(node, chars, range) {
        if (!range) {
            range = document.createRange()
            range.selectNode(node);
            range.setStart(node, 0);
        }

        if (chars.count === 0) {
            range.setEnd(node, chars.count);
        } else if (node && chars.count >0) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent.length < chars.count) {
                    chars.count -= node.textContent.length;
                } else {
                    range.setEnd(node, chars.count);
                    chars.count = 0;
                }
            } else {
                for (var lp = 0; lp < node.childNodes.length; lp++) {
                    range = Cursor._createRange(node.childNodes[lp], chars, range);

                    if (chars.count === 0) {
                    break;
                    }
                }
            }
        } 

        return range;
    }
    
    static _isChildOf(node, parentElement) {
        while (node !== null) {
            if (node === parentElement) {
                return true;
            }
            node = node.parentNode;
        }

        return false;
    }
}
move(element:any,x:number){//parent
    let arr:Array<any>=[];
    arr=this.getAllnodeOfanItem(this.input.nativeElement,arr);
    let j=0;
    while (x>arr[j].length && j<arr.length){
        x-=arr[j].length;
        j++;
    }
    

    
    var el = arr[j];
    var range = document.createRange();
    var sel = window.getSelection();
    range.setStart(el,x );
    range.collapse(true);
    if (sel)sel.removeAllRanges();
    if (sel)sel.addRange(range);
}   

getAllnodeOfanItem(element:any,rep:Array<any>){
    let ch:Array<any>=element.childNodes;
    if (ch.length==0 && element.innerText!="")
        rep.push(element);
    else{
        for (let i=0;i<ch.length;i++){
            rep=this.getAllnodeOfanItem(ch[i],rep)
        }
    }
    return rep;
}

在大多数浏览器中,都需要Range和Selection对象。将每个选择边界指定为一个节点,并在该节点中指定偏移量。例如,要将插入符号设置为第二行文本的第五个字符,您可以执行以下操作:

function setCaret() var el =文档。getElementById(“编辑”) 咖啡壶。 var = window.getSelection() 太阳城setStart (el。childNodes [2], 5) 太阳城崩溃(真正的)。 细胞removeAllRanges()。 细胞addRange(射程)。 的 <div id=“编辑”contenteble =“真”> 文本文本<br>文本文本<br>文本文本<br> < / div > <巴顿id=“巴顿”onclick=“setCaret”()巴顿”focus > < - >

IE < 9的工作方式完全不同。如果需要支持这些浏览器,则需要不同的代码。

jsFiddle示例:http://jsfiddle.net/timdown/vXnCM/

  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }