【问题标题】:How to get <span> element when all spanned text selected?选择所有跨区文本时如何获取 <span> 元素?
【发布时间】:2020-11-06 14:22:08
【问题描述】:

在内容可编辑的 div 中,我想用 &lt;span&gt; 包围选定的文本,或者当我选择其中的所有文本时修改现有的 &lt;span&gt;。我可以做第一个,但不能做第二个。

在第二种情况下,如果我有 A&lt;span&gt;B&lt;/span&gt;C 并选择 B,则选择将 anchorNode 显示为 A,将 focusNode 显示为 C,并且 toString() 返回 B。父节点是封闭段落,而不是&lt;span&gt;。我找不到任何方法来区分在A&lt;span&gt;B&lt;/span&gt;CABC 中选择B,因为我找不到任何方法来发现在第一种情况下B 周围存在&lt;span&gt; 元素。一定有办法做到这一点,当然?谁能告诉我该怎么做?

代码如下:

var sel = window.getSelection();
var range = sel.getRangeAt(0);
var selected = range.compareBoundaryPoints(range.START_TO_END, range);
if (selected > 0) {    // some text is selected
  var el = range.commonAncestorContainer;
  //
  // would expect to find el == <span> if selection is B
  // in <p>A<span>B</span>C<p>, but find <p> instead!
  //
  //... code to surround selection with new span or to process
  //    existing span... but there is never an existing span!
}

【问题讨论】:

  • 您要在什么环境中执行此操作?你有更多代码吗?
  • 在上面添加了一些代码。我在做什么:使用 Firefox,双击内容可编辑 div 内段落中的一个单词,添加一个跨度(有效),然后双击同一个单词,尝试修改我刚刚插入的跨度,然后在分配给“el”之后,在浏览器的调试器中放置一个断点。 “el”的值只有当我单击按钮以在插入后立即修改跨度时才显示跨度——如果我单击其他地方,然后返回并再次双击该单词,则无法找到跨度,甚至尽管选择的文本完全在其中。
  • 因为没有找到span,所以我最终得到了

    ABC

标签: html contenteditable


【解决方案1】:

Range.commonAncestorContainer 有一个名为 parentElement 的属性。它是所选文本的父级。


在代码 sn-p 中,伦敦和巴黎都被 span 元素包裹。

const text = "<p>Hello from <span>London</span> and <span>Paris</span></p>"
const parser = new DOMParser();
const html = parser.parseFromString(text, "text/html").body.innerHTML
const editor = document.querySelector('div[contenteditable]')
editor.innerHTML = html


function getParent() {
  var selection = window.getSelection();
  if (selection.rangeCount) {
    const range = selection.getRangeAt(0);
    const parent = range.commonAncestorContainer.parentElement;

    document.querySelector("#result").innerText = parent instanceof HTMLSpanElement
    if (parent instanceof HTMLSpanElement) {
      console.log(parent)
    }
  }
}


document.querySelector("button").onclick = getParent;
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div class="container py-5">
  <div class="row align-items-center">
    <div class="col">
      <div class="w-100 p-5 border" contenteditable>

      </div>
    </div>
    <div class="col">
      <button class="btn btn-primary">Get Text</button>
    </div>
  </div>
  <div class="row align-items-center">
    <div class="col">
      <p>Is Is parent instanceof HTMLSpanElement?  <span id="result"></span> </p>
    </div>
  </div>
</div>

【讨论】:

  • 这仅适用于您选择跨度内的文本,而不是整个跨度内容。例如。如果您选择整个单词“London”,则共同祖先是包含

    ,而 parentElement 则是包含

    。我正在尝试处理选择是整个跨度内容的情况。似乎当您在上面选择“伦敦”时,实际上是在选择“伦敦”,并且似乎没有任何简单的方法可以区分“伦敦”和“伦敦"
  • 考虑通过在 中换行来更改字体颜色。如果文本不是整个跨度内容(例如“伦敦”内的“ondo”),那么我将其包装起来,例如London。如果我选择整个跨度,我最终会得到 London,它仍然是蓝色的,因为我是包裹在现有跨度之外而不是内部。在这种情况下,我只想将封闭范围从蓝色更改为红色。
  • @user1636349 London 是 instanceof Text,而 London 是 instanceof HTMLSpanElement。如果您使用示例 [action, expected-result] 添加所有场景,我会帮助您。如果您这样做,请将其包含在您的问题中。
  • 好吧,在您的示例中,如果您选择“伦敦”一词并单击“获取文本”,则不会发生任何事情(因为选择了整个跨度,并且其父项是

    ),但是如果您从单词中间选择“ondo”,它会显示“London”。我希望如果你从单词中间选择“ondo”,它会显示“ondo”,如果你选择“London”,它会显示“London”。

  • 正如我在下面所说的,我有一个解决方法,但它完全是一个杂项。因此,我们将非常感谢您提供更简洁的解决方案!
【解决方案2】:

对于上述问题,我想出了一个相当可怕的解决方法。为了测试所选内容是否是整个&lt;span&gt; 元素,我克隆了该范围,当它是整个跨度时,该范围包含前后是空字符串的跨度。所以我要做的是删除其中的任何一个,并检查我剩下的是否是单个跨度元素。如果是这种情况,我会在原始范围开始后取下一个元素,即&lt;span&gt;

var el = range.commonAncestorContainer;
var copy = range.cloneContents();
while (copy.childNodes.length > 1 && copy.firstChild.nodeValue === "") {
  copy.removeChild(copy.firstChild);
}
while (copy.childNodes.length > 1 && copy.lastChild.nodeValue === "") {
  copy.removeChild(copy.lastChild);
}
if (copy.childNodes.length == 1 && copy.childNodes[0] instanceof HTMLSpanElement) {
  el = range.startContainer.nextSibling;
}

这行得通,但它不是很吸引人。如果有人有更简洁的解决方案,我会很高兴。

【讨论】:

    猜你喜欢
    相关资源
    最近更新 更多
    热门标签