web dom api中的Selection和Range
如果你做过wysiwyg这样的app,一个很让人头疼的问题是如何保证执行bold,italic等格式化操作后保持先前鼠标所在的位置。要好好的解决这个问题,就必须将Selection和Range的api搞搞清楚。
https://javascript.info/selection-range
Selection and Range
js可以获得当前的选中区域信息,可以选择或者去选择部分或者全部内容,清楚document中的选中部分,使用一个心的tag来进行包裹等操作。所有这些操作的基石就是Selction和Range这两个api.
Range
选择区的基本概念是Range:它是一对边界点组成,分别定义range的start和end.
每一个端点都是以相对于父DOM Node的offset这些信息来表达的point。如果父亲node是一个element element node,那么offset就是child的number号,儿对于text node,则是在text中的位置。我们以例子来说明,我们以选中某些内容为例:
首先,我们可以创建一个range:
let range = new Range();
然后我们可以通过使用 range.setStart(node, offset), range.setEnd(node, offset) 这两个api函数来设定range的边界,比如,如果我们的html代码如下:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
其对应的dom树如下:

我们来选择 Example: <i>italic</i> 这一部分内容。它实际上是p这个父元素的前面两个儿子节点(包含text node)

我们来看实际的代码:
<p id="p">Example: <i>italic</i> and <b>bold</b></p> <script>
let range = new Range(); range.setStart(p, 0);
range.setEnd(p, 2); // toString of a range returns its content as text (without tags)
alert(range); // Example: italic // apply this range for document selection (explained later)
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
</script>
- range.setStart(p,0)- 设定该选择范围是p父元素的第0个child节点(也就是一个text node: Example: )
- range.setEnd(p,2)-指定该range将延展到p父元素的第2个child(也就是" and "这个text node),但是注意这里是不包含额,也就是说实际上是到第1个child,因此也就是 i 节点
需要注意的是我们实际上不需要在setStart和setEnd调用中使用同一个参考node节点,一个范围可能延展涵盖到多个不相关的节点。唯一需要注意的是end必须是在start的后面
选中text nodes的部分,而非全部
假设我们想像下面的情况来做选中操作:

这也可以使用代码轻松实现,我们需要做的是设定start和end时使用相对于text nodes的offset位置就好了。
我们需要先创建一个range:
1. range的start是p父亲元素的first child的position 2,也就是"ample:"
2.range的end则是b父亲元素的position 3,也就是"bol"
<p id="p">Example: <i>italic</i> and <b>bold</b></p> <script>
let range = new Range(); range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3); alert(range); // ample: italic and bol // use this range for selection (explained later)
document.getSelection().removeAllRanges();??
window.getSelection().addRange(range);
</script>
这时,range属性如下图取值:

- startContainer,startOffset-分别指定start点的node和相对于该node的offset,本例中是p节点的首个text node子节点,以及第2个position
- endContainer,endOffset-分别指定end点的node和offset,本例中是b节点的首个text node子节点,以及position 3
- collapsed - 布尔值,如果star和end point都指向了同一个point的话为true,也意味着在该range中没有内容被选中,本例中取值为false
- commonAncestorContainer - 在本range中所有节点的最近的共同祖先节点,本例中为p节点
Range的方法methods
range对象有很多有用的方法用于操作range:
设定range的start:
setEnd(node, offset) set end at: position offset in node
setEndBefore(node) set end at: right before node
setEndAfter(node) set end at: right after node
正如前面演示的那样,node可以是一个text或者element node,对于text node, offset意思是忽略几个字符,而如果是element node,则指忽略多少个child nodes
其他的方法:
selectNode(node)set range to select the wholenodeselectNodeContents(node)set range to select the wholenodecontentscollapse(toStart)iftoStart=trueset end=start, otherwise set start=end, thus collapsing the rangecloneRange()creates a new range with the same start/end
用于操作range的内容的方法:
deleteContents()– remove range content from the documentextractContents()– remove range content from the document and return as DocumentFragmentcloneContents()– clone range content and return as DocumentFragmentinsertNode(node)– insertnodeinto the document at the beginning of the rangesurroundContents(node)– wrapnodearound range content. For this to work, the range must contain both opening and closing tags for all elements inside it: no partial ranges like<i>abc.
有了这些有用的方法,我们就可以基本上针对选中的nodes做任何事情了,看下面一个比价复杂的例子:
Click buttons to run methods on the selection, "resetExample" to reset it. <p id="p">Example: <i>italic</i> and <b>bold</b></p> <p id="result"></p>
<script>
let range = new Range(); // Each demonstrated method is represented here:
let methods = {
deleteContents() {
range.deleteContents()
},
extractContents() {
let content = range.extractContents();
result.innerHTML = "";
result.append("extracted: ", content);
},
cloneContents() {
let content = range.cloneContents();
result.innerHTML = "";
result.append("cloned: ", content);
},
insertNode() {
let newNode = document.createElement('u');
newNode.innerHTML = "NEW NODE";
range.insertNode(newNode);
},
surroundContents() {
let newNode = document.createElement('u');
try {
range.surroundContents(newNode);
} catch(e) { alert(e) }
},
resetExample() {
p.innerHTML = `Example: <i>italic</i> and <b>bold</b>`;
result.innerHTML = ""; range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3); window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}; for(let method in methods) {
document.write(`<div><button onclick="methods.${method}()">${method}</button></div>`);
} methods.resetExample();
</script>
除此之外,还有一些很少使用的用于比较range的api,https://developer.mozilla.org/en-US/docs/Web/API/Range
Selection
Range是一个用于管理selection ranges的通用对象。我们可以创建这些range对象,然后传递给dom api
document的selection是由Selection对象来表征的,这可以通过 window.getSelection()或者document.getSelection() 来获得。
一个selection可以包括0个或者多个ranges,但是在实际使用中,仅仅firefox允许选中多个ranges,这需要通过ctrl+click来实现,比如下图:

selection对象的属性
和range类似,一个selection也有start,被称为"anchor",和一个end,被称为"focus",主要的属性如下:
anchorNode– the node where the selection starts,anchorOffset– the offset inanchorNodewhere the selection starts,focusNode– the node where the selection ends,focusOffset– the offset infocusNodewhere the selection ends,isCollapsed–trueif selection selects nothing (empty range), or doesn’t exist.rangeCount– count of ranges in the selection, maximum1in all browsers except Firefox.
selection events
1. elem.onselectstart -当一个selection从elem这个元素开始发生时,比如用户当按下左键同时拖动鼠标时就会发生该事件。需要注意的是,如果elem被prevent default时,不发生该事件
2. document.onselectionchange,这个事件只能在document上发生,只要有selection发生变化就会触发该事件
看以下代码

selection的常用methods:
getRangeAt(i)– get i-th range, starting from0. In all browsers except firefox, only0is used.addRange(range)– addrangeto selection. All browsers except Firefox ignore the call, if the selection already has an associated range.removeRange(range)– removerangefrom the selection.removeAllRanges()– remove all ranges.empty()– alias toremoveAllRanges
以下方法无需操作底层的range对象就可以直接完成对应的功能:
collapse(node, offset)– replace selected range with a new one that starts and ends at the givennode, at positionoffset.setPosition(node, offset)– alias tocollapse.collapseToStart()– collapse (replace with an empty range) to selection start,collapseToEnd()– collapse to selection end,extend(node, offset)– move focus of the selection to the givennode, positionoffset,setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)– replace selection range with the given startanchorNode/anchorOffsetand endfocusNode/focusOffset. All content in-between them is selected.selectAllChildren(node)– select all children of thenode.deleteFromDocument()– remove selected content from the document.containsNode(node, allowPartialContainment = false)– checks whether the selection containsnode(partically if the second argument istrue)
我们再来看看以下例子代码及其效果:

Selection in form controls
Form元素,比如input, textarea则提供了更多的api用于selection操作和处理,而没有selection或者说range对象。由于input的value仅仅是text,而非html,因此也没有必要提供这些selection和range对象,事情会变得更加简单。
input.selectionStart– position of selection start (writeable),input.selectionEnd– position of selection start (writeable),input.selectionDirection– selection direction, one of: “forward”, “backward” or “none” (if e.g. selected with a double mouse click)
input.onselect – triggers when something is selected.
web dom api中的Selection和Range的更多相关文章
- 抛弃jQuery:DOM API之选择元素
原文链接:http://blog.garstasio.com/you-dont-need-jquery/selectors/ 我的Blog:http://cabbit.me/you-dont-need ...
- C# 调用百度地图Web服务API
最近公司项目中需要根据两个地点的交通路径和距离做一些数据推荐,为了程序的稳定和用户体验所以想从百度地图 API 采集数据保存到数据库中,经过一翻研究之后选定了百度地图 Web 服务 API 中的 Di ...
- C# 调用百度地图 Web 服务 API
最近公司项目中需要根据两个地点的交通路径和距离做一些数据推荐,为了程序的稳定和用户体验所以想从百度地图 API 采集数据保存到数据库中,经过一翻研究之后选定了百度地图 Web 服务 API 中的 Di ...
- web API简介(三):客户端储存之Web Storage API
概述 前篇:web API简介(二):客户端储存之document.cookie API 客户端储存从某一方面来说和动态网站差不多.动态网站是用服务端来储存数据,而客户端储存是用客户端来储存数据. W ...
- 【ASP.NET Web API教程】4.1 ASP.NET Web API中的路由
原文:[ASP.NET Web API教程]4.1 ASP.NET Web API中的路由 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. ...
- ASP.NET Web API中的JSON和XML序列化
ASP.NET Web API中的JSON和XML序列化 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok ...
- ASP.NET Web API中的Routing(路由)
[译]Routing in ASP.NET Web API 单击此处查看原文 本文阐述了ASP.NET Web API是如何将HTTP requests路由到controllers的. 如果你对ASP ...
- ASP.NET MVC和Web API中的Angular2 - 第1部分
下载源码 - 903.5 KB 内容 第1部分:Visual Studio 2017中的Angular2设置,基本CRUD应用程序,第三方模态弹出控件 第2部分:使用Angular2管道进行过滤/搜索 ...
- Web API中的模型验证
一.模型验证的作用 在ASP.NET Web API中,我们可以使用 System.ComponentModel.DataAnnotations 命名空间中的属性为模型上的属性设置验证规则. 一个模型 ...
随机推荐
- 9. [mmc subsystem] host(第三章)——sdhci-pltfm说明
一.sdhci-pltfm说明 sdhci-pltfm并不是实际某个host的driver. sdhci-pltfm是指在sdhci core的基础上,提供了统一对sdhci_host的必要属性进行解 ...
- [TCP/IP] ping traceroute和TTL
1.Time To Live是生存时间的意思,就是说这个ping的数据包能在网络上存在多少时间.当我们对网络上的主机进行ping操作的时候,我们本地机器会发出一个数据包,数据包经过一定数量的路由器传送 ...
- luoguP1198 [JSOI2008]最大数
https://www.luogu.org/problem/P1198 update!!! 经过老师的讲解,惊人的发现这题有用更简单数据结构维护的解法,而越简单的数据结构(如果能够用的话),越好(实现 ...
- 15-numpy笔记-莫烦pandas-3
代码 import pandas as pd import numpy as np dates = pd.date_range('20130101', periods=6) df=pd.DataFra ...
- Java8——jdk——java.time包
public class TestLocalDateTime { //6.ZonedDate.ZonedTime.ZonedDateTime : 带时区的时间或日期 @Test public void ...
- CloudCompare打开pcd文件
Ubuntu下CloudCompare稳定版本无法打开pcd点云文件,切换到edge版本即可 $ sudo snap refresh --edge cloudcompare
- Python进阶-Ⅷ 匿名函数 lambda
1.匿名函数的引入 为了解决那些功能很简单的需求而设计的一句话函数 def func(i): return 2*i # 简化之后 func = lambda i:2*i #todo 其中:func是函 ...
- 毕业一年的大专生程序员工作总结(java后台)
文章导读 一.回眸过去-- 闲扯的话-- 零碎的技术 二.经验总结-- 沟通交流-- 贵在坚持-- 合理规划 三.展望未来-- 积累行业背景-- 学习清单 四.最后补充 一. 回牟过去 1.闲扯的话 ...
- vue-quill-editor富文本编辑器,添加了汉化样式却汉化不了
背景 今天在做后台管理系统时,尝试整合 vue-quill-editor 富文本编辑器,整合完成后,想进行汉化,查阅资料发现,只需自己定义样式替换即可. 原因 当进行汉化时,发现样式并没有替换,汉化失 ...
- [LeetCode] 119. Pascal's Triangle II 杨辉三角之二
Given a non-negative index k where k ≤ 33, return the kth index row of the Pascal's triangle. Note t ...