获取内容可编辑的插入符号位置

IT技术 javascript contenteditable caret selection-api
2021-01-19 22:52:53

我找到了大量关于如何元素中设置插入符号位置的跨浏览器的好答案contentEditable,但没有找到关于如何首先获得插入符号位置的答案

我想要做的是知道 .div 上的插入符号位置keyup因此,当用户输入文本时,我可以随时知道contentEditable元素内的插入符号位置

<div id="contentBox" contentEditable="true"></div>

$('#contentbox').keyup(function() { 
    // ... ? 
});
6个回答

以下代码假设:

  • 可编辑中始终只有一个文本节点<div>,没有其他节点
  • 可编辑的 div 没有将 CSSwhite-space属性设置为pre

如果您需要更通用的方法来处理嵌套元素的内容,请尝试以下答案:

https://stackoverflow.com/a/4812022/96100

代码:

function getCaretPosition(editableDiv) {
  var caretPos = 0,
    sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.rangeCount) {
      range = sel.getRangeAt(0);
      if (range.commonAncestorContainer.parentNode == editableDiv) {
        caretPos = range.endOffset;
      }
    }
  } else if (document.selection && document.selection.createRange) {
    range = document.selection.createRange();
    if (range.parentElement() == editableDiv) {
      var tempEl = document.createElement("span");
      editableDiv.insertBefore(tempEl, editableDiv.firstChild);
      var tempRange = range.duplicate();
      tempRange.moveToElementText(tempEl);
      tempRange.setEndPoint("EndToEnd", range);
      caretPos = tempRange.text.length;
    }
  }
  return caretPos;
}
#caretposition {
  font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
<div id="caretposition">0</div>
<script>
  var update = function() {
    $('#caretposition').html(getCaretPosition(this));
  };
  $('#contentbox').on("mousedown mouseup keydown keyup", update);
</script>

@giorgio79:是的,因为换行符会生成一个<br>or<div>元素,这违反了答案中提到的第一个假设。如果您需要更通用的解决方案,可以尝试stackoverflow.com/a/4812022/96100
2021-03-10 22:52:53
如果我按 Enter 并换行,JSFIDDLE 演示就会失败。位置将显示 0。
2021-03-12 22:52:53
如果那里有任何其他标签,这将不起作用。问题:如果插入符号位于 内的<a>元素内<div>,那么您想要什么偏移量?文本内的偏移量<a>
2021-03-13 22:52:53
有没有办法做到这一点,所以它包括行号?
2021-03-19 22:52:53
@Richard:嗯,keyup这可能是错误的事件,但这是原始问题中使用的。getCaretPosition()在自己的限度内是好的。
2021-03-21 22:52:53

我在其他答案中没有看到的一些皱纹:

  1. 该元素可以包含多个级别的子节点(例如,具有子节点的子节点具有子节点......)
  2. 选择可以由不同的开始和结束位置组成(例如选择多个字符)
  3. 包含插入符号开始/结束的节点可能不是元素或其直接子元素

这是一种获取开始和结束位置作为元素 textContent 值的偏移量的方法:

// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
  var result = func(node);
  for(node = node.firstChild; result !== false && node; node = node.nextSibling)
    result = node_walk(node, func);
  return result;
};

// getCaretPosition: return [start, end] as offsets to elem.textContent that
//   correspond to the selected portion of text
//   (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
  var sel = window.getSelection();
  var cum_length = [0, 0];

  if(sel.anchorNode == elem)
    cum_length = [sel.anchorOffset, sel.extentOffset];
  else {
    var nodes_to_find = [sel.anchorNode, sel.extentNode];
    if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
      return undefined;
    else {
      var found = [0,0];
      var i;
      node_walk(elem, function(node) {
        for(i = 0; i < 2; i++) {
          if(node == nodes_to_find[i]) {
            found[i] = true;
            if(found[i == 0 ? 1 : 0])
              return false; // all done
          }
        }

        if(node.textContent && !node.firstChild) {
          for(i = 0; i < 2; i++) {
            if(!found[i])
              cum_length[i] += node.textContent.length;
          }
        }
      });
      cum_length[0] += sel.anchorOffset;
      cum_length[1] += sel.extentOffset;
    }
  }
  if(cum_length[0] <= cum_length[1])
    return cum_length;
  return [cum_length[1], cum_length[0]];
}
这必须被选为正确答案。它适用于文本中的标签(接受的响应不适用)
2021-03-12 22:52:53
@WeAreDoomed 请参阅上述评论重新设置CaretPosition
2021-03-19 22:52:53
有没有办法包括换行符?按“输入”不会改变此功能的结果。我也知道问题中没有提到它,但是等效的“setCaretPosition”会非常有帮助
2021-03-20 22:52:53
setCaretPosition 在这里被询问/回答:stackoverflow.com/questions/512528 /... (虽然我使用了解决方案的修改版本,不记得为什么)
2021-03-28 22:52:53
换行:是的,但它是一个更复杂的解决方案。换行符在文本节点中表示为插入节点树中的无文本 BR 节点,这在 textContent 中没有正确反映。因此,为了处理它们,基本上任何对 textContent 的引用都必须替换为一个函数,例如“getNodeInnerText()”,它将遍历节点树并构造正确的文本字符串,特别是,将为任何 BR 节点插入“\n” (在大多数情况下——它比那更微妙)
2021-04-09 22:52:53

$("#editable").on('keydown keyup mousedown mouseup',function(e){
		   
       if($(window.getSelection().anchorNode).is($(this))){
    	  $('#position').html('0')
       }else{
         $('#position').html(window.getSelection().anchorOffset);
       }
 });
body{
  padding:40px;
}
#editable{
  height:50px;
  width:400px;
  border:1px solid #000;
}
#editable p{
  margin:0;
  padding:0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div contenteditable="true" id="editable">move the cursor to see position</div>
<div>
position : <span id="position"></span>
</div>

如果您有一些粗体和/或斜体字,它就不能正常工作。
2021-03-13 22:52:53
不幸的是,一旦您按 Enter 键并从另一行开始(它再次从 0 开始 - 可能从 CR/LF 开始计数),这就会停止工作。
2021-04-01 22:52:53

参加聚会有点晚,但以防其他人在挣扎。我在过去两天找到的所有 Google 搜索都没有提出任何有效的方法,但我提出了一个简洁而优雅的解决方案,无论您有多少嵌套标签,该解决方案始终有效:

function cursor_position() {
    var sel = document.getSelection();
    sel.modify("extend", "backward", "paragraphboundary");
    var pos = sel.toString().length;
    if(sel.anchorNode != undefined) sel.collapseToEnd();

    return pos;
}

// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)

function printCaretPosition(){
  console.log( cursor_position(), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

它一直选择回到段落的开头,然后计算字符串的长度以获取当前位置,然后撤消选择以将光标返回到当前位置。如果您想对整个文档(多于一个段落)执行此操作,请更改paragraphboundarydocumentboundary或适合您的案例的任何粒度。查看 API 了解更多详情干杯! :)

看起来所有浏览器都可能支持也可能不支持 selection.modify。developer.mozilla.org/en-US/docs/Web/API/Selection
2021-03-10 22:52:53
奇怪的。我在 Chrome 中没有得到这种行为。你使用的是什么浏览器?
2021-03-13 22:52:53
如果我 <div contenteditable> some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text </div> 每次将光标放在i标记之前或内部的任何子 html 元素之前div,光标位置都从 0 开始。有没有办法逃避这个重新启动计数?
2021-03-25 22:52:53
在 Firefox 中不起作用:/ NS_ERROR_NOT_IMPLEMENTEDselection.modify 看起来在这个浏览器上并不真正支持它:developer.mozilla.org/en-US/docs/Web/API/Selection/modify
2021-03-25 22:52:53
非常好。做得好。
2021-04-03 22:52:53

试试这个:

Caret.js 从文本字段获取插入符号位置和偏移量

https://github.com/ichord/Caret.js

演示:http : //ichord.github.com/Caret.js

@AndroidDev 我不是 Caret.js 的作者,但您是否考虑过获取所有主要浏览器的插入位置比几行代码更复杂?您知道或已经创建了一个可以与我们分享的非臃肿替代方案吗?
2021-03-13 22:52:53
这是甜蜜的。contenteditable li当单击按钮重命名li的内容时,我需要这种行为来将插入符号设置为 a 的结尾
2021-03-27 22:52:53