我從Typora中學(xué)到的Clipboard妙用.md

Typora是我經(jīng)常使用的一款軟件,用來(lái)寫(xiě)MarkDown很舒適送膳,有著非常優(yōu)秀的使用體驗(yàn):

  • 實(shí)時(shí)預(yù)覽

  • 自定義圖片上傳服務(wù)

  • 文檔轉(zhuǎn)換

  • 主題自定義

起因

不過(guò)我遇到一個(gè)非常好玩的事情普碎,當(dāng)我復(fù)制Typora內(nèi)容粘貼到文本編輯器時(shí)吼肥,會(huì)得到MarkDown格式的內(nèi)容;復(fù)制到富文本編輯器時(shí)麻车,可以渲染出富文本效果:

復(fù)制到VS Code:

VS Code

復(fù)制到其他富文本編輯器:

富文本編輯器

我很好奇為什么會(huì)出現(xiàn)兩種不同的結(jié)果缀皱,Typora應(yīng)該是使用Electron(或類似技術(shù))開(kāi)發(fā)的,我嘗試用Clipboard API來(lái)進(jìn)行測(cè)試:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="js" cid="n72" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">// 為什么使用setTimeout:我是在Chrome控制臺(tái)進(jìn)行的測(cè)試动猬,clipboard依托于頁(yè)面啤斗,所以我需要設(shè)置1s延時(shí),以便可以點(diǎn)擊頁(yè)面聚焦
setTimeout(async()=>{
const clipboardItems = await navigator.clipboard.read();
console.log(clipboardItems)
},1000)</pre>

然后看到了剪切板中有兩種不同類型的內(nèi)容:純文本text/plain和富文本text/html赁咙。所以不同的內(nèi)容接收者選擇了不同的內(nèi)容作為數(shù)據(jù)钮莲,文本編輯器拿到的是純文本,富文本編輯器獲取的是富文本格式數(shù)據(jù)彼水。

結(jié)果

再來(lái)看看獲取到的具體內(nèi)容吧:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="js" cid="n100" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">setTimeout(async()=>{
const clipboardItems = await navigator.clipboard.read();
console.log(clipboardItems)
for (const clipboardItem of clipboardItems) {
for (const type of clipboardItem.types) {
const contentBlob = await clipboardItem.getType(type)
const text = await contentBlob.text()
console.log(text)
}
}
},1000)</pre>

image-20211117193144843

Clipboard塞入數(shù)據(jù)試一下:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="js" cid="n127" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">setTimeout(async ()=>{
await navigator.clipboard.write([
new ClipboardItem({
["text/plain"]: new Blob(['# 純文本和富文本'],{type:'text/plain'}),
["text/html"]: new Blob(['<h1 cid="n21" mdtype="heading" class="md-end-block md-heading md-focus" style="box-sizing: border-box; break-after: avoid-page; break-inside: avoid; orphans: 4; font-size: 2.25em; margin-top: 1rem; margin-bottom: 1rem; position: relative; font-weight: bold; line-height: 1.2; cursor: text; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(238, 238, 238); white-space: pre-wrap; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, "Segoe UI Emoji", sans-serif; font-style: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration: none;"><span md-inline="plain" class="md-plain md-expand" style="box-sizing: border-box;">純文本和富文本</span></h1>'],{type:'text/html'}),
})
]);
},[1000])</pre>

嘗試了幾個(gè)富文本編輯器得到的結(jié)果(不同富文本編輯器的具體實(shí)現(xiàn)可能存在差異):

  • 如果只存在純文本(僅保留上段代碼中的純文本部分), 會(huì)讀取剪切板中純文本內(nèi)容

  • 如果存在純文本和富文本崔拥,會(huì)讀取剪切板中富文本內(nèi)容

那這個(gè)效果是Typora幫我們實(shí)現(xiàn)的嗎?

我們先來(lái)看一下復(fù)制富文本的默認(rèn)行為凤覆,打開(kāi)一個(gè)網(wǎng)頁(yè)链瓦,復(fù)制網(wǎng)頁(yè)文本,然后使用剛才的代碼嘗試一下,看看讀取到的剪切板內(nèi)容慈俯。

image-20211118103737687

我們可以看到渤刃,在復(fù)制富文本的時(shí)候,Chrome實(shí)現(xiàn)的clipboard API都會(huì)生成兩份結(jié)果贴膘,一份是純文本格式text/plain卖子,一份是富文本格式text/html

不同的是:當(dāng)我們?cè)赥ypora復(fù)制時(shí)刑峡,得到的是Markdown格式的純文本和富文本揪胃,是Typora幫我們進(jìn)行了處理。

監(jiān)聽(tīng)復(fù)制氛琢,寫(xiě)入剪切板

監(jiān)聽(tīng)復(fù)制我們可以使用HTMLElement.oncopy實(shí)現(xiàn):

打開(kāi)任意一個(gè)網(wǎng)頁(yè)喊递,切換到控制臺(tái):

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="js" cid="n189" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">document.body.oncopy = function(e){
console.log(e)
var text = e.clipboardData.getData("text");
console.log(text)
}</pre>

復(fù)制頁(yè)面中內(nèi)容,我們就可以的看到打印的結(jié)果了:

監(jiān)聽(tīng)復(fù)制

本來(lái)為數(shù)據(jù)會(huì)在clipboardData中阳似,但是嘗試了一下并沒(méi)有獲取到內(nèi)容骚勘,看了一下API, 需要在copy事件中通過(guò)setData設(shè)置數(shù)據(jù),在paste時(shí)間中g(shù)etData獲取數(shù)據(jù)撮奏。我們可以通過(guò)Selection API來(lái)獲取選中的內(nèi)容俏讹。

<pre mdtype="fences" cid="n502" lang="js" spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">document.addEventListener('copy', function(e){
e.preventDefault(); // 防止我們篩入的數(shù)據(jù)被覆蓋
const selectionObj = window.getSelection()
const rangeObj = selectionObj.getRangeAt(0)
const fragment = rangeObj.cloneContents() // 獲取Range包含的文檔片段
const wrapper = document.createElement('div')
wrapper.append(fragment)
e.clipboardData.setData('text/plain', wrapper.innerText + '額外的文本');
e.clipboardData.setData('text/html', wrapper.innerHTML+ '<h1>額外的文本</h1>');
});</pre>

或者使用clipboard.write實(shí)現(xiàn)寫(xiě)入:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="js" cid="n218" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit inherit; background-repeat: inherit inherit;">document.body.oncopy = function(e){
e.preventDefault();
const selectionObj = window.getSelection()
const rangeObj = selectionObj.getRangeAt(0)
const fragment = rangeObj.cloneContents() // 獲取Range包含的文檔片段
const wrapper = document.createElement('div')
wrapper.append(fragment)
navigator.clipboard.write([
new ClipboardItem({
["text/plain"]: new Blob([wrapper.innerText,'額外的文本'],{type:'text/plain'}),
["text/html"]: new Blob([wrapper.innerHTML,'<h1>額外的富文本</h1>'],{type:'text/html'}),
})
])
}</pre>

監(jiān)聽(tīng)復(fù)制還可以用來(lái)添加版權(quán)信息,比如上面代碼中的額外信息就會(huì)出現(xiàn)在復(fù)制的文本中畜吊。

對(duì)于復(fù)制和粘貼內(nèi)容也可以通過(guò)document.execCommand泽疆,不過(guò)目前屬于已經(jīng)被棄用的API,不建議使用

歡迎關(guān)注微信“混沌前端”

參考文檔:

ClipboardItem

Clipboard-write

element.oncopy

Selection

Range

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玲献,一起剝皮案震驚了整個(gè)濱河市殉疼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捌年,老刑警劉巖瓢娜,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異礼预,居然都是意外死亡眠砾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)托酸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)褒颈,“玉大人,你說(shuō)我怎么就攤上這事励堡」韧瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵念秧,是天一觀的道長(zhǎng)淤井。 經(jīng)常有香客問(wèn)我布疼,道長(zhǎng)摊趾,這世上最難降的妖魔是什么币狠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮砾层,結(jié)果婚禮上漩绵,老公的妹妹穿的比我還像新娘。我一直安慰自己肛炮,他們只是感情好止吐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著侨糟,像睡著了一般碍扔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秕重,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天不同,我揣著相機(jī)與錄音,去河邊找鬼溶耘。 笑死二拐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凳兵。 我是一名探鬼主播百新,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼庐扫!你這毒婦竟也來(lái)了饭望?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤形庭,失蹤者是張志新(化名)和其女友劉穎杰妓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體碘勉,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巷挥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了验靡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倍宾。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胜嗓,靈堂內(nèi)的尸體忽然破棺而出高职,到底是詐尸還是另有隱情,我是刑警寧澤辞州,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布怔锌,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏埃元。R本人自食惡果不足惜涝涤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岛杀。 院中可真熱鬧阔拳,春花似錦、人聲如沸类嗤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遗锣。三九已至货裹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間精偿,已是汗流浹背泪酱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留还最,地道東北人墓阀。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拓轻,于是被迫代替她去往敵國(guó)和親斯撮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容