Typora是我經常使用的一款軟體,用來寫MarkDown很舒适,有着非常優秀的使用體驗:
- 實時預覽
- 自定義圖檔上傳服務
- 文檔轉換
- 主題自定義
起因
不過我遇到一個非常好玩的事情,當我複制Typora内容粘貼到文本編輯器時,會得到MarkDown格式的内容;複制到富文本編輯器時,可以渲染出富文本效果:
複制到VS Code:

複制到其他富文本編輯器:
我很好奇為什麼會出現兩種不同的結果,Typora應該是使用Electron(或類似技術)開發的,我嘗試用Clipboard API來進行測試:
// 為什麼使用setTimeout:我是在Chrome控制台進行的測試,clipboard依托于頁面,是以我需要設定1s延時,以便可以點選頁面聚焦
setTimeout(async()=>{
const clipboardItems = await navigator.clipboard.read();
console.log(clipboardItems)
},1000)
然後看到了剪切闆中有兩種不同類型的内容:純文字
text/plain
和富文本
text/html
。是以不同的内容接收者選擇了不同的内容作為資料,文本編輯器拿到的是純文字,富文本編輯器擷取的是富文本格式資料。
再來看看擷取到的具體内容吧:
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)
Clipboard塞入資料試一下:
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])
嘗試了幾個富文本編輯器得到的結果(不同富文本編輯器的具體實作可能存在差異):
- 如果隻存在純文字(僅保留上段代碼中的純文字部分), 會讀取剪切闆中純文字内容
- 如果存在純文字和富文本,會讀取剪切闆中富文本内容
那這個效果是Typora幫我們實作的嗎?
我們先來看一下複制富文本的預設行為,打開一個網頁,複制網頁文本,然後使用剛才的代碼嘗試一下,看看讀取到的剪切闆内容。
我們可以看到,在複制富文本的時候,Chrome實作的clipboard API都會生成兩份結果,一份是純文字格式
text/plain
,一份是富文本格式
text/html
。
不同的是:當我們在Typora複制時,得到的是Markdown格式的純文字和富文本,是Typora幫我們進行了處理。
監聽複制,寫入剪切闆
監聽複制我們可以使用HTMLElement.oncopy實作:
打開任意一個網頁,切換到控制台:
document.body.oncopy = function(e){
console.log(e)
var text = e.clipboardData.getData("text");
console.log(text)
}
複制頁面中内容,我們就可以的看到列印的結果了:
本來為資料會在clipboardData中,但是嘗試了一下并沒有擷取到内容,看了一下API, 需要在copy事件中通過setData設定資料,在paste時間中getData擷取資料。我們可以通過Selection API來擷取選中的内容。
document.addEventListener('copy', 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)
e.clipboardData.setData('text/plain', wrapper.innerText + '額外的文本');
e.clipboardData.setData('text/html', wrapper.innerHTML+ '<h1>額外的文本</h1>');
});
或者使用clipboard.write實作寫入:
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'}),
})
])
}
監聽複制還可以用來添加版權資訊,比如上面代碼中的額外資訊就會出現在複制的文本中。
對于複制和粘貼内容也可以通過document.execCommand,不過目前屬于已經被棄用的API,不建議使用
更多文章歡迎關注“混沌前端”