简体   繁体   English

Contenteditable DIV - 如何确定光标是否位于内容的开头或结尾

[英]Contenteditable DIV - how can I determine if the cursor is at the start or end of the content

I have a contenteditable div which contains typical wysiwyg editor html (bold, anchors, lists). 我有一个contenteditable div,其中包含典型的所见即所得编辑器html(粗体,锚点,列表)。

I need to determine if the current cursor is, onKeyDown, at the start and at the end of the div. 我需要确定当前光标是否为onKeyDown,在div的开头和结尾。 The reason for this is, based on the cursor position, and the key pressed, I might want to merge this div with the previous div on a backspace, or create a new following div on enter. 原因是,基于光标位置和按下的键,我可能想要将此div与退格上的前一个div合并,或者在输入时创建一个新的跟随div。

I've been fiddling around with ranges, but when you're working with html inside the element, things get pretty complicated. 我一直在摆弄范围,但是当你在元素中使用html时,事情变得相当复杂。

I'm hoping I must be overlooking some simple solution. 我希望我必须忽略一些简单的解决方案。

Is there a relatively simple way to determine this - I'm open to using a library like Rangy. 是否有一种相对简单的方法来确定这一点 - 我愿意使用像Rangy这样的库。

Thanks! 谢谢!

Edit: I'm thinking something along these lines: 编辑:我正在考虑以下几点:

$('.mycontenteditable').bind('keydown', handle_keydown)

handle_keydown = function(e) {

  range = window.getSelection().getRangeAt(0)
  start_range = document.createRange()
  start_range.selectNodeContents(this.firstChild)
  start_range.collapse(true) // collapse to start
  is_start = start_range.compareBoundaryPoints(Range.START_TO_START,range)
  end_range = document.createRange()
  end_range.selectNodeContents(this.lastChild)
  end_range.collapse(false)
  is_end = end_range.compareBoundaryPoints(Range.END_TO_END,range)
}

Am I going to run into any odd issues with something like this? 我会遇到类似这样的奇怪问题吗?

I would use a similar approach to yours except using the toString() method of Range objects rather than cloneContents() to avoid unnecessary cloning. 我会使用类似的方法,除了使用Range对象的toString()方法而不是cloneContents()以避免不必要的克隆。 Also, in IE < 9 (which doesn't support ranges), you can use a similar approach with the text property of TextRange . 此外,在IE <9(不支持范围)中,您可以使用与TextRangetext属性类似的方法。

Note that this will have issues when there are leading and/or trailing line breaks in the content because the toString() method of a range works just like the textContent property of a node and only considers text nodes, therefore not taking into account line breaks implied by <br> or block elements. 请注意,当内容中存在前导和/或尾随换行符时,这会出现问题,因为范围的toString()方法就像节点的textContent属性一样,只考虑文本节点,因此不考虑换行符通过暗示<br>或块元素。 Also CSS is not taken into account: for example, text inside elements that are hidden via display: none is included. 此外,不考虑CSS:例如,通过display: none隐藏的元素内的文本包含在内。

Here's an example: 这是一个例子:

Live demo: http://jsfiddle.net/YA3Pu/1/ 现场演示: http//jsfiddle.net/YA3Pu/1/

Code: 码:

function getSelectionTextInfo(el) {
    var atStart = false, atEnd = false;
    var selRange, testRange;
    if (window.getSelection) {
        var sel = window.getSelection();
        if (sel.rangeCount) {
            selRange = sel.getRangeAt(0);
            testRange = selRange.cloneRange();

            testRange.selectNodeContents(el);
            testRange.setEnd(selRange.startContainer, selRange.startOffset);
            atStart = (testRange.toString() == "");

            testRange.selectNodeContents(el);
            testRange.setStart(selRange.endContainer, selRange.endOffset);
            atEnd = (testRange.toString() == "");
        }
    } else if (document.selection && document.selection.type != "Control") {
        selRange = document.selection.createRange();
        testRange = selRange.duplicate();

        testRange.moveToElementText(el);
        testRange.setEndPoint("EndToStart", selRange);
        atStart = (testRange.text == "");

        testRange.moveToElementText(el);
        testRange.setEndPoint("StartToEnd", selRange);
        atEnd = (testRange.text == "");
    }

    return { atStart: atStart, atEnd: atEnd };
}

This is how I ended up solving this. 这就是我最终解决这个问题的方法。 My proposed solution above worked sometimes but there were way to many edge cases, so I ended up considering how much text was before or after the cursor, and if that was 0 characters, then I was at the start or end: 我上面提出的解决方案有时会工作但有很多边缘情况,所以我最终考虑了光标前后有多少文本,如果是0个字符,那么我就在开始或结束时:

handle_keydown = function(e) {
  // Get the current cusor position
  range = window.getSelection().getRangeAt(0)
  // Create a new range to deal with text before the cursor
  pre_range = document.createRange();
  // Have this range select the entire contents of the editable div
  pre_range.selectNodeContents(this);
  // Set the end point of this range to the start point of the cursor
  pre_range.setEnd(range.startContainer, range.startOffset);
  // Fetch the contents of this range (text before the cursor)
  this_text = pre_range.cloneContents();
  // If the text's length is 0, we're at the start of the div.
  at_start = this_text.textContent.length === 0;
  // Rinse and repeat for text after the cursor to determine if we're at the end.
  post_range = document.createRange();
  post_range.selectNodeContents(this);
  post_range.setStart(range.endContainer, range.endOffset);
  next_text = post_range.cloneContents();
  at_end = next_text.textContent.length === 0;
}

Still not entirely sure there are any other edge cases, as I'm not entirely sure how to unit test this, since it requires mouse interaction - there's probably a library to deal with this somewhere. 仍然不完全确定还有其他边缘情况,因为我不完全确定如何对其进行单元测试,因为它需要鼠标交互 - 可能有一个库来处理这个问题。

I figured out this pretty consistent and short method: 我想出了这个非常一致和简短的方法:

function isAtTextEnd() {
    var sel = window.getSelection(),
      offset = sel.focusOffset;
  sel.modify ("move","forward","character");
  if (offset == sel.focusOffset) return true;
  else {
    sel.modify ("move","backward","character");
    return false;
  }
}

The key: try to force move it one character forward - if it actually moved: not at the end (move it one character back), if it didn't - it's at the end (no need to move back, it didn't move). 关键:尝试强制向前移动一个角色 - 如果它实际移动:不在最后(将其移回一个角色),如果没有 - 它在最后(不需要向后移动,它没有移动)。
Implementing for start of text is the opposite, and is "left as an exercise for the reader"... 实现文本的开头是相反的,并且“留给读者练习”......

Cavities: 蛀牙:

  • MDN marks modify as "Non-standard", though the compatibility table shows a pretty wide support (tested to work on the latest Chrome and Firefox, according to the table - not supported in Edge). MDN标记modify为“非标准”,但兼容性表显示了相当广泛的支持(根据表格测试了最新的Chrome和Firefox,Edge不支持)。
    I tried using the more supported extend() for it - however, it seems that, weirdly, the extend does work even when at the end of text. 我尝试使用更受支持的extend() - 但是,奇怪的是,即使在文本末尾,扩展也能正常工作

  • If you check if after a user initiates a move of the caret (eg in a keyboard or mouse event handler) - you should handle cases where the check forces the caret to move in an unexpected way. 如果检查用户是否在启动插入符号移动后(例如,在键盘或鼠标事件处理程序中) - 您应该处理检查强制插入符以意外方式移动的情况。

I've had the same issue today with no clean solution, so I developed the following approach. 我今天遇到了同样的问题,没有干净的解决方案,所以我开发了以下方法。 It uses just Selection - no Range or vendor-specific features. 它仅使用Selection - 无Range或特定于供应商的功能。 It also takes newlines at the start and end of the content into account. 它还考虑了内容开头和结尾的换行符。

It works in current Chrome, Firefox, Safari and Opera. 它适用于当前的Chrome,Firefox,Safari和Opera。 Microsoft Edge again is the outlier since text selection itself is partially broken in contenteditable div s when there are newlines at the beginning or end of the content. Microsoft Edge再次出现异常,因为当内容的开头或结尾有换行符时,文本选择本身在contenteditable div部分被破坏。 Unfortunately I haven't found a workaround for that issue yet. 不幸的是,我还没有找到解决该问题的方法。

It's also worth noting that the logic is different not just between browsers but also between white-space modes ( normal vs. pre* ) because the browser will generate different nodes for each while typing. 值得注意的是,不仅浏览器之间的逻辑不同,而且white-space模式( normal模式与pre*模式pre* )之间的逻辑也不同,因为浏览器在打字时会为每个模式生成不同的节点。

 document.addEventListener("selectionchange", function() { updateCaretInfo(document.getElementById('input-normal')) updateCaretInfo(document.getElementById('input-pre')) }); function updateCaretInfo(input) { function isAcceptableNode(node, side) { if (node === input) { return true } const childProperty = side === 'start' ? 'firstChild' : 'lastChild' while (node && node.parentNode && node.parentNode[childProperty] === node) { if (node.parentNode === input) { return true } node = node.parentNode } return false } function isAcceptableOffset(offset, node, side) { if (side === 'start') { return offset === 0 } if (node.nodeType === Node.TEXT_NODE) { return offset >= node.textContent.replace(/\\n$/, '').length } else { return offset >= node.childNodes.length - 1 } } function isAcceptableSelection(selection, side) { return selection && selection.isCollapsed && isAcceptableNode(selection.anchorNode, side) && isAcceptableOffset(selection.anchorOffset, selection.anchorNode, side) } const selection = document.getSelection() const isAtStart = isAcceptableSelection(selection, 'start') const isAtEnd = isAcceptableSelection(selection, 'end') document.getElementById('start-' + input.id).innerText = isAtStart ? 'YES' : 'no' document.getElementById('end-' + input.id).innerText = isAtEnd ? 'YES' : 'no' } 
 body { padding: 10px; } [id^="input-"] { border: 1px solid black; display: inline-block; margin-bottom: 10px; padding: 5px; } 
 <div contenteditable id="input-normal">Move the caret inside here!</div> (<code>white-space: normal</code>) <p> Caret at start: <span id="start-input-normal">no</span><br> Caret at end: <span id="end-input-normal">no</span> </p> <hr> <div contenteditable id="input-pre" style="white-space: pre-wrap">Move the caret inside here!</div> (<code>white-space: pre-wrap</code>) <p> Caret at start: <span id="start-input-pre">no</span><br> Caret at end: <span id="end-input-pre">no</span> </p> 

Simple solution for checking if cursor/caret is at the end of an input: 检查游标/插入符号是否位于输入末尾的简单解决方案:

this.$('input').addEventListener('keydown', (e) => {
  if (e.target.selectionEnd == e.target.value.length) {
    // DO SOMETHING
  }
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM