之前寫過一篇只用 JavaScript 實作一鍵複製的作法,在了解原理後,想要在 Angular 中也來實作同樣的功能,其實也很簡單唷!

先提一下,複製、貼上這動作是由瀏覽器所提供的,所以需要使用到 documentwindow 這個瀏覽器物件。

複製文字這件事,基本上三個動作:

  1. 找到目標區塊
  2. 選取目標
  3. 執行複製

找到目標區塊

使用 JavaScript 要找到 DOM 中的某段區塊,可使用 document.querySelector() 方法,搭配 CSS 選擇器來尋找目標區塊,這樣就可以取得所要的 DOM 節點內容。

在 Angular 中也可以使用上面的方法來尋找區塊,但更推薦的作法是使用 Angular 內部的 DOCUMENT Token,透過注入的方式來使用 document 全域物件。

這樣的作法可以用在 AOT,或在寫測試時,可以用模擬的方式來取代全域物件。

// 1. 載入 DOCUMENT Token
import { DOCUMENT } from "@angular/platform-browser";

// ...

// 2. 使用 Token 注入至元件中
constructor( @Inject(DOCUMENT) private dom: Document) { }

// ...

// 3. 設計選取區塊的方法
selectText(selector: string): void {
  const element = this.dom.querySelector(selector);
}

到目前為止,我們在 selectText() 裡面已經可以取到目標區塊並存在 element 裡面了。

由於 documentwindow 都是瀏覽器提供的全域物件,但 Angular 只有提供 DOCUMENT Token 來實作注入,window 了話,則需要自己額外設計。

為了簡化問題,這裡先直接用 window 全域物件來處理。

選取目標

具有 select() 方法的 DOM 元素

目前 DOM 元素中,只有 HTMLInputElementHTMLTextAreaElement 有選取文字的方法 select(),所以針對這兩種 DOM 元素滿容易處理的,直接使用該方法就可以選到目標文字。

其他 DOM 元素

但除上述兩者外,我們還希望能夠複製其他 DOM 元素的文字時,就需要透過 document.Range 物件和 window.getSelection() 方法,藉此來選取目標。

這邊稍微解釋一下 RangeSelection 這兩個物件的差別:

  • Range 物件所包含的是 DOM 裡面的元素,會包含節點及子節點的內容。
  • Selection 物件則是從 Range 物件中,取得選取到的文字,如同使用者用滑鼠選取特定文字般,並記錄選取的起迄位置。

所以這兩者的運作環境是不一樣的,Range 在 DOM 裡面去選取區塊,而 Selection 比較像是瀏覽器功能列上的 Select all 功能,不過他不是全選,而是針對 Range 物件去找到選取目標。

Select All in Web Browser

如此一來 selectText() 改成如下:

// 3. 設計選取區塊的方法
selectText(selector: string): void {
  const element = this.dom.querySelector(selector);
  const isInputElement = element instanceof HTMLInputElement;
  const isTextAreaElement = element instanceof HTMLTextAreaElement;

  if (isInputElement || isTextAreaElement) {
    (element as HTMLInputElement).select();
  } else {
    let range = this.dom.createRange();
    range.selectNodeContents(element);
    let selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
  }
}

執行複製

複製的行為是交由瀏覽器來處理,因此需要調用 document.execCommand() 方法,並告訴瀏覽器要執行 copy 這個動作。這裡的動作很簡單,一行就可以完成,另外再加上一些狀態訊息的輸出。

// 4.  執行瀏覽器的複製指令,複製選取到的文字
execCopy() {
  try {
    const copyStatus = this.dom.execCommand('copy');
    const message = copyStatus ? 'copied' : 'failed';
    console.log(message);
  } catch (error) {
    console.log(`${error}`);
  }
  window.getSelection().removeAllRanges();
}

這個方法最後調用了 removeAllRanges() 方法,這是取消選取的效果,讓使用者察覺不出來到底發生什麼事。

完整程式碼


參考資料:


Poy Chang

Trial and Error