本機開發時按下複製按鈕,console 噴 NotAllowedError。換到 iPhone 測,navigator.clipboard 直接是 undefined。這兩個情境都是同一個問題:navigator.clipboard 要求 Secure Context,也就是 HTTPS 或 localhost,但 iOS Safari 對 localhost 也不完全買帳。
解法很簡單,但要做得乾淨需要注意一件事:navigator.clipboard.writeText() 回傳的是 Promise,而 clipboard.js 的 ClipboardJS.copy() 是同步的。要讓這兩條路可以透明切換,就要把 fallback 也包成 Promise。
為什麼要統一 Promise 介面
navigator.clipboard.writeText() 的 signature:
| |
呼叫端用 await 或 .then() 接結果,語意清楚。如果 fallback 是同步的,呼叫端就要自己判斷走哪條路,邏輯散到各處。
包成同樣的 Promise<void>,呼叫端永遠只看到一個 function,底層走哪條路它不用知道。
完整實作
| |
邏輯只有兩條:
navigator.clipboard存在 → 直接 return,它本身就是 Promise- 不存在 → 用
ClipboardJS.copy()執行複製,把同步結果包進new Promise,成功resolve(),拋錯reject()
接到各種框架
有了這個 function,怎麼用完全由呼叫端決定。
原生 JS
| |
Alpine.js
| |
Vue
| |
自訂事件(適合 Livewire 或跨 component)
後端或其他 component 透過 DOM 事件觸發,不需要直接 import function:
| |
Livewire 觸發:
| |
clipboard.js 的角色
clipboard.js 底層用的是 document.execCommand('copy'),這條舊 API 不需要 Secure Context,在 HTTP 和 iOS 上都能動。它處理了 cross-browser 的邊界情況,省掉自己操作 textarea 的麻煩。
execCommand 雖然被標記為 deprecated,但目前所有主流瀏覽器還是支援,短期內不會消失。用它做 fallback 是目前最穩的方案。
安裝
| |
TypeScript 型別已內建,不需要額外安裝 @types/clipboard。
