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

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


当前回答

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

//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可以引用树中任何级别的任何元素节点(当然它们也可以引用文本节点)。

其他回答

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

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

    if(!!textInput.lastChild){
      const sel = window.getSelection();
      sel.collapse(textInput.lastChild, length);
    }
  }
  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);
  }

如果你不想使用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。

基于Tim Down的答案,但它会检查最后一个已知的“好”文本行。它把光标放在最后。

此外,我还可以递归/迭代地检查每个连续的最后一个子节点的最后一个子节点,以找到DOM中绝对最后一个“好”文本节点。

function onClickHandler() { setCaret(document.getElementById("editable")); } function setCaret(el) { let range = document.createRange(), sel = window.getSelection(), lastKnownIndex = -1; for (let i = 0; i < el.childNodes.length; i++) { if (isTextNodeAndContentNoEmpty(el.childNodes[i])) { lastKnownIndex = i; } } if (lastKnownIndex === -1) { throw new Error('Could not find valid text content'); } let row = el.childNodes[lastKnownIndex], col = row.textContent.length; range.setStart(row, col); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); el.focus(); } function isTextNodeAndContentNoEmpty(node) { return node.nodeType == Node.TEXT_NODE && node.textContent.trim().length > 0 } <div id="editable" contenteditable="true"> text text text<br>text text text<br>text text text<br> </div> <button id="button" onclick="onClickHandler()">focus</button>

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

//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可以引用树中任何级别的任何元素节点(当然它们也可以引用文本节点)。