zhengrenzhe's blog   About

Selection & Range 基础

花了一些时间,总算是把Selection和Range这块的文档看完了,作为一个有着十余年历史的DOM API,虽然日常用的少,但在有些地方使用能极大的减少工作量,尤其是在在线富文本编辑这个领域绝对少不了它的身影。因为Selection与Range不仅API众多,且不同平台的API还不同,所以本文仅介绍了Selection和Range的概念性基础知识,详细的API可以查询MDN SelectionMDN Range学习。另外在MDN中也有一些说的不明白的地方,此时应以W3C文档SelectionRange为准。

浏览器上的选区

在浏览器上我们可以用鼠标选择一段文字,被选中的文字默认会以蓝色背景高亮显示,可以称之为「选区」,之后我们可以通过快捷键对这这个选区的内容进行复制或别的操作。

Selection的作用就是可以让你用代码的方式操作鼠标创建的选区的内容,或者直接通过代码来创建一个高亮选区。Range类似于Selection,表示一个页面上的选择范围,同样它也能对范围内的内容进行一些操作,但与Selection在概念上有着不同: 1. Selection表示一个页面上的高亮显示区域,对用户来说这是可见的(除非该选区的内容为空或通过CSS把高亮背景设为了透明),而Range在页面中是不可见的,除非对Range内的内容进行操作,或者把Range添加到Selection中,否则没法察觉Range的存在 。 2. Selection在页面中是唯一的,也就是说一个页面在任何时刻都只有一个高亮选区。而Range在页面中可以创建无数个,每个Range可以对应不同的范围,这也是Selection与Range最大的区别。

选区API的技术标准

上面说到的Selection和Range虽然核心概念有些差别,但它们同属于W3C标准,所以可以简单的称之为“W3C选区”。同时还有另外的两个选区标准:“Microsoft选区”及“Mozilla选区”,分别由Microsoft和Mozilla开发,分别应用在其自家的IE和Firefox上。本文将重点讨论W3C和Microsoft的选区标准,Mozilla的API已经没有讨论的必要,因为它本质上是为了向后兼容而对W3C标准的一个延伸,两者的API及概念基本相同。

W3C Selection 对象

Selection对象与页面的关系

现在来详细分析一下Selection对象。前面说过,Selection表示页面上的高亮显示区域,也就是说Selection对象与页面中的一个高亮区域存在引用关系,类似于document.body对应页面中的body节点一样。

获取Selection对象

通过window.getSelection 可以获取到页面中的唯一一个Selection对象,同时,Selection还存在一个构造函数:window.Selection,但是我们无法通过该构造函数创建实例,所以只有getSelection这一种办法。

let sel = window.getSelection();
sel instanceof Selection; // true

W3C Selection作为现在的标准,基本被所有现代浏览器支持,当然这个要排除IE8及以下。

前面说过,Selection在页面中是唯一的,不仅高亮选区在页面中是唯一的,Selection对象在页面也是唯一的。

window.getSelection() === window.getSelection(); // true

不论调用getSelection多少次,它总是范围同一个Selection,Selection的生命周期贯穿整个页面,从页面被加载,到页面被关闭,Selection始终存在,无法重新创建,更无法销毁。

Selection对象的范围

有内容的Selection

以下面这段文字为例:

Star Trek: First Contact is an American science fiction film, released on November 22, 1996.

我们在其上面随便选中一个范围

打开控制台运行:

let sel = window.getSelection();
console.log(sel);

即可看到Selection对象。

首先需要关注的是:

这四个属性,它们是Selection对象的核心,anchorNodeanchorOffset用来表示Selection的起点,focusNodefocusOffset用来表示Selection的终点。

那这个起点与终点是如何计算的呢?anchorNode与focusNode表示端点所在的Node,因为选区两个端点的两边都是文本,所以anchorNode与focusNode都是文本所在的文本节点。

从上图可以直观的看出两个端点的计算方式。

如果Selection的端点的两边并不都是文字,那端点的计算方式就要变了。以下面这段为例:

Star Trek: First Contact isan American science fiction film, released on November 22, 1996.

创建如下选区:

可以看到anchorNode和anchorOffset都变的不一样了:

因为现在的选区起点的两边一边是文字,一边是图片,所以自然不能用上面那种计算方法。此时的anchorNode和变为focusNode了当前的段落,是一个非文本节点,所以适用于以下的计算方法:

从上图可以直观的看出两个端点的计算方式。

无内容的Selection

说完了选择了一段内容后的Selection,接下来看一下无内容的Selection。

当页面加载完成后,如果鼠标没有与页面发生点击,打开控制台可以看到下面的情景:

anchorNode与focusNode相同(都是null),anchorOffset与focusOffset相同(都是0),此时Selection处于「折叠」状态,Selection内没有内容。

当我们随便点击一下页面,再查看一下Selection:

这时anchorNode与focusNode都不再是null,Selection中是有内容的,但因为anchorNode与focusNode相同,anchorOffset与focusOffset相同,所以Selection的长度为0,在页面上仍是不可见的。

你可以把它理解为一个「光标」

光标有位置,但是因为其中不含有任何内容,所以长度仍然为0。

总结一下Selection对象

Selection是页面上高亮选区的引用,两者关系类似于document.body与页面上的<body></body>

Selection是有长度的,就像body标签是有内容的。Selection的长度取决于其起点与终点的位置,也就是页面上高亮选区的起点与起点的位置。当起点终点相同时,Selection处于「折叠状态」,页面上的高亮选区也会不可见。

通过Selection的API,我们可以手动的设置Selection的起点终点、把Selection折叠到某一点、向某一方向扩展高亮选区的范围等。

W3C Range 对象

Range与Selection的关系

Range对象也表示页面中的一个范围,但与Selection不同的是它不可见,即使Range的首尾不同,它可以被创建无数次,每个Range都可以不一样。

Selection是一个选择范围的外在,它可以被用户看到,也具有一定的修改自身范围的功能。而Range则是像是选择范围的内在,它不可见,只有被添加到了Selection中才能被用户看到,但它有强大的修改自身范围的功能。

Selection与Range的关系就上上图那样,Range可以通过Selection.addRange()添加进Selection对象中,Selection对象也可通过Selection.removeAllRanges()移除其中的Range。一个Range添加进Selection,选区就会更新,Range从Selection中移出,选区就会消失。

创建Range

Range有多种创建方式:

通常使用的是第1种和第3种方法来获取一个空的和当前Selection内的Range。

Range对象的范围

Range对象的范围概念与Selection的完全相同,只是属性名不同罢了。以下面这段为例:

Star Trek: First Contact is an American science fiction film, released on November 22, 1996.

建立如下选区:

打开控制台运行下列代码:

let sel = window.getSelection();
let ran = sel.getRangeAt(0);
console.log(ran.startContainer === sel.anchorNode);
console.log(ran.endContainer === sel.focusNode);
console.log(ran.startOffset === sel.anchorOffset);
console.log(ran.endOffset === sel.focusOffset);

即可看到打印了四个true:

总结一下Range

Range与Selection的概念很接近,但是一个后者容纳前者的关系。Range的范围计算与Selection的范围计算完全相同,因为Range是Selection的内在,只有一个Range被Selection.addRange添加到Selection中,Range选中的范围才能被用户看到,而且Range也具有比Selection更强大的范围修改功能,详细API可以查看MDN

Microsoft TextRange对象

在IE8及以下并不支持W3C Selection与Range对象,IE有一套自己的selection与TextRange API。这套API基本上与W3C的API类似,但核心概念上确有很大不同。TextRange API被IE11及以下支持,在Edge中被废弃。selection API被IE10及以下支持,在Edge中被废弃。

document.selection

在IE中有一个类似于W3C Selection的东西:document.selection,它代表了页面上的高亮选区,同样它在页面中也是唯一的,不能被销毁,也不能被创建。

与W3C Selection,它所支持的方法就少多了,但是API的功能与W3C的还是有几分相似的。但不幸的是上面的属性只在IE9-10两个版本出现,IE8及以下只有两个简单的属性:

因为document.selection的兼容性问题,通常并不推荐使用,在IE8控制选区,最好的还是使用TextRange。

TextRange

TextRange类似于W3C Range,表示一个选择范围,同样它也是能无限创建的。

但不幸的是createTextRange方法只能在body, button, textArea, input这几种元素上调用。以body为例,在其上调用createTextRange方法:

document.body.createTextRange()

可以看到返回了TextRange对象,其中包含不少操作选区的方法,但与W3C的相比还是弱得多了,更不幸的是,在IE8及以下版本返回的TextRange对象只有几个属性,不含有任何方法:

所以IE还真是不让人省心啊,不过幸好现在也基本可以不用管IE8及以下版本的浏览器了。

当创建一了一个TextRange后,其选择范围就变成了调用了createTextRange方法的那个元素,与W3C类似的是,TextRange创建后默认也是不会在页面上显示的,需要手动调用select方法选中整个范围。以body为例,在其上调用就选中了整个body:

button上调用选中了按钮中的文字。

如果你想获取页面上高亮选区的TextRange的话,在IE9-10上可以使用document.selection.createRange()来从当前的高亮选区中创建一个TextRange。但如果想在任意一个范围直接通过代码创建高亮选区的话那可就麻烦了,只能通过moveStartmoveEnd方法结合把TextRange的范围一点点移到指定的位置上去。

关于TextRange的更多支持可以查看MSDN

虽然TextRange是如此的难用,但仍有一个好消息,那就是IE9-11支持了W3C的Selection及Range API,虽然在IE上W3C版本的个别方法不支持,但起码W3C的API可是比IE的好用多了。

总结一下MS的API

总的来说微软的API就是个废物,基本上是起不到什么作用的。IE9-11支持了W3C标准API,TextRange可以只在有不支持W3C标准的地方作Polyfill。

最终的总结

高亮选区这部分的API在开始看的时候确实是有些复杂的,在现代浏览器上它们的行为基本是相同的,但对于IE需要做一些适配。由于这是一个庞大的API几何,故本文没法对每一个API进行讲解分析,只能对它们的核心概念及一些核心API进行分析。感兴趣的同学可以结合MDN与W3C标准文档进行更深一步的学习。

← NodeJS传输二进制数据  加法器是如何工作的 →