我從Typora中學到的Clipboard妙用.md

一顆小行星發表於2021-11-18

Typora是我經常使用的一款軟體,用來寫MarkDown很舒適,有著非常優秀的使用體驗:

  • 實時預覽
  • 自定義圖片上傳服務
  • 文件轉換
  • 主題自定義

起因

不過我遇到一個非常好玩的事情,當我複製Typora內容貼上到文字編輯器時,會得到MarkDown格式的內容;複製到富文字編輯器時,可以渲染出富文字效果:

複製到VS Code:

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)

image-20211117193144843

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: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, &quot;Segoe UI Emoji&quot;, 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幫我們實現的嗎?

我們先來看一下複製富文字的預設行為,開啟一個網頁,複製網頁文字,然後使用剛才的程式碼嘗試一下,看看讀取到的剪下板內容。

image-20211118103737687

我們可以看到,在複製富文字的時候,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,不建議使用

混沌前端

參考文件:

ClipboardItem

Clipboard-write

element.oncopy

Selection

Range

相關文章