文档:caretPositionFromPoint() 方法

可用性有限

此特性不是基线特性,因为它在一些最广泛使用的浏览器中不起作用。

Document 接口的 caretPositionFromPoint() 方法返回一个 CaretPosition 对象,其中包含 DOM 节点以及该节点内插入符号和插入符号的字符偏移量。

语法

js
caretPositionFromPoint(x, y)
caretPositionFromPoint(x, y, options)

参数

x

点的水平坐标。

y

点的垂直坐标。

options 可选

还可以指定以下可选属性。

shadowRoots 可选

ShadowRoot 对象的数组。该方法可以返回一个在所提供的 shadow root 的 shadow DOM 中定义的节点的插入符号位置。如果插入符号位置落在未提供的 shadow root 中,则返回的 CaretPosition 将重新映射到作为 shadow root 主机的节点。

返回值

一个 CaretPosition 对象或 null

如果文档没有关联的视口,如果 xy 为负值或超出视口区域,或者如果坐标指示的点无法插入文本插入点指示器,则返回值为 null

示例

在 DOM 中插入符号位置处拆分文本节点

此示例演示如何从选定的 DOM 节点获取插入符号位置,使用该位置拆分节点,并在两个节点之间插入换行符。该示例使用 caretPositionFromPoint()(如果支持)获取插入符号位置,并以非标准的 Document.caretRangeFromPoint() 方法作为备用。

请注意,代码的某些部分是隐藏的,包括用于日志记录的代码,因为这对于理解此方法没有用。

HTML

HTML 定义了一个段落文本。

html
<p>
  Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
  eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
  voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
  kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>

JavaScript

下面的方法首先检查 document.caretPositionFromPoint 支持,并使用它获取插入符号位置的文本节点和偏移量。如果浏览器不支持该方法,则代码会检查 document.caretRangeFromPoint,并改用它。

如果插入符号位置处的节点是文本节点,则代码会在选定的偏移量处将该节点拆分为两个,并在两个节点之间插入换行符。

js
function insertBreakAtPoint(e) {
  let range;
  let textNode;
  let offset;

  if (document.caretPositionFromPoint) {
    range = document.caretPositionFromPoint(e.clientX, e.clientY);
    textNode = range.offsetNode;
    offset = range.offset;
  } else if (document.caretRangeFromPoint) {
    // Use WebKit-proprietary fallback method
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
    textNode = range.startContainer;
    offset = range.startOffset;
  } else {
    // Neither method is supported, do nothing
    return;
  }

  // Logging code (uses hidden method to get substring with ^ at offset)
  if (textNode?.nodeType === 3) {
    const caretInText = getSubstringAroundOffset(textNode.textContent, offset);
    log(
      `node: ${textNode.nodeName}, offset: ${offset}, insert: ${caretInText}`,
    );
  }

  // Only split TEXT_NODEs
  if (textNode?.nodeType === 3) {
    let replacement = textNode.splitText(offset);
    let br = document.createElement("br");
    textNode.parentNode.insertBefore(br, replacement);
  }
}

该方法被添加为任何段落元素的点击事件处理程序。

js
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
  paragraph.addEventListener("click", insertBreakAtPoint);
}

结果

单击下面 Lorem ipsum ... 段落中的任意位置,可在您单击的点插入换行符。请注意,日志显示 nodeName、偏移量以及选定节点的片段,其中在偏移量处带有 ^ 字符。

在 Shadow DOM 中在插入符号位置处拆分文本节点

此示例演示如何从 shadow root 中的选定节点获取插入符号位置。该示例与上面的纯 DOM 示例非常相似,不同之处在于部分文本位于 shadow root 内部。我们提供了一个按钮,允许您查看在将 shadow root 传递/未传递给 caretPositionFromPoint() 时之间的区别。

请注意,代码的某些部分是隐藏的,包括用于日志记录的代码,因为这对于理解此方法没有用。

HTML

HTML 定义了一个 <div> 元素内部的段落文本。该段落包含一个 <span> 元素,其 id 为“host”,我们将用作 shadow root 的主机。还有一些按钮,我们将用它们来重置示例,并向 caretPositionFromPoint() 添加/删除 shadow root 选项参数。

html
<button id="reset" type="button">Reset</button>
<button id="shadowButton" type="button">Add Shadow</button>
<div>
  <p>
    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
    eirmod tempor invidunt ut <span id="host"></span> labore et dolore magna
    aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
    dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
    Lorem ipsum dolor sit amet.
  </p>
</div>

CSS

在这里,我们使用 CSS 使 #host 元素变为红色和粗体。这使得区分 DOM 中的文本和 shadow DOM 中的文本更容易。

css
#host {
  color: red;
  font-weight: bold;
}

JavaScript

首先,我们有一些代码来填充我们的 shadow DOM。我们正在使用 JavaScript 动态附加 shadow root,因为 MDN 示例系统不允许我们使用 <template> 元素声明性地执行此操作。shadow DOM 的内容是一个 <span> 元素,其中包含文本“I'm in the shadow DOM”。

js
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
const shadowSpan = document.createElement("span");
shadowSpan.textContent = "I'm in the shadow DOM";
shadow.appendChild(shadowSpan);

接下来,我们为“启用/禁用阴影”按钮添加一个处理程序。此代码切换 useShadows 变量的值并相应地更新按钮文本。

js
let useShadows = false;

const shadowButton = document.querySelector("#shadowButton");
shadowButton.addEventListener("click", () => {
  useShadows = !useShadows;
  shadowButton.innerText = useShadows ? "Remove Shadow" : "Add Shadow";
});

下面的方法首先检查 document.caretPositionFromPoint 支持,并使用它获取插入符号位置的文本节点和偏移量。useShadows 变量的值用于确定是否将我们文本中托管的 shadow root 传递给 caretPositionFromPoint()

  • 如果浏览器不支持该方法,则代码会检查 document.caretRangeFromPoint,并改用它。
  • 如果插入符号位置处的节点是文本节点,则代码会在选定的偏移量处拆分节点,并在它们之间插入换行符。
  • 如果节点是元素节点,则代码会在偏移量处插入一个换行符元素节点。
js
function insertBreakAtPoint(e) {
  let range;
  let textNode;
  let offset;

  if (document.caretPositionFromPoint) {
    range = document.caretPositionFromPoint(
      e.clientX,
      e.clientY,
      useShadows ? { shadowRoots: [shadow] } : null,
    );
    textNode = range.offsetNode;
    offset = range.offset;
  } else if (document.caretRangeFromPoint) {
    // Use WebKit-proprietary fallback method
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
    textNode = range.startContainer;
    offset = range.startOffset;
  } else {
    // Neither method is supported, do nothing
    return;
  }

  // Logging code (uses hidden method to get substring with ^ at offset)
  if (textNode) {
    if (textNode.nodeType === 3) {
      const caretInText = getSubstringAroundOffset(
        textNode.textContent,
        offset,
      );
      log(
        `type: TEXT_NODE, name: ${textNode.nodeName}, offset: ${offset}:
${caretInText}`,
      );
    } else if (textNode.nodeType === 1) {
      log(`type: ELEMENT_NODE, name: ${textNode.nodeName}, offset: ${offset}`);
    } else {
      log(
        `type: ${textNode.nodeType}, name: ${textNode.nodeName}, offset: ${offset}`,
      );
    }
  }

  // Insert line at caret
  if (textNode?.nodeType === 3) {
    // TEXT_NODE - split text at offset and add br
    let replacement = textNode.splitText(offset);
    let br = document.createElement("br");
    textNode.parentNode.insertBefore(br, replacement);
  } else if (textNode?.nodeType === 1) {
    // ELEMENT_NODE - Add br node at offset node
    let br = document.createElement("br");
    const targetNode = textNode.childNodes[offset];
    textNode.insertBefore(br, targetNode);
  } else {
    // Do nothing
  }
}

最后,我们分别为 DOM 和 shadow root 中的段落元素添加两个点击事件处理程序。请注意,我们需要专门查询 shadowRoot 中的元素,因为它们对正常的 DOM 查询方法不可见。

js
// Click event handler <p> elements in the DOM
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
  paragraph.addEventListener("click", insertBreakAtPoint);
}

// Click event handler <p> elements in the Shadow DOM
const shadowParagraphs = host.shadowRoot.querySelectorAll("p");
for (const paragraph of shadowParagraphs) {
  console.log(paragraph);
  paragraph.addEventListener("click", insertBreakAtPoint);
}

结果

单击 shadow DOM 文本之前或之后 Lorem ipsum ... 段落中的任意位置,可在您单击的点插入换行符。请注意,在这种情况下,日志显示您已选择 TEXT_NODE、偏移量以及选定节点的片段,其中在偏移量处带有 ^ 字符。

最初,shadow root 未传递给 caretPositionFromPoint(),因此如果您单击文本“I'm in the shadow DOM”,返回的插入符号位置节点是主机的父节点,位于 shadow root 的偏移量处。因此,换行符被添加到节点之前,而不是您选择的点。请注意,在这种情况下,插入符号位置节点的类型为 ELEMENT_NODE

如果您单击“添加阴影”按钮,则 shadow root 将传递给 caretPositionFromPoint(),因此返回的插入符号位置是 shadow DOM 中特定的选定节点。这使得 shadow DOM 文本的行为与其他段落文本类似。

规范

规范
CSSOM 视图模块
# dom-document-caretpositionfrompoint

浏览器兼容性

另见