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中,但是嘗試了一下並沒有獲取到內容,看來只能另闢蹊徑了,我們可以通過Range API來獲取選中的內容。
document.body.oncopy = function(e){
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,不建議使用