window.getSelection().getRange(0) 在文本被 <mark> 包裹时不起作用

window.getSelection().getRange(0) does not work when text is wrapped by <mark>

我正在尝试使用 window.getSelection().getRangeAt(0) 获取句子中所选单词的索引。它在没有任何 <mark><abbr> 的文本中工作正常。但是当一个句子中有这样的标签时,好像这个函数会把句子分成几段。

例如,HTML中的一个句子看起来像My car <abbr title="car_state"><mark>broke down</mark></abbr>. What do I do?

当我选择 broke down 之前的文本时,它工作正常。但是当我选择之后的文本时,例如,e What,它将在 2 处给出 startOffset 而不是 22

是否可以获取整个句子的索引?

受Kaiido的回答启发,下面的方法就可以了。 虽然高亮的文字不匹配,反正我也不需要高亮的文字

请随时添加有关解决方案的评论。

运行 示例

$('#selected_text').click(function(){
var text = "My car is broke down. What do I do?";
var range = window.getSelection().getRangeAt(0);
var start = range.startOffset;
var end = range.endOffset;
var extra = 0;
var selected_string = range.toString();

var t = $('span').contents();
for(var i = 0; i < t.length; i++){
  console.log(extra);
  if(t[i].wholeText === undefined){
    extra += t[i].textContent.length;
  }else if(t[i].wholeText.includes(selected_string)){
    break;
  }else{
    extra += t[i].length;
  }
}

start += extra;
end += extra;
console.log("start index: " + start);
console.log("end index: " + end);
console.log(text.slice(start, end));
console.log(selected_string);
console.log("match: ", (selected_string === text.slice(start, end)));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<span>My car is <abbr title="car_state"><mark>broke down</mark></abbr>. What do <mark>I</mark> d<mark>o</mark>?</span>
<button id='selected_text'>show selected text</button>
</body>
</html>

Quoting MDN

The Range.startOffset read-only property returns a number representing where in the startContainer the Range starts.

Range.endOffset 也是如此,returns 在 endContainer.

中的位置

当您 select 页面中的单词 What 时,startContainer 是在您的 </abbr> 之后开始的 TextNode。所以你得到的索引是相对于这个TextNode的。

如果您想获得 selected 文本,只需调用 Selection.toString() 方法即可。

$('#selected_text').click(function() {

  var sel = window.getSelection();
  var range = sel.getRangeAt(0);
  var start = range.startOffset;
  var end = range.endOffset;
  console.log("start index: " + start);
  console.log("end index: " + end);
  console.log('startContainer', range.startContainer.nodeName, range.startContainer.textContent);
  console.log('endContainer', range.endContainer.nodeName, range.endContainer.textContent);
  console.log('toString:', sel.toString());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<span>My car is <abbr title="car_state"><mark>broke down</mark></abbr>. What do I do?</span>
<button id='selected_text'>show selected text</button>

如果您知道公共容器并想知道您相对于此祖先文本内容的位置,那么您必须遍历其子节点,直到找到 startContainer 和 endContainer。

var container = $('#container')[0];
$('#selected_text').click(function() {

  var sel = window.getSelection();
  var range = sel.getRangeAt(0);
  var sel_start = range.startOffset;
  var sel_end = range.endOffset;
  
  var charsBeforeStart = getCharactersCountUntilNode(range.startContainer, container);
  var charsBeforeEnd = getCharactersCountUntilNode(range.endContainer, container);
  if(charsBeforeStart < 0 || charsBeforeEnd < 0) {
    console.warn('out of range');
    return;
  }
  var start_index = charsBeforeStart + sel_start;
  var end_index = charsBeforeEnd + sel_end;
  console.log('start index', start_index);
  console.log('end index', end_index);
  console.log(container.textContent.slice(start_index, end_index));
});

function getCharactersCountUntilNode(node, parent) {
  var walker = document.createTreeWalker(
    parent || document.body,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );
  var found = false;
  var chars = 0;
  while (walker.nextNode()) {
    if(walker.currentNode === node) {
      found = true;
      break;
    }
    chars += walker.currentNode.textContent.length;
  }
  if(found) {
    return chars;
  }
  else return -1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<span id="container">My car is <abbr title="car_state"><mark>broke down</mark></abbr>. What do I do?</span>
<button id='selected_text'>show selected text</button>