美文网首页
我从Typora中学到的Clipboard妙用.md

我从Typora中学到的Clipboard妙用.md

作者: 一颗小行星_混沌前端 | 来源:发表于2021-11-18 15:51 被阅读0次

    Typora是我经常使用的一款软件,用来写MarkDown很舒适,有着非常优秀的使用体验:

    • 实时预览

    • 自定义图片上传服务

    • 文档转换

    • 主题自定义

    起因

    不过我遇到一个非常好玩的事情,当我复制Typora内容粘贴到文本编辑器时,会得到MarkDown格式的内容;复制到富文本编辑器时,可以渲染出富文本效果:

    复制到VS Code:

    VS Code

    复制到其他富文本编辑器:

    富文本编辑器

    我很好奇为什么会出现两种不同的结果,Typora应该是使用Electron(或类似技术)开发的,我尝试用Clipboard API来进行测试:

    <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控制台进行的测试,clipboard依托于页面,所以我需要设置1s延时,以便可以点击页面聚焦
    setTimeout(async()=>{
    const clipboardItems = await navigator.clipboard.read();
    console.log(clipboardItems)
    },1000)</pre>

    然后看到了剪切板中有两种不同类型的内容:纯文本text/plain和富文本text/html。所以不同的内容接收者选择了不同的内容作为数据,文本编辑器拿到的是纯文本,富文本编辑器获取的是富文本格式数据。

    结果

    再来看看获取到的具体内容吧:

    <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塞入数据试一下:

    <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>

    尝试了几个富文本编辑器得到的结果(不同富文本编辑器的具体实现可能存在差异):

    • 如果只存在纯文本(仅保留上段代码中的纯文本部分), 会读取剪切板中纯文本内容

    • 如果存在纯文本和富文本,会读取剪切板中富文本内容

    那这个效果是Typora帮我们实现的吗?

    我们先来看一下复制富文本的默认行为,打开一个网页,复制网页文本,然后使用刚才的代码尝试一下,看看读取到的剪切板内容。

    image-20211118103737687

    我们可以看到,在复制富文本的时候,Chrome实现的clipboard API都会生成两份结果,一份是纯文本格式text/plain,一份是富文本格式text/html

    不同的是:当我们在Typora复制时,得到的是Markdown格式的纯文本和富文本,是Typora帮我们进行了处理。

    监听复制,写入剪切板

    监听复制我们可以使用HTMLElement.oncopy实现:

    打开任意一个网页,切换到控制台:

    <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>

    复制页面中内容,我们就可以的看到打印的结果了:

    监听复制

    本来为数据会在clipboardData中,但是尝试了一下并没有获取到内容,看了一下API, 需要在copy事件中通过setData设置数据,在paste时间中getData获取数据。我们可以通过Selection API来获取选中的内容。

    <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(); // 防止我们筛入的数据被覆盖
    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实现写入:

    <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>

    监听复制还可以用来添加版权信息,比如上面代码中的额外信息就会出现在复制的文本中。

    对于复制和粘贴内容也可以通过document.execCommand,不过目前属于已经被弃用的API,不建议使用

    欢迎关注微信“混沌前端”

    参考文档:

    ClipboardItem

    Clipboard-write

    element.oncopy

    Selection

    Range

    相关文章

      网友评论

          本文标题:我从Typora中学到的Clipboard妙用.md

          本文链接:https://www.haomeiwen.com/subject/ccadtrtx.html