Document: phương thức caretPositionFromPoint()

Baseline 2025 *
Newly available

Since December 2025, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

* Some parts of this feature may have varying levels of support.

Phương thức caretPositionFromPoint() của giao diện Document trả về một đối tượng CaretPosition, chứa nút DOM, cùng với vị trí caret và offset ký tự của caret trong nút đó.

Cú pháp

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

Tham số

x

Tọa độ ngang của một điểm.

y

Tọa độ dọc của một điểm.

options Optional

Các thuộc tính tùy chọn sau cũng có thể được chỉ định.

shadowRoots Optional

Một mảng các đối tượng ShadowRoot. Phương thức có thể trả về vị trí caret cho một nút được định nghĩa trong shadow DOM của một shadow root được cung cấp. Nếu vị trí caret nằm trong một shadow root không được cung cấp, CaretPosition trả về sẽ được ánh xạ lại đến nút là host của shadow root.

Giá trị trả về

Một đối tượng CaretPosition hoặc null.

Giá trị trả về là null nếu không có viewport nào được liên kết với tài liệu, nếu x hoặc y là số âm hoặc nằm ngoài vùng viewport, hoặc nếu tọa độ chỉ đến một điểm mà không thể chèn con trỏ chèn văn bản.

Ví dụ

Tách các nút văn bản tại vị trí caret trong DOM

Ví dụ này minh họa cách lấy vị trí caret từ một nút DOM đã chọn, sử dụng vị trí để tách nút và chèn dấu ngắt dòng giữa hai nút. Ví dụ sử dụng caretPositionFromPoint() để lấy vị trí caret nếu được hỗ trợ, với phương thức không chuẩn Document.caretRangeFromPoint() làm dự phòng.

Lưu ý rằng một số phần của mã được ẩn, bao gồm mã dùng để ghi log, vì điều này không hữu ích để hiểu phương thức này.

HTML

HTML định nghĩa một đoạn văn bản.

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

Phương thức dưới đây trước tiên kiểm tra hỗ trợ document.caretPositionFromPoint và sử dụng nó để lấy nút văn bản và offset tại vị trí caret. Nếu trình duyệt không hỗ trợ phương thức đó, mã sau đó kiểm tra document.caretRangeFromPoint, và sử dụng phương thức đó thay thế.

Nếu nút tại vị trí caret là một nút văn bản, mã sẽ tách nút thành hai tại offset đã chọn, và chèn dấu ngắt dòng giữa hai nút.

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) {
    // Sử dụng phương thức dự phòng riêng của WebKit
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
    textNode = range.startContainer;
    offset = range.startOffset;
  } else {
    // Không phương thức nào được hỗ trợ, không làm gì cả
    return;
  }

  // Mã ghi log (sử dụng phương thức ẩn để lấy chuỗi con với ^ tại offset)
  if (textNode?.nodeType === 3) {
    const caretInText = getSubstringAroundOffset(textNode.textContent, offset);
    log(
      `node: ${textNode.nodeName}, offset: ${offset}, insert: ${caretInText}`,
    );
  }

  // Chỉ tách các TEXT_NODE
  if (textNode?.nodeType === 3) {
    let replacement = textNode.splitText(offset);
    let br = document.createElement("br");
    textNode.parentNode.insertBefore(br, replacement);
  }
}

Phương thức được thêm vào làm trình xử lý sự kiện click cho bất kỳ phần tử đoạn văn nào.

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

Kết quả

Nhấp vào bất kỳ đâu trong đoạn văn Lorem ipsum ... bên dưới để chèn dấu ngắt dòng tại điểm bạn nhấp. Lưu ý rằng log hiển thị nodeName, offset và một đoạn của nút đã chọn với ký tự ^ tại offset.

Tách các nút văn bản tại vị trí caret trong Shadow DOM

Ví dụ này minh họa cách lấy vị trí caret từ một nút đã chọn trong shadow root. Ví dụ rất giống với ví dụ DOM ở trên, ngoại trừ một số văn bản nằm trong shadow root. Chúng ta cung cấp một nút để cho phép bạn xem sự khác biệt khi một shadow root được truyền/không truyền vào caretPositionFromPoint().

Lưu ý rằng một số phần của mã được ẩn, bao gồm mã dùng để ghi log, vì điều này không hữu ích để hiểu phương thức này.

HTML

HTML định nghĩa một đoạn văn bản bên trong phần tử <div>. Đoạn văn chứa một phần tử <span> với id là "host" mà chúng ta sẽ sử dụng làm host cho một shadow root. Ngoài ra còn có một số nút mà chúng ta sẽ sử dụng để đặt lại ví dụ, và để Thêm/Xóa đối số tùy chọn shadow root cho caretPositionFromPoint().

html
<button id="reset" type="button">Đặt lại</button>
<button id="shadowButton" type="button">Thêm 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

Ở đây chúng ta sử dụng CSS để làm cho phần tử #host có màu đỏ và in đậm. Điều này giúp dễ dàng phân biệt giữa văn bản trong DOM và văn bản trong shadow DOM.

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

JavaScript

Đầu tiên chúng ta có một số mã để populate shadow DOM. Chúng ta sử dụng JavaScript để đính kèm shadow root một cách động, vì hệ thống ví dụ của MDN không cho phép chúng ta làm điều này một cách khai báo bằng phần tử <template>. Nội dung của shadow DOM là một phần tử <span> chứa văn bản "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);

Tiếp theo chúng ta thêm trình xử lý cho nút "Bật/Tắt shadow" của chúng ta. Mã này chuyển đổi giá trị của biến useShadows và cập nhật văn bản nút cho phù hợp.

js
let useShadows = false;

const shadowButton = document.querySelector("#shadowButton");
shadowButton.addEventListener("click", () => {
  useShadows = !useShadows;
  shadowButton.innerText = useShadows ? "Xóa Shadow" : "Thêm Shadow";
});

Phương thức dưới đây trước tiên kiểm tra hỗ trợ document.caretPositionFromPoint và sử dụng nó để lấy nút văn bản và offset tại vị trí caret. Giá trị của biến useShadows được sử dụng để xác định xem shadow root được lưu trữ trong văn bản của chúng ta có được truyền vào caretPositionFromPoint() hay không.

  • Nếu trình duyệt không hỗ trợ phương thức đó, mã sau đó kiểm tra document.caretRangeFromPoint, và sử dụng phương thức đó thay thế.
  • Nếu nút tại vị trí caret là một nút văn bản, mã sẽ tách nút tại offset đã chọn, và chèn dấu ngắt dòng giữa chúng.
  • Nếu nút là một nút phần tử, thì mã chèn một nút phần tử ngắt dòng tại offset.
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) {
    // Sử dụng phương thức dự phòng riêng của WebKit
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
    textNode = range.startContainer;
    offset = range.startOffset;
  } else {
    // Không phương thức nào được hỗ trợ, không làm gì cả
    return;
  }

  // Mã ghi log (sử dụng phương thức ẩn để lấy chuỗi con với ^ tại 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}`,
      );
    }
  }

  // Chèn dòng tại caret
  if (textNode?.nodeType === 3) {
    // TEXT_NODE - tách văn bản tại offset và thêm br
    let replacement = textNode.splitText(offset);
    let br = document.createElement("br");
    textNode.parentNode.insertBefore(br, replacement);
  } else if (textNode?.nodeType === 1) {
    // ELEMENT_NODE - Thêm nút br tại nút offset
    let br = document.createElement("br");
    const targetNode = textNode.childNodes[offset];
    textNode.insertBefore(br, targetNode);
  } else {
    // Không làm gì cả
  }
}

Cuối cùng chúng ta thêm hai trình xử lý sự kiện click cho các phần tử đoạn văn trong DOM và trong shadow root, tương ứng. Lưu ý rằng chúng ta cần truy vấn cụ thể các phần tử trong shadowRoot vì chúng không hiển thị với các phương thức truy vấn DOM thông thường.

js
// Trình xử lý sự kiện click cho các phần tử <p> trong DOM
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
  paragraph.addEventListener("click", insertBreakAtPoint);
}

// Trình xử lý sự kiện click cho các phần tử <p> trong Shadow DOM
const shadowParagraphs = host.shadowRoot.querySelectorAll("p");
for (const paragraph of shadowParagraphs) {
  console.log(paragraph);
  paragraph.addEventListener("click", insertBreakAtPoint);
}

Kết quả

Nhấp vào đoạn văn Lorem ipsum ... trước hoặc sau văn bản shadow DOM để chèn dấu ngắt dòng tại điểm bạn nhấp. Lưu ý rằng trong trường hợp này log cho biết bạn đã chọn một TEXT_NODE, offset và một đoạn của nút đã chọn với ký tự ^ tại offset.

Ban đầu shadow root không được truyền vào caretPositionFromPoint(), vì vậy nếu bạn nhấp vào văn bản "I'm in the shadow DOM", nút vị trí caret trả về là nút cha của host, tại offset của shadow root. Do đó dấu ngắt dòng được thêm vào trước nút thay vì điểm bạn đã chọn. Lưu ý rằng nút vị trí caret trong trường hợp này có kiểu ELEMENT_NODE.

Nếu bạn nhấp vào nút "Thêm shadow", shadow root được truyền vào caretPositionFromPoint(), do đó vị trí caret trả về là nút đã chọn cụ thể trong shadow DOM. Điều này làm cho văn bản shadow DOM hoạt động giống như văn bản đoạn văn khác.

Đặc tả kỹ thuật

Specification
CSSOM View Module
# dom-document-caretpositionfrompoint

Tương thích trình duyệt

Xem thêm