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 允许用户通过点击操作后将 range 重置为 null;Firefox 12.0a1 和 Chrome 17 dev 则不允许。我们遵循
Gecko/WebKit 的行为,因为这样 getRangeAt(0) 抛异常的概率更低。
一旦选择与指定范围关联,除非本规范要求,否则必须持续与该 范围关联。
例如,如果 DOM 发生变化导致范围的边界点发生改变,或者脚本修改了范围的边界点,则仍必须使用同一个范围对象与选择关联。但如果用户更改选择或者脚本调用addRange(),则该选择必须与新范围对象关联,其他地方有要求。
如果选择的范围不为 null 且已折叠,则插入符号的位置必须在该边界点。当选择未折叠时,本规范未定义插入符号位置;用户代理应遵循平台惯例决定插入符号是在选择的起始、末尾或其它位置。
每个选择有一个方向:正向、 反向或无方向。当用户首先按下一个边界点,再按下另一个(如,点击某一点再拖至另一点)时,如果第一个边界点在第二个之后,则该选择初始为反向。如果第一个边界点在第二个之前,则初始为正向。 否则应为无方向。
当选择的范围被脚本修改(例如调用selectNode(node)),
方向必须被保留。
每个选择还具有锚点与 焦点。如果选择的范围为 null,其锚点和焦点都为 null。 若范围非 null 且方向为正向, 锚点为范围的起点, 焦点为终点。否则 焦点为起点, 锚点为终点。
每个文档、input 元素 和 textarea 元素 有一个布尔值属性has scheduled selectionchange event,其初始为 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
只有当 anchor 和 focus 相同时(包括两者均为 null 的情况)该属性才应返回 true。否则应返回 false。
rangeCount
type
如果 此对象 为 空,或 focus 或
anchor
任一不在 文档树
中,则该属性必须返回 "None";如果此对象的 range 为 折叠,则返回 "Caret";否则返回
"Range"。
direction
如果 此对象 为 空 或者该选择为
无方向,则该属性必须返回 "none"。如果该选择的方向为 正向,返回 "forward";如果方向为 反向,返回
"backward"。
getRangeAt() method
如果 index 不是 0,或者 此对象 为 空,或 focus 或 anchor 任何一个不在 文档树
中,则该方法必须抛出 IndexSizeError
异常。否则,必须返回对此对象的 范围
的引用(不是副本)。
因此,如果在此期间没有移除此对象的范围,随后对该方法的调用将返回相同的 范围 对象。特别是,当选择非空时,getSelection().getRangeAt(0) ===
getSelection().getRangeAt(0) 的求值应为 true。
addRange() method
该方法必须遵循以下步骤:
removeRange() method
该方法必须通过解除关联其 范围 来使 此对象 变为 空,前提是此对象的范围正是
range。否则,必须抛出 NotFoundError。
removeAllRanges() method
empty() method
该方法必须作为 removeAllRanges() 的别名并且其行为与之完全相同。
getComposedRanges() method
shadowRoots]
的影子包含性祖先时,重复以下步骤:
shadowRoots]
的影子包含性祖先时,重复以下步骤:
StaticRange 组成的数组,
这些 StaticRange 的 起始节点 为
startNode,
起始偏移量
为 startOffset,
结束节点 为
endNode,
以及 结束偏移量 为
endOffset。
collapse() method
该方法必须遵循以下步骤:
removeAllRanges() 完全相同,并中止这些步骤。
DocumentType,则抛出 InvalidNodeTypeError
异常并中止这些步骤。
IndexSizeError
异常并中止这些步骤。
setPosition() method
该方法必须作为 collapse() 的别名并且其行为与之完全相同。
collapseToStart() method
如果 InvalidStateError 异常如果
此对象 为 空,则应抛出该异常。否则,必须创建一个新的 范围,将其 起始 和 结束
都设置为此对象的范围的起始位置,然后将此对象的范围设置为新创建的范围。
对于 collapseToStart/End,IE9 会修改现有范围,而 Firefox 9.0a2 和 Chrome 15 dev 会替换为新的范围。规范遵循多数实现,使用新的范围替换并保持旧的 Range 对象不变。
collapseToEnd() method
如果 InvalidStateError 异常如果
此对象 为 空,则应抛出该异常。否则,必须创建一个新的 范围,将其 起始 和 结束
都设置为此对象的范围的结束位置,然后将此对象的范围设置为新创建的范围。
extend() method
该方法必须遵循以下步骤:
InvalidStateError
并中止这些步骤。
约在 2011 年 1 月进行的逆向工程。IE 不支持该方法,因此我依赖 Firefox(在 2000 年之前实现了 extend())和 WebKit(2007 年实现 extend())。我基本上忽略 Opera,因为其实现据说不兼容。Firefox 12.0a1 似乎会修改现有范围。IE9 不支持 extend(),而 Chrome 17 dev 或 Opera Next 12.00 alpha 是否修改或替换无法判断,因为 getRangeAt() 无论如何都会返回副本。尽管如此,为与 collapse() 保持一致,我在这里不采用 Gecko 的行为。
setBaseAndExtent() method
该方法必须遵循以下步骤:
IndexSizeError
异常并中止这些步骤。
selectAllChildren() method
该方法必须遵循以下步骤:
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() method
该方法必须遵循以下步骤:
在一个边界点处的解析后的文本方向, 如果该位置字符的嵌入 方向([UAX9] BD3)为 L, 则为 ltr;如果嵌入 方向为 R,则为 rtl。
在双向文本边界处——即相邻字符具有不同 嵌入方向的位置——用户代理可以使用额外上下文 (例如基于光标移动方向的插入符亲和性) 来确定要使用哪个字符的嵌入方向。
我们需要更精确地定义按每种粒度扩展或移动 选择意味着什么。
deleteFromDocument() method
如果此对象非 空
且 focus
与 anchor 均在 文档树 中,则该方法必须在此对象的 范围 上调用 deleteContents()。否则该方法不得执行任何操作。
这是唯一一个实际修改范围而不是替换它的方法。这与 IE9 和 Firefox 12.0a1 一致。(Chrome 17 dev 和 Opera Next 12.00 alpha 无法测试,因为 getRangeAt() 无论如何返回副本。)
containsNode() method
如果 此对象 为 空,或
node 的 根 不是与 此对象 关联的文档,则该方法必须返回 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: