若是你作過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.