如果你做过wysiwyg这样的app,一个很让人头疼的问题是如何保证执行bold,italic等格式化操作后保持先前鼠标所在的位置。要好好的解决这个问题,就必须将Selection和Range的api搞搞清楚。
https://javascript.info/selection-range
js可以获得当前的选中区域信息,可以选择或者去选择部分或者全部内容,清楚document中的选中部分,使用一个心的tag来进行包裹等操作。所有这些操作的基石就是Selction和Range这两个api.
选择区的基本概念是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>
需要注意的是我们实际上不需要在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 nodeselectNodeContents(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.
原文:https://www.cnblogs.com/kidsitcn/p/11628822.html