contenteditable 文本选择不起作用

contenteditable selection of text not working

我面临以下问题:当我尝试在 contenteditable 元素中 select 文本并且 selection 的末尾是元素内容的开始时,没有select 事件已触发,并且没有 SelectionRange 个对象。

有人可以就为什么会发生这种情况或如何防止这种情况给我任何建议吗?

负责获取select离子范围的代码:

$('div[contenteditable="true"]').bind("mouseup keyup touchend", function() {
  lastCaretIndex = getSelectionRange();
});

function getSelectionRange() {
  var sel;
  if (window.getSelection) {
    sel = window.getSelection();

    console.log(sel); // this doesn't print anything event empty string

    if (sel.rangeCount) {
      return sel.getRangeAt(0);
    }
  } else if (document.selection) {
    return document.createRange();
  }

  return null;
}
<div id="main-input" contenteditable="true">Hello world!</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

JSFiddle(打开浏览器控制台以确保 selection 不会被记录)。

问题是您仅在 contenteditable 元素上发生特定事件时才记录选择更改。更具体地说,您有

$('div[contenteditable="true"]').bind("mouseup keyup touchend", // ...

特别是 mouseup 事件通常会在选择更改时触发。除非没有。当您在可编辑 div 之外释放鼠标(您在示例中这样做!),那么 div 将永远不会收到 mouseup 事件,因此永远不会记录选择。

有两种解决方法:

  1. 监听整个 body 上的事件。缺点是您会收到更多不影响选择的事件,并且仍然有可能在页面外收到 mouseup 个事件。
  2. 监听 selectionchange 事件。

document.addEventListener('selectionchange', function(event) {
  console.log(event.type);
});
<div contenteditable="true">Hello world!</div>

您当然仍然可以像当前在此事件处理程序中那样访问选择。每次选择更改时都会触发此事件,因此您可能希望 throttle 它。

可以在下面找到完整的实现。

function handler() {
  // do whatever you want here
  // this shows the selection and all ranges it consists of
  var sel = window.getSelection(),
      ranges = Array(sel.rangeCount).fill(0).map((_, i) => sel.getRangeAt(i));
  ranges = ranges.map((r) => `${r.startOffset}-${r.endOffset}`).join(';');
  console.log(`Selection [${ranges}:"${sel.toString()}"]`);
}

function throttle(func) {
  var timeoutId = false,
      called = false,
      wrap = function() {
        if (!called) {
          clearInterval(timeoutId);
          timeoutId = false;
        } else {
          func();
        }
        called = false;
      };
  return function() {
    if (timeoutId === false) {
      func();
      timeoutId = setInterval(wrap, 500);
    } else {
      called = true;
    }
  };
}

document.addEventListener('selectionchange', throttle(handler));
<div contenteditable="true">Hello world!</div>

您的实际代码可以完美运行并在控制台中记录一个 Selection 对象,即使选择的结束是元素内容的开始。

确实,您需要记录此 Selection 文本而不是记录整个 object,它反映了每个事件的整个选择对象更改。

我更新了您的代码段以使用 Selection.toString() 记录选择的文本,您可以在此处看到它的工作原理:

$('div[contenteditable="true"]').bind("mouseup keyup touchend", function() {
  lastCaretIndex = getSelectionRange();
});

function getSelectionRange() {
  var sel;
  if (window.getSelection) {
    sel = window.getSelection();

    console.log(sel.toString()); // this doesn't print anything event empty string

    if (sel.rangeCount) {
      return sel.getRangeAt(0);
    }
  } else if (document.selection) {
    return document.createRange();
  }

  return null;
}
<div id="main-input" contenteditable="true">Hello world!</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

您可以查看 this answer,它显示并解释了获取文本选择的完美方式。