我有一个简单的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);

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


当前回答

我正在编写一个语法高亮显示(和基本的代码编辑器),我需要知道如何自动键入一个单引号字符并将插入符号移回(就像现在的许多代码编辑器一样)。

这里是我的解决方案的一个片段,感谢来自这个线程的帮助,MDN文档,和很多moz控制台观看。

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

这是一个可满足内容的div元素

我把这个留在这里作为感谢,意识到已经有一个公认的答案。

其他回答

我已经阅读并尝试了这里的一些案例,只是把对我有用的放在这里,根据dom节点考虑一些细节:

  focus(textInput){
    const length = textInput.innerText.length;
    textInput.focus();

    if(!!textInput.lastChild){
      const sel = window.getSelection();
      sel.collapse(textInput.lastChild, length);
    }
  }

我认为将插入符号设置到可满足元素中的某个位置并不简单。我为此写了自己的代码。它绕过了计算剩余字符数的节点树,并在所需元素中设置插入符号。我没有对这段代码进行太多测试。

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

我还写了代码来获取当前插入符号的位置(没有测试):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

你还需要注意范围。starttooffset和range。endOffset包含文本节点(nodeType === 3)的字符偏移量和元素节点(nodeType === 1)的子节点偏移量。startContainer和range。endContainer可以引用树中任何级别的任何元素节点(当然它们也可以引用文本节点)。

我正在编写一个语法高亮显示(和基本的代码编辑器),我需要知道如何自动键入一个单引号字符并将插入符号移回(就像现在的许多代码编辑器一样)。

这里是我的解决方案的一个片段,感谢来自这个线程的帮助,MDN文档,和很多moz控制台观看。

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

这是一个可满足内容的div元素

我把这个留在这里作为感谢,意识到已经有一个公认的答案。

如果你不想使用jQuery,你可以试试这个方法:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDiv你的可编辑元素,不要忘记为它设置一个id。然后你需要从元素中获取innerHTML并切断所有刹车线。然后用next参数设置collapse。

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;
}