Copyright © 2026 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本文件是关于 Selection API 及与选择相关功能规范的初步草案。它取代了 HTML specification 的几节旧章节,以及旧 DOM Range specification 中关于选择的部分。
本文件定义了与选择相关的 API,允许用户和作者选择文档的一部分或指定用于复制、粘贴及其他编辑操作的关注点。
本节描述的是本文档在其发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 标准和草案 索引中找到。
这是正在进行中的工作。
本文档由 Web Editing Working Group 作为 使用 推荐标准 轨道的工作草案发布。
作为 工作草案发布并不意味着 W3C 及其成员认可。
这是一份草案文档,可能随时被其他 文档更新、替换或废弃。除作为一项 正在进行中的工作之外,不应引用本文档。
本文档由一个 依据 W3C 专利 政策运作的工作组制作。 W3C 维护一份 与该组交付物相关的任何专利 披露的公开列表 ;该页面还包含 披露专利的说明。实际 知晓某项专利且认为该专利包含 基本权利要求的个人 必须依照 W3C 专利政策第 6 节披露该信息。
本文档受 2025 年 8 月 18 日 W3C 流程文档管辖。
本节为非规范性内容。
IE9 和 Firefox 6.0a2 允许在选择中存在任意范围,这与本规范最初的描述一致。但这会导致作者、实现者和规范作者都必须处理一些棘手的边界情况,而且这些情况其实并没有实际意义。Chrome 14 dev 和 Opera 11.11 强制规范化选择,例如不允许其位于空元素内部等,但这也被认为不是好主意,因为这限制了作者的灵活性。
所以我将规范改为一个折中方案,既允许一定简化,又不怎么限制作者。参见 讨论。基本上会在某些地方抛出异常,以防止选择包含一个不是 Element 或 Text 节点的 边界点,或选择的边界点不属于某个文档。
但这意味着 getRangeAt() 必须开始返回副本而不是引用。同时,某些边缘情况可能会导致奇怪的失败。或许最重要的是,当 DOM 发生变化时可能会出现各种问题,比如如果边界点的节点从其父节点被移除,并且变更规则会将新的边界点放在非 Text/Element 节点内部。最后,先前规定的行为具有与两个主流实现一致的优点,而新的行为却没有任何实现一致。因此,我又改回去了。
参见 bug 15470。 IE9、Firefox 12.0a1、Chrome 17 dev 和 Opera Next 12.00 alpha 都使 range 初始为 null。
这是 HTML 规范的一项要求。IE9 和 Opera Next 12.00 alpha 似乎遵循了这一要求,而 Firefox 12.0a1 和 Chrome 17 dev 似乎没有。 请参阅 Mozilla bug、 WebKit bug。
这一个选区必须由 文档的所有内容共享(但嵌套的文档除外),包括该 文档中的任何编辑 宿主。
每个选区都可以与单个范围关联。 当没有范围 与该选区关联时,该 选区为空。选区最初必须为 空。
一个文档的选区是与该文档关联的单例对象,因此当
调用 Document.open() 时,它会被替换为一个新对象。请参阅bug 15470。
IE9 和 Opera Next 12.00 alpha 允许用户之后通过点击某处将范围重置为
null;Firefox 12.0a1 和 Chrome 17
dev 则不允许。我们遵循 Gecko/WebKit,因为这样可以降低
getRangeAt(0) 抛出异常的可能性。
一旦一个选区与给定范围关联,它 必须继续与同一个范围关联,直到本 规范另有要求。
例如,如果 DOM 以某种方式发生更改,从而改变范围的
边界点,或者脚本修改了范围的边界点,
同一个范围对象必须继续与该
选区关联。但是,如果用户更改了选区,或者脚本调用
addRange(),则选区必须
与一个新的范围
对象关联,如本规范其他部分所要求的那样。
如果选区的范围不为 null 且处于 折叠状态,那么插入符 位置必须位于该 范围的边界点。当 选区 不处于 折叠状态时,本 规范不定义插入符 位置;用户代理在决定插入符位于选区的起点、位于 选区的终点还是其他位置时,应遵循平台约定。
每个选区都有一个方向:向前、 向后,或无方向。如果 用户通过先指示 范围的一个边界点,然后再指示另一个边界点来创建 选区(例如点击一个点并 拖动到另一个点),并且第一个指示的边界点位于第二个边界点 之后, 那么相应的 选区最初必须为向后。如果第一个 指示的边界点位于 第二个边界点之前,那么相应的选区最初必须为 向前。 否则,它必须为无方向。
当选区的范围被脚本改变时,例如
通过 selectNode(node),
选区的方向
必须保留。
每个选区还有一个锚点和一个 焦点。如果选区的范围为 null,则其 锚点和焦点都为 null。如果选区的 范围不为 null,并且其方向为向前, 则其锚点是 范围的起点,其 焦点是终点。否则,其焦点是 起点,其锚点是 终点。
每个文档、input 元素和textarea 元素 都有一个布尔值已调度 selectionchange 事件,其 初始值为 false。
Selection 接口提供了与每个文档关联的选择交互的方式。
WebIDL[Exposed=Window]
interface Selection {
readonly attribute Node? anchorNode;
readonly attribute unsigned long anchorOffset;
readonly attribute Node? focusNode;
readonly attribute unsigned long focusOffset;
readonly attribute boolean isCollapsed;
readonly attribute unsigned long rangeCount;
readonly attribute DOMString type;
readonly attribute DOMString direction;
Range getRangeAt(unsigned long index);
undefined addRange(Range range);
undefined removeRange(Range range);
undefined removeAllRanges();
undefined empty();
sequence<StaticRange> getComposedRanges(optional GetComposedRangesOptions options = {});
undefined collapse(Node? node, optional unsigned long offset = 0);
undefined setPosition(Node? node, optional unsigned long offset = 0);
undefined collapseToStart();
undefined collapseToEnd();
undefined extend(Node node, optional unsigned long offset = 0);
undefined setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset);
undefined selectAllChildren(Node node);
undefined modify(optional DOMString alter, optional DOMString direction, optional DOMString granularity);
[CEReactions] undefined deleteFromDocument();
boolean containsNode(Node node, optional boolean allowPartialContainment = false);
stringifier;
};
dictionary GetComposedRangesOptions {
sequence<ShadowRoot> shadowRoots = [];
};
anchorNode
anchorOffset
focusNode
focusOffset
isCollapsed
当且仅当 锚点和焦点相同(包括 两者均为 null)时,该属性必须返回 true。否则必须返回 false。
rangeCount
type
如果 this 为空,或
焦点或锚点中的任一个不在文档树中,
该属性必须返回 "None";如果 this 的范围处于
折叠状态,则返回
"Caret";否则返回 "Range"。
direction
如果 this 为空,或
此选区为无方向,该属性必须返回 "none"。如果此
选区的方向为向前,则返回 "forward";如果此
选区的方向为向后,则返回 "backward"。
getRangeAt() 方法
如果
index 不是 0,或者 this 为
空,或
焦点或锚点中的任一个不在
文档树中,
该方法必须抛出 IndexSizeError 异常。
否则,它必须返回对 this 的范围的引用(而不是
副本)。
addRange() 方法
该方法必须遵循以下步骤:
removeRange() 方法
如果 this 的范围为
range,该方法必须通过解除其
范围的关联,使 this 变为空。
否则,它必须抛出 NotFoundError。
removeAllRanges() 方法
empty() 方法
该方法必须是 removeAllRanges() 的别名,并且其行为
必须完全相同。
getComposedRanges() 方法
shadowRoots"]
中任何项的包含影子的
包含自身祖先时,
重复以下步骤:
shadowRoots"]
中任何项的
包含影子的
包含自身祖先时,
重复以下步骤:
StaticRange 组成的数组,其中
起始
节点为 startNode,起始偏移量为
startOffset,结束节点为
endNode,并且结束偏移量为
endOffset。
collapse() 方法
该方法必须遵循以下步骤:
removeAllRanges() 完全相同,并中止这些步骤。
DocumentType,则抛出
InvalidNodeTypeError
异常并中止这些步骤。
IndexSizeError
异常并
中止这些步骤。
setPosition() 方法
该方法必须是 collapse() 的别名,并且其行为
必须完全相同。
collapseToStart() 方法
如果
this 为空,该方法必须抛出 InvalidStateError
异常。否则,它必须
创建一个新的范围,将其起点
和
终点均设置为 this 的范围的起点,
然后将 this 的范围设置为
新创建的
范围。
对于 collapseToStart/End,IE9 会改变现有范围,而 Firefox 9.0a2 和 Chrome 15 dev 会用一个新范围替换它。本规范 遵循多数实现,用一个新范围替换它,并保持旧的 Range 对象不变。
collapseToEnd() 方法
如果
this 为空,该方法必须抛出 InvalidStateError
异常。否则,它必须
创建一个新的范围,将其起点
和
终点均设置为 this 的范围的终点,然后
将 this 的范围设置为
新创建的范围。
extend() 方法
该方法必须遵循以下步骤:
InvalidStateError
异常并中止这些步骤。
这是在 2011 年 1 月前后通过逆向工程得出的。IE 不支持它,因此 我依赖 Firefox(在 2000 年之前的某个时候实现了 extend()) 和 WebKit(于 2007 年实现了 extend())。我基本忽略了 Opera,因为 gsnedders 告诉我它的实现并不 兼容。Firefox 12.0a1 似乎会改变现有范围。IE9 不支持 extend(),而且无法判断 Chrome 17 dev 或 Opera Next 12.00 alpha 是改变还是替换范围,因为 getRangeAt() 无论如何都会返回一个副本。尽管如此,我在这里选择与 Gecko 不同的做法,以便与 collapse() 保持一致。
setBaseAndExtent() 方法
该方法必须遵循以下步骤:
IndexSizeError
异常并中止
这些步骤。
selectAllChildren() 方法
该方法必须遵循以下步骤:
DocumentType,则抛出
InvalidNodeTypeError
异常并中止这些步骤。
0)。
主要基于 Firefox 9.0a2。它有一个我没有 复现的 bug,即如果将 Document 作为参数传入, 结束偏移量会变为 1,而不是其子节点 的数量。它还会抛出 RangeException 而不是 DOMException, 因为它的实现早于这两者的合并。
IE9 的行为类似,但存在一些异常情况。如果节点已分离或 display:none,它会抛出“Unspecified error.”,而且显然 在其他一些随机情况下也会如此。对于 已分离的注释(仅此一种情况!),它会抛出“Invalid argument.”。最后,如果向其传入注释,它 似乎会选择整个注释,这与文本节点的行为不同。
Chrome 16 dev 的行为与其 Selection 实现所预期的一样。它拒绝选择任何不可见的内容, 因此几乎总是错误的。Opera 11.50 像往常一样,在我的所有 测试中都不执行任何操作。
新范围会替换任何现有范围,而不会改变它。这 与 IE9 和 Firefox 12.0a1 一致。(Chrome 17 dev 和 Opera Next 12.00 alpha 无法测试,因为 getRangeAt() 无论如何都会返回一个副本。)
modify() 方法
该方法必须遵循以下步骤:
在一个边界点处的解析文本方向: 如果该位置字符的嵌入 方向([UAX9] BD3)为 L,则为ltr;如果嵌入 方向为 R,则为rtl。
在双向文本边界处——即相邻字符具有不同 嵌入方向的位置——用户代理可以使用其他上下文 (例如基于光标 移动方向的插入符亲和性)来确定使用哪个字符的嵌入方向。
我们需要更精确地定义按每种粒度扩展或移动 选区的含义。
deleteFromDocument() 方法
如果 this 不是空,
并且焦点
和锚点都位于文档树中,则该方法必须在 this 的
范围上调用 deleteContents()。
否则,该
方法必须不执行任何操作。
这是唯一一个实际改变范围而不是 替换它的方法。这与 IE9 和 Firefox 12.0a1 一致。(Chrome 17 dev 和 Opera Next 12.00 alpha 无法测试,因为 getRangeAt() 无论如何都会返回副本。)
containsNode() 方法
如果 this 为
空,或者
node 的根不是与 this 关联的
文档,则该方法必须返回 false。
否则,如果 allowPartialContainment 为
false,则当且仅当其范围的起点位于
node 中第一个边界
点之前或在视觉上与其等价,并且其范围的终点
位于 node 中最后一个边界点之后或在视觉上
与其等价时,该方法才必须返回 true。
如果 allowPartialContainment 为 true,则
当且仅当其范围的起点
位于 node 中最后一个边界点之前或在视觉上
与其等价,并且其范围的终点
位于 node 中第一个
边界点之后或在视觉上与其等价时,该方法才必须返回
true。
stringifier
另见来自 Gecko 的 nsISelection.idl。本规范尚未包含那里的所有内容,特别是 selectionLanguageChange() 和 containsNode() 尚缺失。它们缺失的原因是我无法弄清如何用 Range 来定义它们。
最初,Selection 接口是一个 Netscape 的特性。其原始实现被带入到 Gecko(Firefox),随后该特性被其他浏览器引擎独立实现。Netscape 的实现始终允许在单个选择中存在多个范围,例如用户可以选择表格的一列。然而,多范围选择证明是一个让 Web 开发者不易察觉并且即使是 Gecko 的开发者也很少正确处理的不愉快的边界情况。其他浏览器引擎从未实现该特性,并以各种不兼容的方式将选择限制为单个范围。
本规范遵循非 Gecko 引擎,将选择限制为最多一个范围,但该 API 最初仍为支持任意数量范围的选择而设计。这解释了像 removeRange() 与
removeAllRanges() 并存以及 getRangeAt() 方法接受一个必须始终为零的整数参数等奇怪之处。
所有 Selection 接口的成员都是以该对象所表示的
range
对象(如果有的话)上的操作来定义的。这些操作可能会引发异常,如在 Range 接口的定义中所述;因此,这也可能导致 Selection 接口的成员抛出异常,除上文明确指出的情况外。
本规范扩展了若干接口以提供到本规范中定义的接口的入口点。
WebIDLpartial interface Document {
Selection? getSelection();
};
WebIDLpartial interface Window {
Selection? getSelection();
};
getSelection() method
该方法必须调用并返回 getSelection() 在 此对象 的 Window
的 document
属性上的结果。
GlobalEventHandlers
接口在 [HTML] 中定义。
WebIDLpartial interface mixin GlobalEventHandlers {
attribute EventHandler onselectstart;
attribute EventHandler onselectionchange;
};
onselectstart
该属性必须是一个针对 selectstart 事件的 事件处理器
IDL 属性,该事件由所有 HTML
元素、Document 对象和 Window
对象支持。
onselectionchange
该属性必须是一个针对 selectionchange 事件的 事件处理器
IDL 属性,该事件由所有 HTML
元素、Document 对象和 Window
对象支持。
当用户代理在 CharacterData 上替换数据或截取子数据时,用户代理必须像操作活动范围一样,更新该CharacterData的节点文档的选择关联的范围。
当用户代理要拆分 Text 节点时,用户代理必须像操作活动范围一样,更新该Text的节点文档的选择关联的范围。
当用户代理执行 normalize() 方法步骤时,用户代理必须像操作活动范围一样,更新 此的节点文档的选择关联的范围。
用户代理应允许用户更改与选择 关联的活动文档。 如果用户对选择进行了任何修改,用户代理必须创建一个具有合适起始和结束的新的范围,并将选择与这个新范围关联(而不是修改现有的范围),并根据以下规则设置和更新选择的方向:如果起始小于或等于结束则设为正向;如果结束小于起始则设为反向;如果由于平台惯例无法判断起始和结束的顺序则设为无方向。
对于任何用户操作(如点击非可编辑区域),如果选择原本非空,用户代理不得将其设为空。
见bug 15470。IE9 和 Opera Next 12.00 alpha 允许用户点击后将范围重置为 null;Firefox 12.0a1 和 Chrome 17 dev 则不会。我遵循 Gecko/WebKit,这样 getRangeAt(0) 抛出异常的机会更小。
当用户代理准备在响应用户操作时将新的 newRange 关联到 选择上时,如果之前的 选择是空或原关联范围是折叠状态,则必须在更改选择前,于与
newRange 的边界点相关的节点上,触发一个冒泡且可取消的名为selectstart的事件。
如果该事件被取消,则用户代理不得更改选择。
当选择与其范围解除关联、关联新的范围,或关联的范围的边界点被用户或内容脚本修改时,用户代理必须在文档上调度 selectionchange 事件。
当
input
或
textarea
元素提供文本选择,且其选择(范围或方向)发生变化时,用户代理必须在该元素上调度
selectionchange 事件。
在 节点 target 上调度 selectionchange 事件,按以下步骤操作:
在节点 target 上触发 selectionchange 事件,按以下步骤操作:
selectionchange、可冒泡但不可取消的事件。
selectionchange、不可冒泡且不可取消的事件。
本规范中标记为非规范性(non-normative)的部分,以及所有作者指南、图示、示例和说明都是非规范性的。本规范中其余内容均为规范性内容。
本规范定义了适用于单一产品的符合性标准:实现本规范所含接口的用户代理。
目前该标准未发现已知的安全性考量。
为减轻暴露用户辅助技术使用情况的潜在隐私风险,例如,用户代理可选择在用户修改文档选择时,模拟通常与selectstart或selectionchange事件有关的鼠标和键盘事件。
特别感谢
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: