日常業(yè)務(wù)中,會常常用到拷貝昨凡、剪切的需求爽醋,此外一些針對C端的平臺復(fù)制內(nèi)容下面會新增一段版權(quán)信息,那么這些都是如何實(shí)現(xiàn)的呢便脊?
其實(shí)是用的window.execCommand
方法蚂四,該方法允許運(yùn)行命令來操作可編輯區(qū)域的元素,執(zhí)行系統(tǒng)的copy
命令或者cut
命令哪痰,實(shí)現(xiàn)拷貝和剪切內(nèi)容到系統(tǒng)剪切板中遂赠。window.execCommand
可以執(zhí)行copy
和cut
外,還有其他的命令可參考如下表格:
命令 | 描述 |
---|---|
backColor | 修改文檔的背景顏色晌杰。在 styleWithCss 模式下跷睦,則只影響容器元素的背景顏色。這需要一個<color> 類型的字符串值作為參數(shù)傳入肋演。注意抑诸,IE 瀏覽器用這個設(shè)置文字的背景顏色。 |
bold | 開啟或關(guān)閉選中文字或插入點(diǎn)的粗體字效果爹殊。IE 瀏覽器使用 <strong>標(biāo)簽蜕乡,而不是<b>標(biāo)簽。 |
ClearAuthenticationCache | 清除緩存中的所有身份驗(yàn)證憑據(jù)梗夸。 |
contentReadOnly | 通過傳入一個布爾類型的參數(shù)來使能文檔內(nèi)容的可編輯性层玲。(IE 瀏覽器不支持) |
copy | 拷貝當(dāng)前選中內(nèi)容到剪貼板。啟用這個功能的條件因?yàn)g覽器不同而不同反症,而且不同時期辛块,其啟用條件也不盡相同。使用之前請檢查瀏覽器兼容表铅碍,以確定是否可用润绵。 |
createLink | 將選中內(nèi)容創(chuàng)建為一個錨鏈接。這個命令需要一個hrefURI 字符串作為參數(shù)值傳入该酗。URI 必須包含至少一個字符授药,例如一個空格士嚎。(瀏覽器會創(chuàng)建一個空鏈接) |
cut | 剪貼當(dāng)前選中的文字并復(fù)制到剪貼板呜魄。啟用這個功能的條件因?yàn)g覽器不同而不同,而且不同時期莱衩,其啟用條件也不盡相同爵嗅。使用之前請檢查瀏覽器兼容表,以確定是否可用笨蚁。 |
decreaseFontSize | 給選中文字加上 <small> 標(biāo)簽睹晒,或在選中點(diǎn)插入該標(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 | 在插入點(diǎn)或者選中文字部分修改字體名稱拖云。需要提供一個字體名稱字符串 (例如:"Arial") 作為參數(shù)。 |
fontSize | 在插入點(diǎn)或者選中文字部分修改字體大小应又。需要提供一個 HTML 字體尺寸 (1-7) 作為參數(shù)宙项。 |
foreColor | 在插入點(diǎn)或者選中文字部分修改字體顏色。需要提供一個顏色值字符串作為參數(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 | 更改選擇或插入點(diǎn)的背景顏色躁染。需要一個顏色值字符串作為值參數(shù)傳遞鸣哀。UseCSS 必須開啟此功能。(IE 瀏覽器不支持) |
increaseFontSize | 在選擇或插入點(diǎn)周圍添加一個 BIG 標(biāo)簽吞彤。(IE 瀏覽器不支持) |
indent | 縮進(jìn)選擇或插入點(diǎn)所在的行我衬,在 Firefox 中,如果選擇多行饰恕,但是這些行存在不同級別的縮進(jìn)挠羔,只有縮進(jìn)最少的行被縮進(jìn)。 |
insertBrOnReturn | 控制當(dāng)按下 Enter 鍵時埋嵌,是插入 br 標(biāo)簽還是把當(dāng)前塊元素變成兩個破加。(IE 瀏覽器不支持) |
insertHorizontalRule | 在插入點(diǎn)插入一個水平線(刪除選中的部分) |
insertHTML | 在插入點(diǎn)插入一個 HTML 字符串(刪除選中的部分)。需要一個個 HTML 字符串作為參數(shù)雹嗦。(IE 瀏覽器不支持) |
insertImage | 在插入點(diǎn)插入一張圖片(刪除選中的部分)范舀。需要一個 URL 字符串作為參數(shù)合是。這個 URL 圖片地址至少包含一個字符《Щ罚空白字符也可以(IE 會創(chuàng)建一個鏈接其值為 null) |
insertOrderedList | 在插入點(diǎn)或者選中文字上創(chuàng)建一個有序列表 |
insertUnorderedList | 在插入點(diǎn)或者選中文字上創(chuàng)建一個無序列表聪全。 |
insertParagraph | 在選擇或當(dāng)前行周圍插入一個段落。(IE 會在插入點(diǎn)插入一個段落并刪除選中的部分.) |
insertText | 在光標(biāo)插入位置插入文本內(nèi)容或者覆蓋所選的文本內(nèi)容辅辩。 |
italic | 在光標(biāo)插入點(diǎn)開啟或關(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)插入點(diǎn)開啟或關(guān)閉刪除線。 |
subscript | 在光標(biāo)插入點(diǎn)開啟或關(guān)閉下角標(biāo)获搏。 |
superscript | 在光標(biāo)插入點(diǎn)開啟或關(guān)閉上角標(biāo)赖条。 |
underline | 在光標(biāo)插入點(diǎn)開啟或關(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>
點(diǎn)擊button
按鈕之后徽缚,控制臺打印如下內(nèi)容:
-
Action
:執(zhí)行命令 -
Text
:放入到剪切板的內(nèi)容 -
Trigger
:觸發(fā)的節(jié)點(diǎn)
源碼解析
可以邊參考源碼邊看:點(diǎn)擊查看源碼
打開源碼進(jìn)入clipboardjs
頁面憨奸,可以看到定義了一個Clipboard
類并繼承于Emitter
,Emitter
是負(fù)責(zé)訂閱命令執(zhí)行之后剪切板操作成功和失敗的關(guān)鍵凿试,用到了Emitter
的on
和emit
排宰。
tiny-emitter
是一個簡單實(shí)現(xiàn)發(fā)布訂閱的包(查看tiny-emitter源碼),在函數(shù)的原型對象內(nèi)定義了on
那婉、once
板甘、emit
、off
四個原型方法详炬,具體的實(shí)現(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)聽點(diǎ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
都允許通過配置項(xiàng)的方式聲明一個函數(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)錨點(diǎn)冻押,如data-clipboard-target="div"
中的target
就是div
,此外也可以是className
類名或者#id
節(jié)點(diǎn)id盛嘿,最終是通過document.querySelector(selector);
來獲取節(jié)點(diǎn)洛巢。 -
text
是選中的文本內(nèi)容 -
container
是要將目標(biāo)節(jié)點(diǎn)插入的父級根節(jié)點(diǎn)位置,可以根據(jù)實(shí)際業(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
點(diǎn)擊事件。會遍歷所有的節(jié)點(diǎn)蚤认,給節(jié)點(diǎn)添加監(jiān)聽事件米苹,這個是通過good-listener
插件實(shí)現(xiàn),插件是由作者本人寫的砰琢,實(shí)現(xiàn)原理很簡單蘸嘶,通過檢查trigger
是什么類型
- 節(jié)點(diǎn)(
node
):直接給節(jié)點(diǎn)添加listen
監(jiān)聽事件function listenNode(node, type, callback) { node.addEventListener(type, callback); return { destroy: function() { node.removeEventListener(type, callback); } } }
- 節(jié)點(diǎn)列表(
nodeList
):會遍歷列表,然后給每個節(jié)點(diǎn)添加監(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)我們點(diǎn)擊之后會觸發(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();
},
});
}
實(shí)例化ClipboardAction
后澳骤,創(chuàng)建一個監(jiān)聽事件,會監(jiān)聽所有點(diǎn)擊行為澜薄,當(dāng)監(jiān)聽到某一個節(jié)點(diǎn)點(diǎn)擊为肮,利用節(jié)點(diǎn)的Event
的e.delegateTarget || e.currentTarget
取得目標(biāo)節(jié)點(diǎn)內(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é)點(diǎn)中聲明自定義屬性值,那么它將不會通過if(text)
的判斷若债,直接跳過符相,往下執(zhí)行。隨后是target
蠢琳,由于前面做了邊界異常處理啊终,所以這里作為兜底執(zhí)行,根據(jù)cut
和copy
分配給兩個不同的函數(shù)處理傲须,這里我們只介紹copy
蓝牲,因?yàn)?code>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)兀渴且驗(yàn)閷τ趽Q行文本內(nèi)容使用textarea
會更友好累澡,如果使用input
標(biāo)簽梦抢,那么需要拷貝的是textarea
多行文本內(nèi)容則會發(fā)生格式異常的問題,因?yàn)樽罱K是需要將選中的文本進(jìn)行input.value = value
這樣賦值的愧哟,多行變成單行奥吩,而反之使用textarea
支持拷貝input
和它自身類型節(jié)點(diǎn)。 - 第二種是節(jié)點(diǎn)類型翅雏,不為
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)行 |
圖像 | 從左向右的運(yùn)動 | 從右向左運(yùn)動 |
回到上面代碼陡舅,設(shè)置了標(biāo)簽擺放的位置后,需要將元素設(shè)置為可編輯狀態(tài)才行程储,所以取消了readonly
蹭沛,隨后給textarea
標(biāo)簽賦值,最終把處理的元素返回章鲤。
以上剪切板功能就實(shí)現(xiàn)了摊灭,那么我們需要訂閱剪切板是成功還是失敗改怎么辦,tiny-emitter
的發(fā)布訂閱功能就派上用場啦败徊!
我們來看onClick
函數(shù)下的this.emit
就是做的這個工作帚呼。把actions
、text
皱蹦、trigger
煤杀、clearSelection
四個屬性暴露出去,我們就可以實(shí)現(xiàn)訂閱沪哺。前面三個前面有介紹過了沈自,這里介紹一下clearSelection
屬性,它的職責(zé)是負(fù)責(zé)清理內(nèi)容選中光標(biāo)的辜妓,還記得前面講fakeCopyAction
函數(shù)中用到了select(fakeElement)
枯途,這里的select
也是作者自己寫的插件忌怎,插件很簡單才43行代碼,實(shí)現(xiàn)原理是就是對選中的內(nèi)容的元素設(shè)置focus
聚集光標(biāo)酪夷,感興趣可以看源碼榴啸。
至此,源碼就解析完了晚岭。
簡化版hook
了解了原理之后鸥印,我們能不能自己實(shí)現(xiàn)一個簡單的剪切板功能呢?能在vue3坦报、react中直接使用的hook
要實(shí)現(xiàn)剪切板最核心就只有兩點(diǎn):
- 創(chuàng)建
textarea
標(biāo)簽库说,并把需要添加剪切板的內(nèi)容賦值給該標(biāo)簽,隨后將其聚焦選中燎竖,最后添加到剪貼板后璃弄,移除該節(jié)點(diǎn)要销。 - 執(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é)點(diǎn)
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)信息在底部是卵牍,這個是如何實(shí)現(xiàn)的果港?
其實(shí)也很簡單,檢查復(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
還是false
if (document.execCommand("copy")) { document.execCommand("copy") } else { window.alert("當(dāng)前系統(tǒng)不支持復(fù)制操作~") }
-
和
document.execCommand
相關(guān)的一個API可以檢查當(dāng)前運(yùn)行的瀏覽器是否兼容 ——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 可用于實(shí)現(xiàn)剪切即碗、復(fù)制焰情、粘貼的功能。
只有在用戶事先授予網(wǎng)站或應(yīng)用對剪切板的訪問許可之后剥懒,才能使用異步剪切板讀寫方法内舟。許可操作必須通過取得權(quán)限 Permissions API 的"clipboard-read"
或"clipboard-write"
獲得。 - 使用