Javascript 選擇(Selection)和范圍(Range)

2023-02-17 10:56 更新

在本章中,我們將介紹文檔中的選擇以及在表單字段(如 ?<input>?)中的選擇。

JavaScript 可以訪問現(xiàn)有的選擇,選擇/取消全部或部分 DOM 節(jié)點的選擇,從文檔中刪除所選部分,將其包裝到一個標簽(tag)中,等。

你可以在本章末尾的“總結(jié)”部分找到一些常見的使用方式??赡芫鸵呀?jīng)滿足了你當前的需求,但如果你閱讀全文,將會有更多收獲。

底層的(underlying)Range 和 Selection 對象很容易掌握,因此,你不需要任何訣竅便可以使用它們做你想要做的事兒。

范圍

選擇的基本概念是 Range:本質(zhì)上是一對“邊界點”:范圍起點和范圍終點。

在沒有任何參數(shù)的情況下,創(chuàng)建一個 Range 對象:

let range = new Range();

然后,我們可以使用 range.setStart(node, offset) 和 range.setEnd(node, offset) 來設(shè)置選擇邊界。

正如你可能猜到的那樣,我們將進一步使用 Range 對象進行選擇,但首先讓我們創(chuàng)建一些這樣的對象。

選擇部分文本

有趣的是,這兩種方法中的第一個參數(shù) node 都可以是文本節(jié)點或元素節(jié)點,而第二個參數(shù)的含義依賴于此。

如果 node 是一個文本節(jié)點,那么 offset 則必須是其文本中的位置。

例如,對于給定的 <p>Hello</p>,我們可以像下面這樣創(chuàng)建一個包含字母 “l(fā)l” 的范圍:

<p id="p">Hello</p>
<script>
  let range = new Range();
  range.setStart(p.firstChild, 2);
  range.setEnd(p.firstChild, 4);

  // 對 range 進行 toString 處理,range 則會把其包含的內(nèi)容以文本的形式返回
  console.log(range); // ll
</script>

在這里,我們獲取 <p> 的第一個子節(jié)點(即文本節(jié)點)并指定其中的文本位置:


選擇元素節(jié)點

或者,如果 node 是一個元素節(jié)點,那么 offset 則必須是子元素的編號。

這對于創(chuàng)建包含整個節(jié)點的范圍很方便,而不是在其文本中的某處停止。

例如,我們有一個更復(fù)雜的文檔片段:

<p id="p">Example: <i>italic</i> and <b>bold</b></p>

這是它的 DOM 結(jié)構(gòu),包含元素和文本節(jié)點:


讓我們?yōu)?nbsp;"Example: <i>italic</i>" 設(shè)置一個范圍。

正如我們所看到的,這個短語正好由 <p> 的索引為 0 和 1 的兩個子元素組成。


  • 起點以 <p> 作為父節(jié)點 node,0 作為偏移量。
  • 因此,我們可以將其設(shè)置為 range.setStart(p, 0)。

  • 終點也是以 <p> 作為父節(jié)點 node,但以 2 作為偏移量(它指定最大范圍,但不包括 offset)。
  • 因此,我們可以將其設(shè)置為 range.setEnd(p, 2)。

示例如下,如果你運行它,你可以看到文本被選中:

<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 以文本形式返回其內(nèi)容,不帶標簽
  console.log(range); // Example: italic

  // 將此范圍應(yīng)用于文檔選擇,后文有解釋
  document.getSelection().addRange(range);
</script>

這是一個更靈活的測試臺,你可以在其中設(shè)置范圍開始/結(jié)束編號,并探索各種情況:


<p id="p">Example: <i>italic</i> and <b>bold</b></p>

From <input id="start" type="number" value=1> – To <input id="end" type="number" value=4>
<button id="button">Click to select</button>
<script>
  button.onclick = () => {
    let range = new Range();

    range.setStart(p, start.value);
    range.setEnd(p, end.value);

    // 應(yīng)用選擇,后文有解釋
    document.getSelection().removeAllRanges();
    document.getSelection().addRange(range);
  };
</script>

例如,在同一個 <p> 中從偏移量 1 到 4 選擇得到的范圍為 <i>italic</i> and <b>bold</b>


起始和結(jié)束的節(jié)點可以不同

我們不是必須在 setStart 和 setEnd 中使用相同的節(jié)點。一個范圍可能會跨越很多不相關(guān)的節(jié)點。唯一要注意的是終點要在起點之后。

選擇更大的片段

讓我們在示例中選擇一個更大的片段,像這樣:


我們已經(jīng)知道如何實現(xiàn)它了。我們只需要將起點和終點設(shè)置為文本節(jié)點中的相對偏移量即可。

我們需要創(chuàng)建一個范圍,它:

  • 從 ?<p>? 的第一個子節(jié)點的位置 2 開始(選擇 "Example: " 中除前兩個字母外的所有字母)
  • 到 ?<b>? 的第一個子節(jié)點的位置 3 結(jié)束(選擇 “bold” 的前三個字母,就這些):
<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);

  console.log(range); // ample: italic and bol

  // 使用此范圍進行選擇(后文有解釋)
  window.getSelection().addRange(range);
</script>

正如你所看到的,選擇我們想要的范圍其實很容易實現(xiàn)。

如果我們想將節(jié)點作為一個整體,我們可以將元素傳入 setStart/setEnd。否則,我們可以在文本層級上進行操作。

range 屬性

我們在上面的示例中創(chuàng)建的 range 對象具有以下屬性:

  • ?startContainer?,?startOffset? —— 起始節(jié)點和偏移量,
    • 在上例中:分別是 ?<p>? 中的第一個文本節(jié)點和 ?2?。
  • ?endContainer?,?endOffset? —— 結(jié)束節(jié)點和偏移量,
    • 在上例中:分別是 ?<b>? 中的第一個文本節(jié)點和 ?3?。
  • ?collapsed? —— 布爾值,如果范圍在同一點上開始和結(jié)束(所以范圍內(nèi)沒有內(nèi)容)則為 ?true?,
    • 在上例中:?false?
  • ?commonAncestorContainer? —— 在范圍內(nèi)的所有節(jié)點中最近的共同祖先節(jié)點,
    • 在上例中:?<p>?

選擇范圍的方法

有許多便利的方法可以操縱范圍。

我們已經(jīng)見過了 setStart 和 setEnd,這還有其他類似的方法。

設(shè)置范圍的起點:

  • ?setStart(node, offset)? 將起點設(shè)置在:?node? 中的位置 ?offset?
  • ?setStartBefore(node)? 將起點設(shè)置在:?node? 前面
  • ?setStartAfter(node)? 將起點設(shè)置在:?node? 后面

設(shè)置范圍的終點(類似的方法):

  • ?setEnd(node, offset)? 將終點設(shè)置為:?node? 中的位置 ?offset?
  • ?setEndBefore(node)? 將終點設(shè)置為:?node? 前面
  • ?setEndAfter(node)? 將終點設(shè)置為:?node? 后面

從技術(shù)上講,setStart/setEnd 可以做任何事,但是更多的方法提供了更多的便捷性。

在所有這些方法中,node 都可以是文本或者元素節(jié)點:對于文本節(jié)點,偏移量 offset 跨越的是很多字母,而對于元素節(jié)點則跨越的是很多子節(jié)點。

更多創(chuàng)建范圍的方法:

  • ?selectNode(node)? 設(shè)置范圍以選擇整個 ?node?
  • ?selectNodeContents(node)? 設(shè)置范圍以選擇整個 ?node? 的內(nèi)容
  • ?collapse(toStart)? 如果 ?toStart=true? 則設(shè)置 end=start,否則設(shè)置 start=end,從而折疊范圍
  • ?cloneRange()? 創(chuàng)建一個具有相同起點/終點的新范圍

編輯范圍的方法

創(chuàng)建范圍后,我們可以使用以下方法操作其內(nèi)容:

  • ?deleteContents()? —— 從文檔中刪除范圍中的內(nèi)容
  • ?extractContents()? —— 從文檔中刪除范圍中的內(nèi)容,并將刪除的內(nèi)容作為 DocumentFragment 返回
  • ?cloneContents()? —— 復(fù)制范圍中的內(nèi)容,并將復(fù)制的內(nèi)容作為 DocumentFragment 返回
  • ?insertNode(node)? —— 在范圍的起始處將 ?node? 插入文檔
  • ?surroundContents(node)? —— 使用 ?node? 將所選范圍中的內(nèi)容包裹起來。要使此操作有效,則該范圍必須包含其中所有元素的開始和結(jié)束標簽:不能像 ?<i>abc? 這樣的部分范圍。

使用這些方法,我們基本上可以對選定的節(jié)點執(zhí)行任何操作。

點擊按鈕運行所選內(nèi)容上的方法,點擊 "resetExample" 進行重置。

<p id="p">Example: <i>italic</i> and <b>bold</b></p>

<p id="result"></p>
<script>
  let range = new Range();

  // 下面演示了上述的每個方法:
  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) { console.log(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>

還有比較范圍的方法,但是很少使用。當你需要它們時,請參考 規(guī)范 或 MDN 手冊。

選擇

Range 是用于管理選擇范圍的通用對象。盡管創(chuàng)建一個 Range 并不意味著我們可以在屏幕上看到一個內(nèi)容選擇。

我們可以創(chuàng)建 Range 對象并傳遞它們 —— 但它們并不會在視覺上選擇任何內(nèi)容。

文檔選擇是由 Selection 對象表示的,可通過 window.getSelection() 或 document.getSelection() 來獲取。一個選擇可以包括零個或多個范圍。至少,Selection API 規(guī)范 是這么說的。不過實際上,只有 Firefox 允許使用 Ctrl+click (Mac 上用 Cmd+click) 在文檔中選擇多個范圍。

這是在 Firefox 中做的一個具有 3 個范圍的選擇的截圖:


其他瀏覽器最多支持 1 個范圍。正如我們將看到的,某些 Selection 方法暗示可能有多個范圍,但同樣,在除 Firefox 之外的所有瀏覽器中,范圍最多是 1。

選擇屬性

如前所述,理論上一個選擇可能包含多個范圍。我們可以使用下面這個方法獲取這些范圍對象:

  • ?getRangeAt(i)? —— 獲取第 ?i? 個范圍,?i? 從 ?0? 開始。在除 Firefox 之外的所有瀏覽器中,僅使用 ?0?。

此外,還有更方便的屬性。

與范圍類似,選擇的起點被稱為“錨點(anchor)”,終點被稱為“焦點(focus)”。

主要的選擇屬性有:

  • ?anchorNode? —— 選擇的起始節(jié)點,
  • ?anchorOffset? —— 選擇開始的 ?anchorNode? 中的偏移量,
  • ?focusNode? —— 選擇的結(jié)束節(jié)點,
  • ?focusOffset? —— 選擇開始處 ?focusNode? 的偏移量,
  • ?isCollapsed? —— 如果未選擇任何內(nèi)容(空范圍)或不存在,則為 ?true ?。
  • ?rangeCount? —— 選擇中的范圍數(shù),除 Firefox 外,其他瀏覽器最多為 ?1?。

選擇和范圍的起點和終點對比

選擇(selection)的錨點/焦點和 Range 的起點和終點有一個很重要的區(qū)別。

正如我們所知道的,Range 對象的起點必須在其終點之前。

但對于選擇,并不總是這樣的。

我們可以在兩個方向上使用鼠標進行選擇:“從左到右”或“從右到左”。

換句話說,當按下鼠標按鍵,然后它在文檔中向前移動時,它結(jié)束的位置(焦點)將在它開始的位置(錨點)之后。

例如,如果用戶使用鼠標從 “Example” 開始選擇到 “italic”:


但是,我們也可以從前向后進行相同的選擇:從 “italic” 到 “Example”(從前向后),這樣它結(jié)束的位置(焦點)將在它開始的位置(錨點)之前。


選擇事件

有一些事件可以跟蹤選擇:

  • ?elem.onselectstart? —— 當在元素 ?elem? 上(或在其內(nèi)部)開始選擇時。例如,當用戶在元素 ?elem? 上按下鼠標按鍵并開始移動指針時。
    • 阻止默認行為取消了選擇的開始。因此,從該元素開始選擇變得不可能,但該元素仍然是可選擇的。用戶只需要從其他地方開始選擇。
  • ?document.onselectionchange? —— 當選擇發(fā)生變化或開始時。
    • 請注意:此處理程序只能在 ?document? 上設(shè)置。它跟蹤的是 ?document? 中的所有選擇。

選擇跟蹤演示

下面是一個小例子,它跟蹤了 document 上當前的選擇,并將選擇邊界顯示出來:

<p id="p">Select me: <i>italic</i> and <b>bold</b></p>

From <input id="from" disabled> – To <input id="to" disabled>
<script>
  document.onselectionchange = function() {
    let selection = document.getSelection();

    let {anchorNode, anchorOffset, focusNode, focusOffset} = selection;

    // anchorNode 和 focusNode 通常是文本節(jié)點
    from.value = `${anchorNode?.data}, offset ${anchorOffset}`;
    to.value = `${focusNode?.data}, offset ${focusOffset}`;
  };
</script>

選擇復(fù)制演示

復(fù)制所選內(nèi)容有兩種方式:

  1. 我們可以使用 ?document.getSelection().toString()? 來獲取其文本形式。
  2. 此外,想要復(fù)制整個 DOM 節(jié)點,例如,如果我們需要保持其格式不變,我們可以使用 ?getRangeAt(...)? 獲取底層的(underlying)范圍。?Range? 對象還具有 ?cloneContents()? 方法,該方法會拷貝范圍中的內(nèi)容并以 ?DocumentFragment? 的形式返回,我們可以將這個返回值插入到其他位置。

下面是將所選內(nèi)容復(fù)制為文本和 DOM 節(jié)點的演示:

<p id="p">Select me: <i>italic</i> and <b>bold</b></p>

Cloned: <span id="cloned"></span>
<br>
As text: <span id="astext"></span>

<script>
  document.onselectionchange = function() {
    let selection = document.getSelection();

    cloned.innerHTML = astext.innerHTML = "";

    // 從范圍復(fù)制 DOM 節(jié)點(這里我們支持多選)
    for (let i = 0; i < selection.rangeCount; i++) {
      cloned.append(selection.getRangeAt(i).cloneContents());
    }

    // 獲取為文本形式
    astext.innerHTML += selection;
  };
</script>

選擇方法

我們可以通過添加/移除范圍來處理選擇:

  • ?getRangeAt(i)? —— 獲取從 ?0? 開始的第 i 個范圍。在除 Firefox 之外的所有瀏覽器中,僅使用 ?0?。
  • ?addRange(range)? —— 將 ?range? 添加到選擇中。如果選擇已有關(guān)聯(lián)的范圍,則除 Firefox 外的所有瀏覽器都將忽略該調(diào)用。
  • ?removeRange(range)? —— 從選擇中刪除 ?range?。
  • ?removeAllRanges()? —— 刪除所有范圍。
  • ?empty()? —— ?removeAllRanges? 的別名。

還有一些方便的方法可以直接操作選擇范圍,而無需中間的 Range 調(diào)用:

  • ?collapse(node, offset)? —— 用一個新的范圍替換選定的范圍,該新范圍從給定的 ?node? 處開始,到偏移 ?offset? 處結(jié)束。
  • ?setPosition(node, offset)? —— ?collapse? 的別名。
  • ?collapseToStart()? —— 折疊(替換為空范圍)到選擇起點,
  • ?collapseToEnd()? —— 折疊到選擇終點,
  • ?extend(node, offset)? —— 將選擇的焦點(focus)移到給定的 ?node?,位置偏移 ?oofset?,
  • ?setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)? —— 用給定的起點 ?anchorNode/anchorOffset? 和終點 ?focusNode/focusOffset? 來替換選擇范圍。選中它們之間的所有內(nèi)容。
  • ?selectAllChildren(node)? —— 選擇 ?node? 的所有子節(jié)點。
  • ?deleteFromDocument()? —— 從文檔中刪除所選擇的內(nèi)容。
  • ?containsNode(node, allowPartialContainment = false)? —— 檢查選擇中是否包含 ?node?(若第二個參數(shù)是 ?true?,則只需包含 ?node? 的部分內(nèi)容即可)

對于大多數(shù)需求,這些方法就夠了,無需訪問底層的(underlying)Range 對象。

例如,選擇段落 <p> 的全部內(nèi)容:

<p id="p">Select me: <i>italic</i> and <b>bold</b></p>

<script>
  // 從 <p> 的第 0 個子節(jié)點選擇到最后一個子節(jié)點
  document.getSelection().setBaseAndExtent(p, 0, p, p.childNodes.length);
</script>

使用范圍來完成同一個操作:

<p id="p">Select me: <i>italic</i> and <b>bold</b></p>

<script>
  let range = new Range();
  range.selectNodeContents(p); // 或者也可以使用 selectNode(p) 來選擇 <p> 標簽

  document.getSelection().removeAllRanges(); // 清除現(xiàn)有選擇(如果有的話)
  document.getSelection().addRange(range);
</script>

如要選擇一些內(nèi)容,請先移除現(xiàn)有的選擇

如果在文檔中已存在選擇,則首先使用 removeAllRanges() 將其清空。然后添加范圍。否則,除 Firefox 外的所有瀏覽器都將忽略新范圍。

某些選擇方法例外,它們會替換現(xiàn)有的選擇,例如 setBaseAndExtent。

表單控件中的選擇

諸如 input 和 textarea 等表單元素提供了 專用的選擇 API,沒有 Selection 或 Range 對象。由于輸入值是純文本而不是 HTML,因此不需要此類對象,一切都變得更加簡單。

屬性:

  • ?input.selectionStart? —— 選擇的起始位置(可寫),
  • ?input.selectionEnd? —— 選擇的結(jié)束位置(可寫),
  • ?input.selectionDirection? —— 選擇方向,其中之一:“forward”,“backward” 或 “none”(例如使用鼠標雙擊進行的選擇),

事件:

  • ?input.onselect? —— 當某個東西被選擇時觸發(fā)。

方法:

  • ?input.select()? —— 選擇文本控件中的所有內(nèi)容(可以是 ?textarea? 而不是 ?input?),
  • ?input.setSelectionRange(start, end, [direction])? —— 在給定方向上(可選),從 ?start? 一直選擇到 ?end?。
  • ?input.setRangeText(replacement, [start], [end], [selectionMode])? —— 用新文本替換范圍中的文本。
  • 可選參數(shù) start 和 end,如果提供的話,則設(shè)置范圍的起點和終點,否則使用用戶的選擇。

    最后一個參數(shù) selectionMode 決定替換文本后如何設(shè)置選擇??赡艿闹禐椋?

    • ?"select"? —— 將選擇新插入的文本。
    • ?"start"? —— 選擇范圍將在插入的文本之前折疊(光標將在其之前)。
    • ?"end"? —— 選擇范圍將在插入的文本之后折疊(光標將緊隨其后)。
    • ?"preserve"? —— 嘗試保留選擇。這是默認值。

現(xiàn)在,讓我們看看這些方法的實際使用。

示例:跟蹤選擇

例如,此段代碼使用 onselect 事件來跟蹤選擇:

<textarea id="area" style="width:80%;height:60px">
Selecting in this text updates values below.
</textarea>
<br>
From <input id="from" disabled> – To <input id="to" disabled>

<script>
  area.onselect = function() {
    from.value = area.selectionStart;
    to.value = area.selectionEnd;
  };
</script>

請注意:

  • ?onselect? 是在某項被選擇時觸發(fā),而在選擇被刪除時不觸發(fā)。
  • 根據(jù) 規(guī)范,表單控件內(nèi)的選擇不應(yīng)該觸發(fā) ?document.onselectionchange? 事件,因為它與 ?document? 選擇和范圍不相關(guān)。一些瀏覽器會生成它,但我們不應(yīng)該依賴它。

示例:移動光標

我們可以更改 selectionStart 和 selectionEnd,二者設(shè)定了選擇。

一個重要的邊界情況是 selectionStart 和 selectionEnd 彼此相等。那正是光標位置?;蛘?,換句話說,當未選擇任何內(nèi)容時,選擇會折疊在光標位置。

因此,通過將 selectionStart 和 selectionEnd 設(shè)置為相同的值,我們可以移動光標。

例如:

<textarea id="area" style="width:80%;height:60px">
Focus on me, the cursor will be at position 10.
</textarea>

<script>
  area.onfocus = () => {
    // 設(shè)置零延遲 setTimeout 以在瀏覽器 "focus" 行為完成后運行
    setTimeout(() => {
      // 我們可以設(shè)置任何選擇
      // 如果 start=end,則光標就會在該位置
      area.selectionStart = area.selectionEnd = 10;
    });
  };
</script>

示例:修改選擇

如要修改選擇的內(nèi)容,我們可以使用 input.setRangeText() 方法。當然,我們可以讀取 selectionStart/End,并在了解選擇的情況下更改 value 的相應(yīng)子字符串,但是 setRangeText 功能更強大,通常更方便。

那是一個有點復(fù)雜的方法。使用其最簡單的單參數(shù)形式,它可以替換用戶選擇的范圍并刪除該選擇。

例如,這里的用戶的選擇將被包裝在 *...* 中:

<input id="input" style="width:200px" value="Select here and click the button">
<button id="button">Wrap selection in stars *...*</button>

<script>
button.onclick = () => {
  if (input.selectionStart == input.selectionEnd) {
    return; // 什么都沒選
  }

  let selected = input.value.slice(input.selectionStart, input.selectionEnd);
  input.setRangeText(`*${selected}*`);
};
</script>

使用更多參數(shù),我們可以設(shè)置范圍 start 和 end。

在下面這個示例中,我們在輸入文本中找到 "THIS",將其替換,并保持替換文本的選中狀態(tài):

<input id="input" style="width:200px" value="Replace THIS in text">
<button id="button">Replace THIS</button>

<script>
button.onclick = () => {
  let pos = input.value.indexOf("THIS");
  if (pos >= 0) {
    input.setRangeText("*THIS*", pos, pos + 4, "select");
    input.focus(); // 聚焦(focus),以使選擇可見
  }
};
</script>

示例:在光標處插入

如果未選擇任何內(nèi)容,或者我們在 setRangeText 中使用了相同的 start 和 end,則僅插入新文本,不會刪除任何內(nèi)容。

我們也可以使用 setRangeText 在“光標處”插入一些東西。

這是一個按鈕,按下后會在光標位置插入 "HELLO",然后光標緊隨其后。如果選擇不為空,則將其替換(我們可以通過比較 selectionStart!=selectionEnd 來進行檢查,為空則執(zhí)行其他操作):

<input id="input" style="width:200px" value="Text Text Text Text Text">
<button id="button">Insert "HELLO" at cursor</button>

<script>
  button.onclick = () => {
    input.setRangeText("HELLO", input.selectionStart, input.selectionEnd, "end");
    input.focus();
  };
</script>

使不可選

要使某些內(nèi)容不可選,有三種方式:

  1. 使用 CSS 屬性 user-select: none。
  2. <style>
    #elem {
      user-select: none;
    }
    </style>
    <div>Selectable <div id="elem">Unselectable</div> Selectable</div>

    這樣不允許選擇從 elem 開始。但是用戶可以在其他地方開始選擇,并將 elem 包含在內(nèi)。

    然后 elem 將成為 document.getSelection() 的一部分,因此選擇實際發(fā)生了,但是在復(fù)制粘貼中,其內(nèi)容通常會被忽略。

  3. 防止 onselectstart 或 mousedown 事件中的默認行為。
  4. <div>Selectable <div id="elem">Unselectable</div> Selectable</div>
    
    <script>
      elem.onselectstart = () => false;
    </script>

    這樣可以防止在 elem 上開始選擇,但是訪問者可以在另一個元素上開始選擇,然后擴展到 elem

    當同一行為上有另一個事件處理程序觸發(fā)選擇時(例如 mousedown),這會很方便。因此我們禁用選擇以避免沖突,仍然允許復(fù)制 elem 內(nèi)容。

  5. 我們還可以使用 ?document.getSelection().empty()? 來在選擇發(fā)生后清除選擇。很少使用這種方法,因為這會在選擇項消失時導(dǎo)致不必要的閃爍。

參考

總結(jié)

我們介紹了用于選擇的兩種不同的 API:

  1. 對于文檔:?Selection? 和 ?Range? 對象。
  2. 對于 ?input?,?textarea?:其他方法和屬性。

第二個 API 非常簡單,因為它處理的是文本。

最常用的方案一般是:

  1. 獲取選擇:
  2. let selection = document.getSelection();
    
    let cloned = /* 要將所選的節(jié)點克隆到的元素 */;
    
    // 然后將 Range 方法應(yīng)用于 selection.getRangeAt(0)
    // 或者,像這樣,用于所有范圍,以支持多選
    for (let i = 0; i < selection.rangeCount; i++) {
      cloned.append(selection.getRangeAt(i).cloneContents());
    }
  3. 設(shè)置選擇
  4. let selection = document.getSelection();
    
    // 直接:
    selection.setBaseAndExtent(...from...to...);
    
    // 或者我們可以創(chuàng)建一個范圍并:
    selection.removeAllRanges();
    selection.addRange(range);

最后,關(guān)于光標。在諸如 <textarea> 之類的可編輯元素中,光標的位置始終位于選擇的起點或終點。我們可以通過設(shè)置 elem.selectionStart 和 elem.selectionEnd 來獲取光標位置或移動光標。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號