查找给定插入符位置的Node(javascript)

时间:2019-07-11 13:15:47

标签: javascript

我有一个HTML文件,并且我有一个插入符号的位置。使用JS,我需要找到插入符号位于给定位置的html节点。

function findNodeForCaretPosition(caretPosition) {
    var node = null; // type Node (https://developer.mozilla.org/en-US/docs/Web/API/Node)
  // TODO find node
  return node;
}


HTML示例

<html>
<body>
    <div>
        <p id="a">Time waits for no man. Unless that man is Chuck Norris.</p>
    </div>
    <div>
        <p id="b">Chuck Norris can touch <span id="c" style="color:blue">MC Hammer</span>.</p>
    </div>
</body>
</html>


HTML纯文本示例

  

时间在等着没有人。除非那个人是查克·诺里斯(Chuck Norris)。

     

查克·诺里斯(Chuck Norris)可以接触MC Hammer。


测试

Caret位置= 4(时间|等待)
答案= <p id="a">

Caret位置= 16(可以触摸)。
答案= <p id="b">

Caret位置= 25(MC |锤)。
答案= <span id="c">

插入位置= 1000),没有答案(null)。

1 个答案:

答案 0 :(得分:1)

我已经对代码进行了很多评论,因此我认为它不需要更多的解释。只需使用父节点和索引作为参数调用caretPosition。由于JavaScript中的字符串是UTF-16,因此任何表情符号或非ASCII字符都应视为一个字符而不是多个字符。

值得注意的是,空格很重要。因此,问题中的HTML从技术上讲是行不通的,因为它首先会计算换行符和前导空格。为了简单起见,我将其删除。

如果您有任何疑问,请在评论中告诉我。

/**
 * @param {Element} parent
 * @param {number} index
 */
function caretPosition(parent, index) {
  // The index is too large to fit in the element, return `null` per requirements.
  // We also return `null` if the element isn't a text or element node,
  // as there is no text to check against.
  if (
    (parent.nodeType === Node.ELEMENT_NODE && index > parent.innerText.length)
    || (parent.nodeType === Node.TEXT_NODE && index > parent.data.length)
    || ![Node.ELEMENT_NODE, Node.TEXT_NODE].includes(parent.nodeType)
  ){
    return null;
  }

  // The length of all text combined to this point
  // (zero is the beginning of `parent`).
  let combinedLength = 0;

  // Iterate over the children until we find
  // the element where we cross the boundry.
  for (const child of parent.childNodes) {
    // Store this in case we need to recurse.
    const previousLength = combinedLength;

    // For the current child, add the length of its text content.
    // As text and element nodes don't share a common property,
    // we need to explicitly check for both. Other node types
    // (such as comments) are irrelevant to the task at hand.
    if (child.nodeType === Node.TEXT_NODE) {
      combinedLength += child.data.length;
    } else if (child.nodeType === Node.ELEMENT_NODE) {
      combinedLength += child.innerText.length;
    } else {
      // We don't have a text or element node,
      // so there's no text that we could care about.
      // The recursive case will handle the fact that nothing changed,
      // and will return `null`.
      continue;
    }

    // Our cursor is inside or at the end of this node.
    if (index <= combinedLength) {
      // We are in a text node and have nothing to recurse on.
      // Return the parent element of the text node,
      // which is a DOM element.
      if (child.nodeType === Node.TEXT_NODE) {
        return child.parentElement;
      }

      // If we are in an element node, then we have no children.
      // Without children, there is nothing to recurse on;
      // we should return the element.
      // If we are _not_ in an element node,
      // this will be `false`, and we will enter the recursive case.
      else if (child.childElementCount === 0) {
        return child;
      }

      // We have children to iterate over, so do that.
      // It is necessary to change the index to search for
      // as we've got a new reference frame.
      return caretPosition(child, index - previousLength);
    }
  }
}

// Make sure everything works!

const div = document.querySelectorAll('div');

console.assert(caretPosition(div[0], 4) === document.querySelector('#a'));
console.assert(caretPosition(div[1], 16) === document.querySelector('#b'));
console.assert(caretPosition(div[1], 25) === document.querySelector('#c'));
console.assert(caretPosition(div[0], 1000) === null);
console.assert(caretPosition(div[1], 1000) === null);
<div><p id='a'>Time waits for no man. Unless that man is Chuck Norris.</p></div>
<div><p id='b'>Chuck Norris can touch <span id='c' style='color:blue'>MC Hammer</span>.</p></div>