选择所有跨文本时如何获取 <span> 元素?

How to get <span> element when all spanned text selected?

在内容可编辑的 div 中,我想用 <span> 包围 selected 文本,或者当我 select 时修改现有的 <span> ] 其中的所有文字。第一个我可以,第二个不行

在第二种情况下,如果我有 A<span>B</span>C 和 select B,selection 将 anchorNode 显示为 AfocusNodeC,以及 toString() returns B。父节点是封闭的段落,而不是 <span>。我找不到任何方法来区分 A<span>B</span>CABC 中的 selecting B,因为我找不到任何方法来发现 [=12] 的存在=] 在第一种情况下包围 B 的元素。肯定有办法做到这一点,对吧?谁能告诉我怎么做?

代码如下:

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

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


在代码片段中,伦敦和巴黎都用 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>

对于上述问题,我想出了一个相当糟糕的解决方法。为了测试选择是否是整个 <span> 元素,我克隆了范围,当它是整个范围时,它包含前后为空字符串的范围。所以我要做的是删除其中任何一个,并检查我剩下的是否是单个 span 元素。如果是这种情况,我将在原始范围开始后取下一个元素,即 <span>:

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

这可行,但不是很吸引人。如果有人有更好的解决方案,我会很高兴。