web dom api中的Selection和Range

若是你作過wysiwyg這樣的app,一個很讓人頭疼的問題是如何保證執行bold,italic等格式化操做後保持先前鼠標所在的位置。要好好的解決這個問題,就必須將Selection和Range的api搞搞清楚。javascript

https://javascript.info/selection-rangehtml

Selection and Range

js能夠得到當前的選中區域信息,能夠選擇或者去選擇部分或者所有內容,清楚document中的選中部分,使用一個心的tag來進行包裹等操做。全部這些操做的基石就是Selction和Range這兩個api.java

Range

選擇區的基本概念是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.getSelection().removeAllRanges();document.getSelection().removeAllRanges();.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.

相關文章
相關標籤/搜索