如果你做过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 whole node
  • selectNodeContents(node) set range to select the whole node contents
  • collapse(toStart) if toStart=true set end=start, otherwise set start=end, thus collapsing the range
  • cloneRange() creates a new range with the same start/end

用于操作range的内容的方法:

  • deleteContents() – remove range content from the document
  • extractContents() – remove range content from the document and return as DocumentFragment
  • cloneContents() – clone range content and return as DocumentFragment
  • insertNode(node) – insert node into the document at the beginning of the range
  • surroundContents(node) – wrap node around 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 in anchorNode where the selection starts,
  • focusNode – the node where the selection ends,
  • focusOffset – the offset in focusNode where the selection ends,
  • isCollapsed – true if selection selects nothing (empty range), or doesn’t exist.
  • rangeCount – count of ranges in the selection, maximum 1 in 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 from 0. In all browsers except firefox, only 0 is used.
  • addRange(range) – add range to selection. All browsers except Firefox ignore the call, if the selection already has an associated range.
  • removeRange(range) – remove range from the selection.
  • removeAllRanges() – remove all ranges.
  • empty() – alias to removeAllRanges

以下方法无需操作底层的range对象就可以直接完成对应的功能:

  • collapse(node, offset) – replace selected range with a new one that starts and ends at the given node, at position offset.
  • setPosition(node, offset) – alias to collapse.
  • 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 given node, position offset,
  • setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset) – replace selection range with the given start anchorNode/anchorOffset and end focusNode/focusOffset. All content in-between them is selected.
  • selectAllChildren(node) – select all children of the node.
  • deleteFromDocument() – remove selected content from the document.
  • containsNode(node, allowPartialContainment = false) – checks whether the selection contains node (partically if the second argument is true)

我们再来看看以下例子代码及其效果:

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的更多相关文章

  1. 抛弃jQuery:DOM API之选择元素

    原文链接:http://blog.garstasio.com/you-dont-need-jquery/selectors/ 我的Blog:http://cabbit.me/you-dont-need ...

  2. C# 调用百度地图Web服务API

    最近公司项目中需要根据两个地点的交通路径和距离做一些数据推荐,为了程序的稳定和用户体验所以想从百度地图 API 采集数据保存到数据库中,经过一翻研究之后选定了百度地图 Web 服务 API 中的 Di ...

  3. C# 调用百度地图 Web 服务 API

    最近公司项目中需要根据两个地点的交通路径和距离做一些数据推荐,为了程序的稳定和用户体验所以想从百度地图 API 采集数据保存到数据库中,经过一翻研究之后选定了百度地图 Web 服务 API 中的 Di ...

  4. web API简介(三):客户端储存之Web Storage API

    概述 前篇:web API简介(二):客户端储存之document.cookie API 客户端储存从某一方面来说和动态网站差不多.动态网站是用服务端来储存数据,而客户端储存是用客户端来储存数据. W ...

  5. 【ASP.NET Web API教程】4.1 ASP.NET Web API中的路由

    原文:[ASP.NET Web API教程]4.1 ASP.NET Web API中的路由 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. ...

  6. ASP.NET Web API中的JSON和XML序列化

    ASP.NET Web API中的JSON和XML序列化 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok ...

  7. ASP.NET Web API中的Routing(路由)

    [译]Routing in ASP.NET Web API 单击此处查看原文 本文阐述了ASP.NET Web API是如何将HTTP requests路由到controllers的. 如果你对ASP ...

  8. ASP.NET MVC和Web API中的Angular2 - 第1部分

    下载源码 - 903.5 KB 内容 第1部分:Visual Studio 2017中的Angular2设置,基本CRUD应用程序,第三方模态弹出控件 第2部分:使用Angular2管道进行过滤/搜索 ...

  9. Web API中的模型验证

    一.模型验证的作用 在ASP.NET Web API中,我们可以使用 System.ComponentModel.DataAnnotations 命名空间中的属性为模型上的属性设置验证规则. 一个模型 ...

随机推荐

  1. vim 自定义设置格式

    在centos 6.9 或 7.3 环境下 可以在自己账户的主目录下新建一个.vimrc 的文件,放入一下代码: syntax on "即设置语法检测,当编辑C或者Shell脚本时,关键字会 ...

  2. 3-剑指Offer: 连续子数组的最大和

    题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量 ...

  3. 网络编程ssh,粘包

    1.什么是socket? TCP,可靠地,面向连接协议,有阻塞rect udp,不可靠的,无线连接的服务 这里因为不需要阻塞,所以速度会很快,但安全性不高 2.关于客户端退出而服务器未退出的解决办法 ...

  4. 201871010126 王亚涛 《面向对象程序设计JAVA》第十四周学习总结

    内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p/11 ...

  5. 201871010101-陈来弟《面向对象程序设计(Java)》第十二周学习总结

    201871010101-陈来弟<面向对象程序设计(Java)>第十二周学习总结 实验十   集合与GUI初步 实验时间 2019-11-14 第一部分   理论部分 1.(1) 用户界面 ...

  6. python基础语法20 面向对象5 exec内置函数的补充,元类,属性查找顺序

    exec内置函数的补充 exec: 是一个python内置函数,可以将字符串的代码添加到名称空间中; - 全局名称空间 - 局部名称空间 exec(字符串形式的代码, 全局名称空间, 局部名称空间) ...

  7. Sublime Text 3安装GoSublime

      GoLand IDE工具虽然在编程时很好用,但是在使用中也有个问题,有时我们可能只是写一个简单的脚本来测试,对于我而言在打开IDE太重量级了,所以捣鼓了GoSublime工具来满足平时最基本的需求 ...

  8. Python Django项目部署 Linux 服务器

    项目依赖: Linux Centos7 (阿里云轻量级服务器) + Python 3.7.2 + Django 2.2.1 + restframework 3.9.4 + mysql 5.7 1 安装 ...

  9. 3,[VS] 编程时的有必要掌握的小技巧_______________________________请从下面第 1 篇看起

    本文导览: 善用“并排显示窗口”功能 做作业/测试时使用 多项目 多个源文件 多个子函数 使用Visual Studio team代码同步工具,及时把项目文件保存到云端 关闭括号分号自动联想 技巧是提 ...

  10. 阅读java编程思想的总结(一)

    学而不思则罔,思而不学则殆 一.对象(Object) 1.机器模型(方案空间),实际解决问题的问题模型(问题空间). 2.我们将问题空间中的元素以及它们在方案空间的表示物称为“对象”(Object). ...