若是你作过wysiwyg这样的app,一个很让人头疼的问题是如何保证执行bold,italic等格式化操做后保持先前鼠标所在的位置。要好好的解决这个问题,就必须将Selection和Range的api搞搞清楚。javascript
https://javascript.info/selection-rangehtml
js能够得到当前的选中区域信息,能够选择或者去选择部分或者所有内容,清楚document中的选中部分,使用一个心的tag来进行包裹等操做。全部这些操做的基石就是Selction和Range这两个api.java
选择区的基本概念是Range:它是一对边界点组成,分别定义range的start和end.node
每个端点都是以相对于父DOM Node的offset这些信息来表达的point。若是父亲node是一个element element node,那么offset就是child的number号,儿对于text node,则是在text中的位置。咱们以例子来讲明,咱们以选中某些内容为例:api
首先,咱们能够建立一个range:app
let range = new Range();
而后咱们能够经过使用 range.setStart(node, offset), range.setEnd(node, offset) 这两个api函数来设定range的边界,好比,若是咱们的html代码以下:dom
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
其对应的dom树以下:ide
咱们来选择 Example: <i>italic</i> 这一部份内容。它其实是p这个父元素的前面两个儿子节点(包含text node)函数
咱们来看实际的代码:this
<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
document.
getSelection
(
)
.
removeAllRanges
(
)
;
.
getSelection
(
)
.
removeAllRanges
(
)
;
.getSelection().removeAllRanges();
document.getSelection().addRange(range); </script>
须要注意的是咱们实际上不须要在setStart和setEnd调用中使用同一个参考node节点,一个范围可能延展涵盖到多个不相关的节点。惟一须要注意的是end必须是在start的后面
假设咱们想像下面的状况来作选中操做:
这也能够使用代码轻松实现,咱们须要作的是设定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属性以下图取值:
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
contentscollapse(toStart)
if toStart=true
set 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)
– insert node
into the document at the beginning of the rangesurroundContents(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
Range是一个用于管理selection ranges的通用对象。咱们能够建立这些range对象,而后传递给dom api
document的selection是由Selection对象来表征的,这能够经过 window.getSelection()或者document.getSelection() 来得到。
一个selection能够包括0个或者多个ranges,可是在实际使用中,仅仅firefox容许选中多个ranges,这须要经过ctrl+click来实现,好比下图:
和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.1. elem.onselectstart -当一个selection从elem这个元素开始发生时,好比用户当按下左键同时拖动鼠标时就会发生该事件。须要注意的是,若是elem被prevent default时,不发生该事件
2. document.onselectionchange,这个事件只能在document上发生,只要有selection发生变化就会触发该事件
看如下代码
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
)咱们再来看看如下例子代码及其效果:
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.