【问题标题】:Contenteditable DIV - how can I determine if the cursor is at the start or end of the contentContenteditable DIV - 如何确定光标是在内容的开头还是结尾
【发布时间】:2011-11-19 01:41:35
【问题描述】:

我有一个 contenteditable div,其中包含典型的所见即所得编辑器 html(粗体、锚点、列表)。

我需要确定当前光标是onKeyDown,是在div的开头还是结尾。这样做的原因是,根据光标位置和按下的键,我可能想在退格键上将此 div 与前一个 div 合并,或者在 enter 时创建一个新的以下 div。

我一直在摆弄范围,但是当您在元素内部使用 html 时,事情变得相当复杂。

我希望我必须忽略一些简单的解决方案。

是否有一种相对简单的方法来确定这一点 - 我愿意使用像 Rangy 这样的库。

谢谢!

编辑:我在想一些事情:

$('.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)
}

我会遇到这样的奇怪问题吗?

【问题讨论】:

    标签: javascript html dom contenteditable rangy


    【解决方案1】:

    除了使用Range 对象的toString() 方法而不是cloneContents() 来避免不必要的克隆之外,我会使用与您类似的方法。此外,在 IE TextRange 的 text 属性使用类似的方法。

    请注意,当内容中有前导和/或尾随换行符时,这会出现问题,因为范围的 toString() 方法与节点的 textContent 属性一样工作,并且只考虑文本节点,因此不考虑到<br> 或块元素隐含的换行符。也不考虑 CSS:例如,包含通过 display: none 隐藏的元素内的文本。

    这是一个例子:

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

    代码:

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

    【讨论】:

    • 完美运行。处理 div 中的新行。
    【解决方案2】:

    这就是我最终解决这个问题的方式。我上面提出的解决方案有时有效,但有很多边缘情况,所以我最终考虑了光标之前或之后有多少文本,如果那是 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;
    }
    

    仍然不完全确定还有其他边缘情况,因为我不完全确定如何对此进行单元测试,因为它需要鼠标交互 - 可能有一个库可以在某个地方处理这个问题。

    【讨论】:

    • 这应该是相当健壮的,除了 IE
    • 请注意,此解决方案不考虑换行符。如果光标和结尾之间的唯一文本是一系列换行符,则会错误地认为光标位于 div 的末尾。空行总是用<br> 表示,但不能只在post_range.cloneContents() 中查找<br>s,因为如果当前行是最后一行,它包括当前行的<br>。此外,在 Chrome 中,cloneContents() 恰好是 <div><br/></div>,如果您在最后一行并且如果您在倒数第二行并且最后一行是空的。因此,仅靠范围无法解决这个问题。
    • 这不应该是答案,因为它不能解决@twhb 概述的全部问题。应该考虑更全面的解决方案。
    【解决方案3】:

    我想出了这个非常一致且简短的方法:

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

    关键:尝试强制将其向前移动一个字符 - 如果它确实移动了: 不在末尾(将其向后移动一个字符),如果没有 - 它在末尾(无需向后移动,它没有移动)。
    实现文本开头是相反的,并且“留给读者作为练习”......

    蛀牙:

    • MDN 将 modify 标记为“非标准”,尽管兼容性 表显示了相当广泛的支持(根据表测试,可以在最新的 Chrome 和 Firefox 上运行 - Edge 不支持)。
      我尝试为它使用更受支持的 extend() - 然而,奇怪的是,即使在文本末尾,扩展 确实有效

    • 如果您在用户启动插入符号移动后检查(例如在键盘或鼠标事件处理程序中) - 您应该处理检查强制插入符号以意外方式移动的情况。

    【讨论】:

    • 到目前为止这对我有用,非常简单和聪明的想法
    • 你这个男人。卡在这上面一段时间了。效果很好。
    【解决方案4】:

    我今天遇到了同样的问题,但没有干净的解决方案,所以我开发了以下方法。它只使用Selection - 没有Range 或供应商特定的功能。它还考虑了内容开头和结尾处的换行符。

    它适用于当前的 Chrome、Firefox、Safari 和 Opera。 Microsoft Edge 再次成为异常值,因为当内容的开头或结尾处有换行符时,文本选择本身在 contenteditabledivs 中被部分破坏。不幸的是,我还没有找到解决该问题的方法。

    同样值得注意的是,不仅浏览器之间的逻辑不同,white-space 模式之间的逻辑也不同(normalpre*),因为浏览器在输入时会为每种模式生成不同的节点。

    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>

    【讨论】:

      【解决方案5】:

      检查光标/插入符号是否位于输入末尾的简单解决方案:

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

      【讨论】:

      • 不用于输入,用于可满足。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-19
      • 1970-01-01
      • 2010-12-08
      • 1970-01-01
      • 2012-11-10
      相关资源
      最近更新 更多