登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

JavaScript Selection.setBaseAndExtent 怎样设置带方向的文本选区

来源:17golang原创

时间:2026-10-27 14:19:30 206浏览 收藏

Selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset) 用两组边界点设置选区。第一组是锚点,第二组是焦点;焦点在文档顺序中位于锚点之前时,得到反向选区。要保留方向,就不能先把这两组参数按大小重新排序。

文档入口:https://developer.mozilla.org/en-US/docs/Web/API/Selection/setBaseAndExtent。下面用同一文本节点说明 API 语义,再讨论恢复选区的边界。这是既有 Web API 的用法解析,不是新的语言提案;示例按文档编写,未在本文环境执行。

用最小例子建立反向选区

在已有页面上添加一个文本容器和按钮。通过按钮触发设置,避免页面初始化时无条件抢占用户选区:


red green blue

// 在上方HTML之后运行,保证元素已存在
const text = document.getElementById("sample").firstChild;
const button = document.getElementById("select-word");
button.addEventListener("click", () => {
  const selection = window.getSelection();
  if (!selection || typeof selection.setBaseAndExtent !== "function") {
    // 由产品提供不支持方向恢复时的提示
    return;
  }
  // green占据[4, 9),锚点在词尾,焦点在词首
  selection.setBaseAndExtent(text, 9, text, 4);
});

这里的字符串位置可由原文直接推导:red 后有一个空格,green 从偏移4开始、在偏移9之前结束。交换为 (text, 4, text, 9) 会覆盖相同文字,但锚点和焦点角色不同。不要把“选中文字相同”当成“选区状态完全相同”。

分清锚点焦点与范围起止

anchor 表达选择的基点,focus 表达另一端。Range 的 start 与 end 则按文档顺序组织,反向选择并不意味着 start 大于 end。因此,只保存 getRangeAt(0) 的起止位置,并不能完整保存锚点与焦点的角色。

Selection锚点焦点与Range起点终点在反向选择中的关系
原创静态结构图:反向选择中anchor对应范围较后端,focus对应较前端;图中连线说明角色关系,不是脚本执行流程。

方向描述的是文档边界顺序,不等同于屏幕上的左右方向。包含双向文字或复杂布局时,更不能仅凭视觉上的“向左拖动”推断 DOM 端点。本文例子限于普通文档中的单一文本节点。

按节点类型计算偏移

Text 节点的偏移按 UTF-16 代码单元计数;Element 节点的偏移按子节点位置计数。空白文本节点也属于子节点,所以 children 的长度不能直接当作 childNodes 的长度使用。跨元素选择时,每一端都必须有自己的节点和该节点内偏移。

// 示例只接受明确的Text节点,避免把元素偏移误当字符偏移
function selectTextPart(textNode, anchor, focus) {
  if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
    throw new TypeError("需要文本节点");
  }
  for (const offset of [anchor, focus]) {
    // 先限制输入为非负整数,再检查节点内上界
    if (!Number.isInteger(offset) || offset  textNode.length) {
      throw new RangeError("偏移超出文本节点范围");
    }
  }
  if (!textNode.isConnected || textNode.ownerDocument !== document) {
    throw new Error("节点必须位于当前文档");
  }
  const selection = window.getSelection();
  if (!selection || typeof selection.setBaseAndExtent !== "function") {
    return false; // 将不支持情况交给调用方处理
  }
  selection.setBaseAndExtent(textNode, anchor, textNode, focus);
  return true;
}

这段封装有意只处理当前文档内的文本节点,不是可覆盖 Shadow DOM 或所有编辑器模型的通用库。不要把后端的字节偏移直接传进来;emoji 也可能占据两个代码单元,组合字符还可能由多个代码点构成。面向用户字符的索引需要先映射到文本节点偏移。

Text节点代码单元偏移与Element子节点偏移的不同计数边界
原创静态结构图:两种节点有不同偏移单位,anchorOffset与focusOffset均依附各自节点,不能共享一套页面全局字符索引。

保存方向时保留两端角色

旧写法常把范围起止当成选区全部状态。对于需要恢复用户选择方向的编辑工具,可以先保存 anchorNode、anchorOffset、focusNode、focusOffset,再在节点仍然有效时调用 setBaseAndExtent。调用会替换当前选择范围,无须为了这一次设置先 removeAllRanges。

// 只适用于DOM节点身份与文本内容都未改变的短期保存
function captureSelection() {
  const s = window.getSelection();
  if (!s || s.rangeCount === 0 || !s.anchorNode || !s.focusNode) return null;
  return {
    anchorNode: s.anchorNode, anchorOffset: s.anchorOffset,
    focusNode: s.focusNode, focusOffset: s.focusOffset
  };
}
// 在受控DOM更新后,应先重建有效端点,不能盲用旧节点引用
function restoreSelection(saved) {
  const s = window.getSelection();
  if (!saved || !s || typeof s.setBaseAndExtent !== "function") return false;
  if (!saved.anchorNode.isConnected || !saved.focusNode.isConnected) return false;
  try {
    s.setBaseAndExtent(
      saved.anchorNode, saved.anchorOffset,
      saved.focusNode, saved.focusOffset
    );
    return s.anchorNode === saved.anchorNode &&
      s.anchorOffset === saved.anchorOffset &&
      s.focusNode === saved.focusNode && s.focusOffset === saved.focusOffset;
  } catch (error) {
    if (error.name === "IndexSizeError") return false; // 文本缩短导致偏移失效
    throw error;
  }
}

框架重渲染可能替换节点;即便节点没换,文本内容变化也会改变原偏移代表的位置。长期书签应使用编辑器自己的稳定位置模型,并映射回当前 DOM;单纯 JSON 序列化节点引用不能完成这件事。

处理兼容与常见边界

目标合适方式容易忽略的点
普通文档中恢复两端角色setBaseAndExtent不要先排序anchor和focus
只处理文档中的覆盖范围Range边界范围起止不完整表达选择方向
文本输入框内选区setSelectionRange使用控件value的索引

input 与 textarea 应使用各自的文本选择接口。例如 text 类型输入框可以调用 setSelectionRange(start, end, "backward"),并按交互需要让控件获得焦点。不要把输入框的 value 当成可直接选择的子 Text 节点。

偏移超出节点长度会自动截断吗?

不要依赖截断。setBaseAndExtent 的越界偏移会触发 IndexSizeError,封装应提前明确输入范围。

两端相同会报错吗?

同一边界点表示折叠选区,即插入位置;它不包含一段被选中的文字。

接口存在就能覆盖所有编辑器吗?

不能。普通文档、表单控件、嵌套文档和 Shadow DOM 的处理边界不同,特性检测只说明方法存在,不能替代应用自己的节点映射。

参考:MDN方法参考、Selection API规范草案、输入控件选区接口。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>