我在如何设置contentEditable元素中的游标或插入符号索引位置方面发现了很多优秀的跨浏览器答案,但没有关于如何获取或找到其索引的答案...

我想要什么在keyup上知道该div内插入符号的索引。

因此,当用户键入文本时,我随时都可以知道contentEditable元素内的光标索引。

编辑:我正在寻找文本中的INDEX。 div内容(文本),而不是光标坐标。

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

$('#contentbox').keyup(function() { 
    // ... ? 
});


评论

查看其在文本中的位置。然后,在该位置之前查找最后出现的“ @”。所以只是一些文本逻辑。

另外,我不打算允许
中的其他标签,仅允许文本

好的,是的,我将需要
中的其他标签。将有标签,但不会有嵌套...

@Bertvan:如果插入符号位于

#1 楼

以下代码假定:


在可编辑<div>中始终只有一个文本节点,而没有其他节点
可编辑div的CSS white-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> 




评论


如果其中还有其他标签,则无法使用。问题:如果插入符号位于

#2 楼

其他答案中未解决的一些皱纹:


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

这是一种获取起点和终点位置作为元素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]];
}


评论


必须选择正确的答案。它与文本内的标签一起使用(接受的响应无效)

– hamboy75
19年4月11日在7:37

有没有办法包括换行符?按下“输入”并不会改变此功能的结果。我也知道问题中没有提到它,但是等效的“ setCaretPosition”将非常有帮助

–康纳
8月21日3:50



重新换行:是的,但这是一个更复杂的解决方案。换行符在文本节点中表示为插入到节点树中的无文本BR节点,这些节点未正确反映在textContent中。因此,要处理它们,基本上所有对textContent的引用都必须替换为一个函数,例如“ getNodeInnerText()”将遍历节点树并构造适当的文本字符串,尤其是将为任何BR节点插入“ \ n”(在大多数情况下-比这更微妙)

–mwag
8月21日13:46

在这里询问/回答setCaretPosition:stackoverflow.com/questions/512528/…(尽管我使用了解决方案的修改版,但不记得为什么了)

–mwag
8月21日13:46

#5 楼

金达晚了晚会,但万一其他人都在挣扎。在过去两天中,我没有发现任何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> 





它一直选择到段落的开头,然后计算字符串的长度以获取当前位置,然后撤消选择以将光标返回到当前位置。如果要对整个文档(一个以上的段落)执行此操作,则将paragraphboundary更改为documentboundary或您的案例的任何粒度。查看API以获得更多详细信息。干杯! :)

评论


如果我有
这里的一些文本这里的斜体文本这里的一些其他文本这里的粗体文本的结尾
每次将光标放在标签或任何其他内容之前div中的child html元素,光标位置从0开始。是否有办法摆脱这种重新开始的计数?

–vam
19-2-5在14:20



奇。我在Chrome中没有得到这种行为。你使用的是什么浏览器?

– Soubriquet
19年2月8日在16:02

看起来或并非所有浏览器都支持selection.modify。 developer.mozilla.org/zh-CN/docs/Web/API/Selection

–克里斯·沙利文(Chris Sullivan)
19年4月7日在7:42

非常好。做得好。

–伦·怀特
12月20日下午2:10

#8 楼

//global savedrange variable to store text range in
var savedrange = null;

function getSelection()
{
    var savedRange;
    if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
    {
        savedRange = window.getSelection().getRangeAt(0).cloneRange();
    }
    else if(document.selection)//IE 8 and lower
    { 
        savedRange = document.selection.createRange();
    }
    return savedRange;
}

$('#contentbox').keyup(function() { 
    var currentRange = getSelection();
    if(window.getSelection)
    {
        //do stuff with standards based object
    }
    else if(document.selection)
    { 
        //do stuff with microsoft object (ie8 and lower)
    }
});


注意:范围对象本身可以存储在变量中,并且可以随时重新选择,除非contenteditable div的内容发生更改。

参考对于IE 8及更低版本:
http://msdn.microsoft.com/zh-cn/library/ms535872(VS.85).aspx

标准(所有其他)浏览器的参考:
https://developer.mozilla.org/en/DOM/range(它是mozilla文档,但代码也可以在chrome,safari,opera和ie9中使用)

评论


谢谢,但是我究竟如何在div内容中获得插入符号位置的“索引”?

–贝特万
2010-10-19 20:22

好了,好像在.getSelection()上调用.baseOffset可以解决问题。因此,这与您的答案一起回答了我的问题。谢谢!

–贝特万
2010-10-19 20:32



不幸的是.baseOffset只在webkit中起作用(我认为)。它还仅给您与插入符号的直接​​父级之间的偏移量(如果您在
内有标记,它将提供距开头而不是
开头的偏移量基于标准的范围可以使用range.endOffset range.startOffset range.endContainer和range.startContainer来获取与选择的父节点以及节点本身(包括文本节点)之间的偏移量。IE提供range.offsetLeft从左侧偏移的像素,因此无用。

– Nico Burns
2010-10-19 20:52

最好只是存储范围对象本身,并使用window.getSelection()。addrange(range); <-标准和range.select(); <-IE,用于将光标重新放置在同一位置。 range.insertNode(nodetoinsert); <-standards和range.pasteHTML(htmlcode); <-IE,用于在光标处插入文本或html。

– Nico Burns
2010-10-19 20:56

大多数浏览器返回的Range对象和IE返回的TextRange对象是完全不同的东西,因此我不确定此答案能否解决很多问题。

– Tim Down
2010-10-19 23:44

#9 楼

由于这使我永远无法使用新的window.getSelection API,因此我将分享给后代。请注意,MDN建议对window.getSelection提供更广泛的支持,但是,您的里程可能会有所不同。

const getSelectionCaretAndLine = () => {
    // our editable div
    const editable = document.getElementById('editable');

    // collapse selection to end
    window.getSelection().collapseToEnd();

    const sel = window.getSelection();
    const range = sel.getRangeAt(0);

    // get anchor node if startContainer parent is editable
    let selectedNode = editable === range.startContainer.parentNode
      ? sel.anchorNode 
      : range.startContainer.parentNode;

    if (!selectedNode) {
        return {
            caret: -1,
            line: -1,
        };
    }

    // select to top of editable
    range.setStart(editable.firstChild, 0);

    // do not use 'this' sel anymore since the selection has changed
    const content = window.getSelection().toString();
    const text = JSON.stringify(content);
    const lines = (text.match(/\n/g) || []).length + 1;

    // clear selection
    window.getSelection().collapseToEnd();

    // minus 2 because of strange text formatting
    return {
        caret: text.length - 2, 
        line: lines,
    }
} 


这是在键盘启动时触发的jsfiddle。但是请注意,快速方向键的按下以及快速删除似乎是跳过事件。

评论


为我工作!非常感谢。

– dmodo
19-09-11的1:58

使用此文本时,由于已折叠,因此不再可能进行选择。可能的情况:需要评估每个keyUp事件

– hschmieder
19年9月28日在0:31

#10 楼

一种直接的方法,它遍历contenteditable div的所有子项,直到到达endContainer。然后,我添加结束容器偏移量,然后得到字符索引。应该与任何数量的嵌套一起使用。使用递归。

注意:需要多边形填充,例如支持Element.closest('div[contenteditable]')

https://codepen.io/alockwood05/pen/vMpdmZ

function caretPositionIndex() {
    const range = window.getSelection().getRangeAt(0);
    const { endContainer, endOffset } = range;

    // get contenteditableDiv from our endContainer node
    let contenteditableDiv;
    const contenteditableSelector = "div[contenteditable]";
    switch (endContainer.nodeType) {
      case Node.TEXT_NODE:
        contenteditableDiv = endContainer.parentElement.closest(contenteditableSelector);
        break;
      case Node.ELEMENT_NODE:
        contenteditableDiv = endContainer.closest(contenteditableSelector);
        break;
    }
    if (!contenteditableDiv) return '';


    const countBeforeEnd = countUntilEndContainer(contenteditableDiv, endContainer);
    if (countBeforeEnd.error ) return null;
    return countBeforeEnd.count + endOffset;

    function countUntilEndContainer(parent, endNode, countingState = {count: 0}) {
      for (let node of parent.childNodes) {
        if (countingState.done) break;
        if (node === endNode) {
          countingState.done = true;
          return countingState;
        }
        if (node.nodeType === Node.TEXT_NODE) {
          countingState.count += node.length;
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          countUntilEndContainer(node, endNode, countingState);
        } else {
          countingState.error = true;
        }
      }
      return countingState;
    }
  }


最近发表