Selection与Range的实操记录

79 阅读1分钟

简单记录下SelectionRange的实操记录

  1. 点击插入关键词(关键词高亮)按钮,插入关键词到contentEditable属性为true的元素中
  2. 点击可编辑元素中的高亮关键词,中间可编辑,如果光标插入关键词尾部,不可编辑,文字颜色不能与关键词颜色一致
  3. 如果光标移动到可编辑元素的尾部,可插入关键词,插入后输入文字颜色也不能与关键词颜色一致
import { type FC } from "react";
import { useRef } from "react";
import Page from "@/components/Page";
import { Button } from "antd-mobile";

const TestPage: FC = () => {
  const elRef = useRef<HTMLDivElement>(null);

  const onClick = () => {
    const selection = window.getSelection();
    const range = selection?.getRangeAt(0);
    const newNode = document.createElement("span");
    newNode.setAttribute("style", "color: red");
    newNode.setAttribute("data-highlight", "1");
    newNode.appendChild(document.createTextNode("hello world"));
    range?.insertNode(document.createTextNode("\u200B"));
    range?.insertNode(newNode);
    range?.collapse();
    selection?.removeAllRanges();
  };

  const onContentEditableClick = () => {
    const selection = window.getSelection();
    const range = selection?.getRangeAt(0);
    const { endContainer } = range || {};

    if (
      endContainer?.parentNode &&
      (endContainer?.parentNode as HTMLSpanElement).hasAttribute(
        "data-highlight",
      )
    ) {
      if (selection?.focusOffset === endContainer?.nodeValue?.length) {
        const nodes = elRef.current?.childNodes || [];
        const lastNode = nodes[nodes.length - 1];
        const tagNode =
          lastNode?.nodeType === 1 ? lastNode : lastNode.previousSibling;

        if (
          tagNode?.nodeType === 1 &&
          (tagNode as HTMLSpanElement)?.hasAttribute?.("data-highlight")
        ) {
          (tagNode as HTMLSpanElement).setAttribute("contentEditable", "false");
          return;
        }
        selection?.removeAllRanges();
        const newRange = new Range();
        (endContainer?.parentNode as HTMLSpanElement).setAttribute(
          "contentEditable",
          "false",
        );
        newRange.setEndBefore(endContainer?.parentNode.nextSibling as Node);
        newRange.collapse();
        selection?.addRange(newRange);
      } else {
        (endContainer?.parentNode as HTMLSpanElement).setAttribute(
          "contentEditable",
          "true",
        );
      }
    }
  };

  return (
    <Page className="box-border !bg-none">
      <div
        ref={elRef}
        suppressContentEditableWarning
        contentEditable
        onClick={onContentEditableClick}
      >
        useRequest 是一个强大的异步数据管理的 Hooks,React
        项目中的网络请求场景使用 useRequest 就够了。
      </div>

      <Button onClick={onClick}>插入关键词</Button>
    </Page>
  );
};

export default TestPage;