日常業(yè)務(wù)中,會常常用到拷貝、剪切的需求,此外一些針對C端的平臺復(fù)制內(nèi)容下面會新增一段版權(quán)信息,那么這些都是如何實現(xiàn)的呢?
其實是用的window.execCommand方法,該方法允許運行命令來操作可編輯區(qū)域的元素,執(zhí)行系統(tǒng)的copy命令或者cut命令,實現(xiàn)拷貝和剪切內(nèi)容到系統(tǒng)剪切板中。window.execCommand可以執(zhí)行copy和cut外,還有其他的命令可參考如下表格:
| 命令 | 描述 |
|---|---|
| backColor | 修改文檔的背景顏色。在 styleWithCss 模式下,則只影響容器元素的背景顏色。這需要一個<color> 類型的字符串值作為參數(shù)傳入。注意,IE 瀏覽器用這個設(shè)置文字的背景顏色。 |
| bold | 開啟或關(guān)閉選中文字或插入點的粗體字效果。IE 瀏覽器使用 <strong>標(biāo)簽,而不是<b>標(biāo)簽。 |
| ClearAuthenticationCache | 清除緩存中的所有身份驗證憑據(jù)。 |
| contentReadOnly | 通過傳入一個布爾類型的參數(shù)來使能文檔內(nèi)容的可編輯性。(IE 瀏覽器不支持) |
| copy | 拷貝當(dāng)前選中內(nèi)容到剪貼板。啟用這個功能的條件因瀏覽器不同而不同,而且不同時期,其啟用條件也不盡相同。使用之前請檢查瀏覽器兼容表,以確定是否可用。 |
| createLink | 將選中內(nèi)容創(chuàng)建為一個錨鏈接。這個命令需要一個hrefURI 字符串作為參數(shù)值傳入。URI 必須包含至少一個字符,例如一個空格。(瀏覽器會創(chuàng)建一個空鏈接) |
| cut | 剪貼當(dāng)前選中的文字并復(fù)制到剪貼板。啟用這個功能的條件因瀏覽器不同而不同,而且不同時期,其啟用條件也不盡相同。使用之前請檢查瀏覽器兼容表,以確定是否可用。 |
| decreaseFontSize | 給選中文字加上 <small> 標(biāo)簽,或在選中點插入該標(biāo)簽。(IE 瀏覽器不支持) |
| defaultParagraphSeparator | 更改在可編輯文本區(qū)域中創(chuàng)建新段落時使用的段落分隔符。有關(guān)更多詳細(xì)信息,請參閱標(biāo)記生成的差異。 |
| delete | 刪除選中部分。 |
| enableAbsolutePositionEditor | 啟用或禁用允許移動絕對定位元素的抓取器。Firefox 63 Beta/Dev Edition 默認(rèn)禁用此功能 (bug 1449564)。 |
| enableInlineTableEditing | 啟用或禁用表格行和列插入和刪除控件。(IE 瀏覽器不支持) |
| enableObjectResizing | 啟用或禁用圖像和其他對象的大小可調(diào)整大小手柄。(IE 瀏覽器不支持) |
| fontName | 在插入點或者選中文字部分修改字體名稱。需要提供一個字體名稱字符串 (例如:"Arial") 作為參數(shù)。 |
| fontSize | 在插入點或者選中文字部分修改字體大小。需要提供一個 HTML 字體尺寸 (1-7) 作為參數(shù)。 |
| foreColor | 在插入點或者選中文字部分修改字體顏色。需要提供一個顏色值字符串作為參數(shù)。 |
| formatBlock | 添加一個 HTML 塊式標(biāo)簽在包含當(dāng)前選擇的行,如果已經(jīng)存在了,更換包含該行的塊元素 (在 Firefox 中,BLOCKQUOTE 是一個例外 -它將包含任何包含塊元素). 需要提供一個標(biāo)簽名稱字符串作為參數(shù)。幾乎所有的塊樣式標(biāo)簽都可以使用 (例如。"H1", "P", "DL", "BLOCKQUOTE"). (IE 瀏覽器僅僅支持標(biāo)題標(biāo)簽 H1 - H6, ADDRESS,和 PRE,使用時還必須包含標(biāo)簽分隔符 < >, 例如 < H1 > |
| forwardDelete | 刪除光標(biāo)所在位置的字符。和按下刪除鍵一樣。 |
| heading | 添加一個標(biāo)題標(biāo)簽在光標(biāo)處或者所選文字上。需要提供標(biāo)簽名稱字符串作為參數(shù)(例如:"H1"、"H6")(IE 和 Safari 不支持) |
| hiliteColor | 更改選擇或插入點的背景顏色。需要一個顏色值字符串作為值參數(shù)傳遞。UseCSS 必須開啟此功能。(IE 瀏覽器不支持) |
| increaseFontSize | 在選擇或插入點周圍添加一個 BIG 標(biāo)簽。(IE 瀏覽器不支持) |
| indent | 縮進(jìn)選擇或插入點所在的行,在 Firefox 中,如果選擇多行,但是這些行存在不同級別的縮進(jìn),只有縮進(jìn)最少的行被縮進(jìn)。 |
| insertBrOnReturn | 控制當(dāng)按下 Enter 鍵時,是插入 br 標(biāo)簽還是把當(dāng)前塊元素變成兩個。(IE 瀏覽器不支持) |
| insertHorizontalRule | 在插入點插入一個水平線(刪除選中的部分) |
| insertHTML | 在插入點插入一個 HTML 字符串(刪除選中的部分)。需要一個個 HTML 字符串作為參數(shù)。(IE 瀏覽器不支持) |
| insertImage | 在插入點插入一張圖片(刪除選中的部分)。需要一個 URL 字符串作為參數(shù)。這個 URL 圖片地址至少包含一個字符??瞻鬃址部梢裕↖E 會創(chuàng)建一個鏈接其值為 null) |
| insertOrderedList | 在插入點或者選中文字上創(chuàng)建一個有序列表 |
| insertUnorderedList | 在插入點或者選中文字上創(chuàng)建一個無序列表。 |
| insertParagraph | 在選擇或當(dāng)前行周圍插入一個段落。(IE 會在插入點插入一個段落并刪除選中的部分.) |
| insertText | 在光標(biāo)插入位置插入文本內(nèi)容或者覆蓋所選的文本內(nèi)容。 |
| italic | 在光標(biāo)插入點開啟或關(guān)閉斜體字。 (Internet Explorer 使用 EM 標(biāo)簽,而不是 I ) |
| justifyCenter | 對光標(biāo)插入位置或者所選內(nèi)容進(jìn)行文字居中。 |
| justifyFull | 對光標(biāo)插入位置或者所選內(nèi)容進(jìn)行文本對齊。 |
| justifyLeft | 對光標(biāo)插入位置或者所選內(nèi)容進(jìn)行左對齊。 |
| justifyRight | 對光標(biāo)插入位置或者所選內(nèi)容進(jìn)行右對齊。 |
| outdent | 對光標(biāo)插入行或者所選行內(nèi)容減少縮進(jìn)量。 |
| paste | 在光標(biāo)位置粘貼剪貼板的內(nèi)容,如果有被選中的內(nèi)容,會被替換。剪貼板功能必須在 user.js 配置文件中啟用。參閱 [1]. |
| redo | 重做被撤銷的操作。 |
| removeFormat | 對所選內(nèi)容去除所有格式 |
| selectAll | 選中編輯區(qū)里的全部內(nèi)容。 |
| strikeThrough | 在光標(biāo)插入點開啟或關(guān)閉刪除線。 |
| subscript | 在光標(biāo)插入點開啟或關(guān)閉下角標(biāo)。 |
| superscript | 在光標(biāo)插入點開啟或關(guān)閉上角標(biāo)。 |
| underline | 在光標(biāo)插入點開啟或關(guān)閉下劃線。 |
| undo | 撤銷最近執(zhí)行的命令。 |
| unlink | 去除所選的錨鏈接的<a>標(biāo)簽 |
| styleWithCSS | 用這個取代 useCSS 命令。參數(shù)如預(yù)期的那樣工作,i.e. true modifies/generates 風(fēng)格的標(biāo)記屬性,false 生成格式化元素。 |
| AutoUrlDetect | 更改瀏覽器自動鏈接行為(僅 IE 瀏覽器支持) |
document.execCommand可以做的事情很多,但是需要說明的是它在最新的web標(biāo)準(zhǔn)中已經(jīng)被廢棄。來看一下MDN的介紹:
已棄用: 不再推薦使用該特性。雖然一些瀏覽器仍然支持它,但也許已從相關(guān)的 web 標(biāo)準(zhǔn)中移除,也許正準(zhǔn)備移除或出于兼容性而保留。請盡量不要使用該特性,并更新現(xiàn)有的代碼;參見本頁面底部的兼容性表格以指導(dǎo)你作出決定。請注意,該特性隨時可能無法正常工作。
瀏覽器兼容性——取自MDN

上面只展示了幾個屬性,可以看到,copy和cut是全部瀏覽器都支持的,此外還有paste,selectAll等等,在使用的時候最好做一下判斷,其他的命令基本都已不支持,就不推薦再使用了。
介紹完document.execCommand,接下來我們準(zhǔn)備”上菜“!
有請今天的主角登場 —— clipboardjs
基本使用
首先,我們來看一下clipboardjs的使用方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>target-div</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<div>hello</div>
<button class="btn" data-clipboard-action="copy" data-clipboard-target="div">
Copy
</button>
<!-- 2. Include library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.10/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
const clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>
</html>
點擊button按鈕之后,控制臺打印如下內(nèi)容:

-
Action:執(zhí)行命令 -
Text:放入到剪切板的內(nèi)容 -
Trigger:觸發(fā)的節(jié)點
源碼解析
可以邊參考源碼邊看:點擊查看源碼
打開源碼進(jìn)入clipboardjs頁面,可以看到定義了一個Clipboard類并繼承于Emitter,Emitter是負(fù)責(zé)訂閱命令執(zhí)行之后剪切板操作成功和失敗的關(guān)鍵,用到了Emitter的on和emit。
tiny-emitter是一個簡單實現(xiàn)發(fā)布訂閱的包(查看tiny-emitter源碼),在函數(shù)的原型對象內(nèi)定義了on、once、emit、off四個原型方法,具體的實現(xiàn)非常簡單,代碼量很少,這里不做過多介紹,感興趣可以看tiny-emiter源碼。
import Emitter from 'tiny-emitter';
class Clipboard extends Emitter {
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
constructor(trigger, options) {
super();
this.resolveOptions(options);
this.listenClick(trigger);
}
// 省略...
}
構(gòu)造函數(shù)內(nèi)有resolveOptions和listenClick方法分別是處理參數(shù)和監(jiān)聽點擊事件。我們來一一分析。
resolveOption
resolveOptions(options = {}) {
this.action =
typeof options.action === 'function'
? options.action
: this.defaultAction;
this.target =
typeof options.target === 'function'
? options.target
: this.defaultTarget;
this.text =
typeof options.text === 'function' ? options.text : this.defaultText;
this.container =
typeof options.container === 'object' ? options.container : document.body;
}
resolveOptions函數(shù)對this.action、this.target、this.text、this.container進(jìn)行處理取值。
當(dāng)不想在元素中寫如data-clipboard-XXXX的自定義屬性的時候,actions、target、text都允許通過配置項的方式聲明一個函數(shù),做自己的業(yè)務(wù)需求,最后返回一個值。所以在上面我們會看到,是通過typeof判斷是否為function類型,如果不是函數(shù)類型則取默認(rèn)值,默認(rèn)值則是通過自定義屬性進(jìn)行匹配,格式如:data-clipboard-XXXX。
元素的data-clipboard-XXXX會有函數(shù)專門處理并讀取對應(yīng)的自定義屬性值:
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
const attribute = `data-clipboard-${suffix}`;
if (!element.hasAttribute(attribute)) {
return;
}
return element.getAttribute(attribute);
}
下面來解釋一下每個屬性的含義:
-
action是執(zhí)行的方法,可以是copy復(fù)制,也可以是cut剪切。 -
target是目標(biāo)錨點,如data-clipboard-target="div"中的target就是div,此外也可以是className類名或者#id節(jié)點id,最終是通過document.querySelector(selector);來獲取節(jié)點。 -
text是選中的文本內(nèi)容 -
container是要將目標(biāo)節(jié)點插入的父級根節(jié)點位置,可以根據(jù)實際業(yè)務(wù)需求配置,比如在vue中我們希望插入的是#app容器內(nèi),則可以傳入document.getElementById('app'),否則默認(rèn)是插入到document.body
以上就是resolveOptions所做的工作。下面來講解一下this.listenClick函數(shù)。
listenClick
import listen from 'good-listener';
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
listenClick(trigger) {
this.listener = listen(trigger, 'click', (e) => this.onClick(e));
}
listenClick函數(shù)負(fù)責(zé)監(jiān)聽click點擊事件。會遍歷所有的節(jié)點,給節(jié)點添加監(jiān)聽事件,這個是通過good-listener插件實現(xiàn),插件是由作者本人寫的,實現(xiàn)原理很簡單,通過檢查trigger是什么類型
- 節(jié)點(
node):直接給節(jié)點添加listen監(jiān)聽事件function listenNode(node, type, callback) { node.addEventListener(type, callback); return { destroy: function() { node.removeEventListener(type, callback); } } } - 節(jié)點列表(
nodeList):會遍歷列表,然后給每個節(jié)點添加監(jiān)聽事件function listenNodeList(nodeList, type, callback) { Array.prototype.forEach.call(nodeList, function(node) { node.addEventListener(type, callback); }); return { destroy: function() { Array.prototype.forEach.call(nodeList, function(node) { node.removeEventListener(type, callback); }); } } } - 字符串 :傳入的是字符串則直接監(jiān)聽
document.body// 傳入是字符串 function listenSelector(selector, type, callback) { return delegate(document.body, selector, type, callback); } // 監(jiān)聽document.body function delegate(element, selector, type, callback, useCapture) { var listenerFn = listener.apply(this, arguments); element.addEventListener(type, listenerFn, useCapture); return { destroy: function() { element.removeEventListener(type, listenerFn, useCapture); } } }
回到listenClick函數(shù),當(dāng)我們點擊之后會觸發(fā)執(zhí)行this.onClick函數(shù),會觸發(fā)執(zhí)行剪切板命令,最后返回選中的文本內(nèi)容,利用Emitter.emit發(fā)布結(jié)果。我們可以通過clipboard.on('success')訂閱其結(jié)果。這個就是繼承于tiny-emitter的作用。
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
const action = this.action(trigger) || 'copy';
const text = ClipboardActionDefault({
action,
container: this.container,
target: this.target(trigger),
text: this.text(trigger),
});
// Fires an event based on the copy operation result.
this.emit(text ? 'success' : 'error', {
action,
text,
trigger,
clearSelection() {
if (trigger) {
trigger.focus();
}
window.getSelection().removeAllRanges();
},
});
}
實例化ClipboardAction后,創(chuàng)建一個監(jiān)聽事件,會監(jiān)聽所有點擊行為,當(dāng)監(jiān)聽到某一個節(jié)點點擊,利用節(jié)點的Event的e.delegateTarget || e.currentTarget取得目標(biāo)節(jié)點內(nèi)容對象信息。根據(jù)trigger獲取到action,this.action可能是對象,也可能是字符串,當(dāng)this.action不是對象的時候會異常,然后直接賦值copy。
text是由ClipboardActionDefault函數(shù)得到,我們來看看它做了什么處理。
import ClipboardActionCut from './cut';
import ClipboardActionCopy from './copy';
const ClipboardActionDefault = (options = {}) => {
const { action = 'copy', container, target, text } = options;
// 省略action和target的邊界處理代碼...
// 定義基于“文本”屬性的選擇策略。
if (text) {
return ClipboardActionCopy(text, { container });
}
// 定義基于' target '屬性的選擇策略。
if (target) {
return action === 'cut'
? ClipboardActionCut(target)
: ClipboardActionCopy(target, { container });
}
};
根據(jù)resolveOptions函數(shù)對this.text和this.target的處理,我們可以知道,它們有兩種數(shù)據(jù)類型,一種是函數(shù)(typeof === function)類型,一種是通過data-clipboard-xxxx自定義屬性獲取的值。那么如果text沒有聲明function或者在節(jié)點中聲明自定義屬性值,那么它將不會通過if(text)的判斷,直接跳過,往下執(zhí)行。隨后是target,由于前面做了邊界異常處理,所以這里作為兜底執(zhí)行,根據(jù)cut和copy分配給兩個不同的函數(shù)處理,這里我們只介紹copy,因為cut和copy僅有執(zhí)行命令document.execCommand執(zhí)行的差異。
我們來看看ClipboardActionCopy做的工作:
import select from 'select';
import command from '../common/command';
import createFakeElement from '../common/create-fake-element';
/**
* Create fake copy action wrapper using a fake element.
* @param {String} target
* @param {Object} options
* @return {String}
*/
const fakeCopyAction = (value, options) => {
const fakeElement = createFakeElement(value);
options.container.appendChild(fakeElement);
const selectedText = select(fakeElement);
command('copy');
fakeElement.remove();
return selectedText;
};
/**
* Copy action wrapper.
* @param {String|HTMLElement} target
* @param {Object} options
* @return {String}
*/
const ClipboardActionCopy = (
target,
options = { container: document.body }
) => {
let selectedText = '';
if (typeof target === 'string') {
selectedText = fakeCopyAction(target, options);
} else if (
target instanceof HTMLInputElement &&
!['text', 'search', 'url', 'tel', 'password'].includes(target?.type)
) {
// If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange
selectedText = fakeCopyAction(target.value, options);
} else {
selectedText = select(target);
command('copy');
}
return selectedText;
};
export default ClipboardActionCopy;
先來看ClipboardActionCopy函數(shù),針對三種類型做了判斷,分別是
- 是字符串類型,說明目標(biāo)是一個純文本,就需要額外創(chuàng)建一個
textarea標(biāo)簽,為什么不是input標(biāo)簽?zāi)??是因為對于換行文本內(nèi)容使用textarea會更友好,如果使用input標(biāo)簽,那么需要拷貝的是textarea多行文本內(nèi)容則會發(fā)生格式異常的問題,因為最終是需要將選中的文本進(jìn)行input.value = value這樣賦值的,多行變成單行,而反之使用textarea支持拷貝input和它自身類型節(jié)點。 - 第二種是節(jié)點類型,不為
text', 'search', 'url', 'tel', 'password'這幾種類型之一的,均通過創(chuàng)建textarea的方式進(jìn)行拷貝。 - 第三種是為
text', 'search', 'url', 'tel'類型其中之一的直接執(zhí)行document.execCommand('copy')指令。
上面fakeCopyAction方法內(nèi)有一個createFakeElement函數(shù),它是負(fù)責(zé)創(chuàng)建textarae的,我們來看看作者是如何寫的:
/**
* 創(chuàng)建一個帶有值的textarea元素。
* @param {String} value
* @return {HTMLElement}
*/
export default function createFakeElement(value) {
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const fakeElement = document.createElement('textarea');
// 防止在IOS上縮放
fakeElement.style.fontSize = '12pt';
// 重寫盒子模型
fakeElement.style.border = '0';
fakeElement.style.padding = '0';
fakeElement.style.margin = '0';
// 設(shè)置絕對定位,將元素移動出屏幕外面
fakeElement.style.position = 'absolute';
fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px';
// 垂直移動到屏幕相同位置
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElement.style.top = `${yPosition}px`;
fakeElement.setAttribute('readonly', '');
fakeElement.value = value;
return fakeElement;
}
源碼很簡單,就是創(chuàng)建textarea標(biāo)簽,為了防止在IOS中縮放,設(shè)置了字體大小為12pt,隨手重寫了盒子模型border、padding、 margin三個的樣式,主要目的為了限制用戶惡意設(shè)置全局盒子模型樣式。然后將元素移出到屏幕以外,這里用到了isRTL來判斷是移動至屏幕左邊還是屏幕右邊,我們看到上面判斷了頁面元素中是否存在dir屬性,該屬性是否為rtl,它有什么意義呢?
說到
rtl就需要說到另外一個ltr,它們是完全相反的關(guān)系,過去會在頁面中寫dir標(biāo)簽,但是現(xiàn)在已經(jīng)不贊成使用了,感興趣的可以自行查閱對應(yīng)資料,這里簡單的說說rtl和ltr的區(qū)別:
| 元素 | LTR | RTL |
|---|---|---|
| 文本 | 句子從左向右閱讀 | 句子從有向左閱讀 |
| 時間線 | 事件序列從左向右進(jìn)行 | 事件序列從右向左進(jìn)行 |
| 圖像 | 從左向右的運動 | 從右向左運動 |
回到上面代碼,設(shè)置了標(biāo)簽擺放的位置后,需要將元素設(shè)置為可編輯狀態(tài)才行,所以取消了readonly,隨后給textarea標(biāo)簽賦值,最終把處理的元素返回。
以上剪切板功能就實現(xiàn)了,那么我們需要訂閱剪切板是成功還是失敗改怎么辦,tiny-emitter的發(fā)布訂閱功能就派上用場啦!
我們來看onClick函數(shù)下的this.emit就是做的這個工作。把actions、text、trigger、clearSelection四個屬性暴露出去,我們就可以實現(xiàn)訂閱。前面三個前面有介紹過了,這里介紹一下clearSelection屬性,它的職責(zé)是負(fù)責(zé)清理內(nèi)容選中光標(biāo)的,還記得前面講fakeCopyAction函數(shù)中用到了select(fakeElement),這里的select也是作者自己寫的插件,插件很簡單才43行代碼,實現(xiàn)原理是就是對選中的內(nèi)容的元素設(shè)置focus聚集光標(biāo),感興趣可以看源碼。
至此,源碼就解析完了。
簡化版hook
了解了原理之后,我們能不能自己實現(xiàn)一個簡單的剪切板功能呢?能在vue3、react中直接使用的hook
要實現(xiàn)剪切板最核心就只有兩點:
- 創(chuàng)建
textarea標(biāo)簽,并把需要添加剪切板的內(nèi)容賦值給該標(biāo)簽,隨后將其聚焦選中,最后添加到剪貼板后,移除該節(jié)點。 - 執(zhí)行
document.execCommand拷貝到剪切板。
了解了核心之后,我們就來寫一個簡單的版本。剪切板執(zhí)行完畢之后,我們給一個回調(diào)讓用戶做其他業(yè)務(wù)邏輯。
代碼如下:
function useClipboard(value,cb) {
const fakeElement = document.createElement('textarea');
// 防止在IOS上縮放
fakeElement.style.fontSize = '12pt';
// 重寫盒子模型
fakeElement.style.border = '0';
fakeElement.style.padding = '0';
fakeElement.style.margin = '0';
// 設(shè)置絕對定位,將標(biāo)簽移動出屏幕外面
fakeElement.style.position = 'absolute';
fakeElement.style.right = '-9999px';
// 垂直移動到屏幕相同位置
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElement.style.top = `${yPosition}px`;
// 設(shè)置為可讀
fakeElement.setAttribute('readonly', '');
// 賦值
fakeElement.value = value;
// 添加到根節(jié)點
document.body.appendChild(fakeElement);
// 選中內(nèi)容
fakeElement.select();
// 檢查命令是否支持
if(document.execCommand('copy')){
document.execCommand('copy')
// 執(zhí)行回調(diào)
typeof cb === 'function' && cb()
} else {
alert("系統(tǒng)不支持,請手動復(fù)制!")
}
// 拷貝到剪切板之后,移除自身標(biāo)簽
fakeElement.remove()
}
使用方法
<script lang="ts" setup>
import { useClipboard } from "composable/index"
const handleCopy = (text: string): void => {
useClipboard(text, () => {
console.log("復(fù)制成功啦")
})
}
</script>
LeetCode、CSDN網(wǎng)站復(fù)制內(nèi)容長度比較長的時候,都會添加版權(quán)信息在底部是,這個是如何實現(xiàn)的?
其實也很簡單,檢查復(fù)制的內(nèi)容長度是否超出閾值,超出則添加版權(quán)信息。
<script lang="ts" setup>
import { useClipboard } from "composable/index"
const handleCopy = (text: string): void => {
let url = window.location.href;
let newText = text
// 如果超出規(guī)定長度,則添加版權(quán)信息
if (text.length >= 50) {
newText =`${text}
原文出自 smallzip - url 轉(zhuǎn)載請保留原文鏈接
`
}
useClipboard(newText, () => {
console.log("復(fù)制成功啦")
})
}
</script>
兼容與降級
由于document.execCommand存在兼容問題,那么我該如何檢查是否兼容呢?
- 通過執(zhí)行命令查看返回值是為
true還是falseif (document.execCommand("copy")) { document.execCommand("copy") } else { window.alert("當(dāng)前系統(tǒng)不支持復(fù)制操作~") }
-
和
document.execCommand相關(guān)的一個API可以檢查當(dāng)前運行的瀏覽器是否兼容 ——document.queryCommandSupported,它會檢查執(zhí)行的命令是否支持。if(document.queryCommandSupported && document.queryCommandSupported('copy')){ document.execCommand("copy") } else { window.alert("當(dāng)前系統(tǒng)不支持復(fù)制操作~") }當(dāng)然我可以封裝成一個函數(shù),來專門判斷是否支持當(dāng)前瀏覽器,更方便業(yè)務(wù)調(diào)用:
export function isSupported(action = ['copy', 'cut']) { const actions = typeof action === 'string' ? [action] : action; let support = !!document.queryCommandSupported; actions.forEach((action) => { support = support && !!document.queryCommandSupported(action); }); return support; } // 使用方式 isSupported("copy") isSupported(["copy", "cut", "selectAll"])- 使用
Navigator.cliporad,這個是新的剪切板指令,兼容全部瀏覽器。推薦使用該方法,支持異步讀寫。下面貼一段來自MDN的介紹,更信息請自行查閱。
剪貼板 Clipboard API 為
Navigator接口添加了只讀屬性clipboard,該屬性返回一個可以讀寫剪切板內(nèi)容的Clipboard對象。在 Web 應(yīng)用中,剪切板 API 可用于實現(xiàn)剪切、復(fù)制、粘貼的功能。
只有在用戶事先授予網(wǎng)站或應(yīng)用對剪切板的訪問許可之后,才能使用異步剪切板讀寫方法。許可操作必須通過取得權(quán)限 Permissions API 的"clipboard-read"或"clipboard-write"獲得。 - 使用