【问题标题】:In JavaScript range selection is it possible to prevent selection of a partial node?在 JavaScript 范围选择中是否可以防止选择部分节点?
【发布时间】:2023-01-18 23:28:25
【问题描述】:

在 JavaScript 范围选择中是否可以防止选择部分节点?

例如:

“那只巨大的猫坐在非常小的地毯上。”

用户可能会选择““而且通常情况下,他们的鼠标选择不是那么精确,并且还包括两侧的可见空间,因此选择范围几乎总是包括”巨大的“ 和 ”坐着“这是我们不想要的。

每个跨度包含一个单词。单词之间的可见空间可以是 span 标签内的真正空白,用换行符堆叠的 span,它可以是填充,它也可以是 css 单词空间,甚至是一个不间断的空间。无论哪种方式,如果用户的选择无意中误入了另一个节点,默认当然是将节点作为选择的一部分返回。

如何避免这种情况?

欣然接受任何指点。

非常感谢你。

示例代码:

<span id="a1">The </span>
<span id="a2">enormous</span>
<span id="a3"> cat </span><span id="a4">sat</span>
<span id="a5"> on </span><span id="a6" style="padding-right: 2px;">the</span>
<span id="a7">very </span><span id="a8">small </span><span id="a9">rug</span><span id="a10">. </span>

【问题讨论】:

  • 您只想将选择限制为一个词,还是我遗漏了什么?
  • 您好,感谢您的回复。用户可能希望选择多个单词并且可能希望跨行。对不起,如果问题不清楚。我们想要的是保留所有已经被完全选中的词,并丢弃那些已经被包含的词,因为用户的选择已经越过了相邻词/节点的边界,尽管他们不可见,他们只是假设它是空白但实际上它包括那个相邻的跨度。这是我们希望避免的。
  • 好的,跨度节点是必需的吗?可以是纯文本吗?
  • 跨度或其他 html 标记是必要的,因为我们然后获取 id

标签: javascript jquery range selection


【解决方案1】:

我想我终于搞定了。让我们从选择范围中获取开始和结束跨度,并检查修剪后的跨度内容是否等于修剪后的跨度选择(在跨度中实际选择的部分)。如果在 span 中仅选择了空白或部分选择了文本,则将其从选择中排除。检查 parentNode !== SPAN 适用于仅选择空格的情况。

$(function() {
  $(document).on("mouseup", function(e) {
    const range = document.getSelection().getRangeAt(0);
    let start = range.startContainer.parentNode;
    let end = range.endContainer.parentNode;

    if (start === end) {
      if (start.parentNode.nodeName !== "SPAN") {
        start = null;
        end = null;
      } else {
        const spanContent = start.textContent.slice().trim();
        const spanSelection = range.endContainer.textContent.slice(0, range.endOffset).trim()
        
        if (spanContent !== spanSelection) {
          start = null;
          end = null;
        }
      }
    } else {
      if (end.nodeName !== "SPAN") {
        end = range.endContainer.previousElementSibling;
      } else if (range.endOffset > 0) {
        const spanContent = range.endContainer.textContent.trim();
        const spanSelection = range.endContainer.textContent.slice(0, range.endOffset).trim()
        
        if (!spanSelection || spanContent !== spanSelection) {
          end = end.previousElementSibling;
        }
      }
      if (start.nodeName !== "SPAN") {
        start = range.startContainer.nextElementSibling;
      } else if (range.startOffset > 0) {
        const spanContent = range.startContainer.textContent.trim();
        const spanSelection = range.startContainer.textContent.slice(range.startOffset).trim()

        if (!spanSelection || spanContent !== spanSelection) {
          start = start.nextElementSibling;
        }
      }
    }
    if (start) {
      const x = $(start).index();
      const y = $(end).index();
      const childs = $(start.parentNode).children();
      const result = [];
      
      for (let i = x; i <= y; i++) {
        result.push(childs.eq(i).text().trim());
      }
      console.log(result);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="test">
  <span id="a1">The </span>
  <span id="a2">enormous</span>
  <span id="a3"> cat </span><span id="a4">sat</span>
  <span id="a5"> on </span><span id="a6" style="padding-right: 2px;">the</span>
  <span id="a7">very </span><span id="a8">small </span><span id="a9">rug</span><span id="a10">. </span>
</div>

【讨论】:

  • 出色的。它是如此接近。唯一仍然存在问题的是,如果您以“The”开始选择并移至右侧的空白区域。它仍然返回“巨大”,但说实话,你是如此接近,这对我的目的来说已经足够了,我很乐意选择你的答案,但请随意继续努力哈哈。希望我能为此悬赏,你知道吗,在我看来,这实际上应该是在标签内选择文本时的默认行为。做得好。
【解决方案2】:

这是您可以使用Selection API构建的脚本

const container = document.getElementById("container");
const spans = container.querySelectorAll('span');
document.addEventListener('selectionchange', (e) => {
  const sel = window.getSelection();
  const start = sel.anchorNode.parentNode;
  const end = sel.focusNode.parentNode;
  const partialContainment = false;
  if (start != end) {
    console.log("Words: Start", start.textContent, "End:", end.textContent);
    let started = false;
    spans.forEach(span => {
      span.classList = "";

      console.log("containsNode partial", span.id, span.textContent, ':', sel.containsNode(span, partialContainment));
      if (span === start) {
        span.classList.add('start');
        started = true;
      } else if (span === end) {
        span.classList.add('end');
        started = false;
      } else if (started) {
        span.classList.add('middle');
      }
    })
    const middleSpans = [...document.querySelectorAll('span.middle')].map(span => ({
      [span.id]: span.textContent.trim()
    }));
    console.log(JSON.stringify(middleSpans))
  }
})
.start {
  color: green
}

.middle {
  color: orange
}

.end {
  color: red
}
<div id="container">
  <span id="a1">The </span>
  <span id="a2">enormous</span>
  <span id="a3"> cat </span><span id="a4">sat</span>
  <span id="a5"> on </span><span id="a6" style="padding-right: 2px;">the</span>
  <span id="a7">very </span><span id="a8">small </span><span id="a9">rug</span><span id="a10">. </span>
</div>

【讨论】:

  • 这很有趣,但展示了确切的问题。如果我选择“cat”并且你的脚本两边的空格也会选择节点“enormous”和“sat”?
  • @user3012857 你说的是选择,但是你是如何进行选择的呢?只需点击这个词...?通过双击单词...?通过在按住鼠标按钮的同时选择文本...?我承认我不太明白...
  • 是的,所以你有文件,你有选择,你有选择内的跨度。所以如果我理解你是正确的,你想删除所选跨度两侧的跨度,如果有 4 或 5 个跨度怎么办?)所以比较每个选定跨度的开始和结束,看看要删除哪个。我只是给了你工具,但你的用例太不清楚了,我无法给你整个脚本
  • 是的,我想是这样。请耐心等待,直到我可以在大约 30 分钟内完成测试
  • 我刚刚找到这篇关于 partialContainment 的文章“当为 true 时,containsNode() 在节点的一部分是选择的一部分时返回 true。当 false 时,containsNode() 仅在整个节点是选择的一部分时返回 true。如果未指定,默认值 false 被使用”似乎在我正在查看的区域中,但我不知道如何在其中工作或者它是否不适用于这种情况
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-03
  • 1970-01-01
  • 2012-05-18
  • 2011-11-26
  • 1970-01-01
  • 2012-08-13
  • 1970-01-01
相关资源
最近更新 更多