杨二

Machine repeats, Human creates

自定义复制功能

起因

从知乎上面找了个别人的文章复制了一些内容,当复制的内容超过了一定字数的时候,就发现粘贴出来的东西多了一些内容,如下图所示:

知乎复制内容

挺有意思的功能,于是想到把这个功能在自己博文里也实现一下。 前段时间用clipboard.js写过一键复制代码功能,大概知道document.execCommand这个强大API的作用与用法了。不过在此处,根据多年技术的直觉,我想应该不会用到。

思路

先猜测下知乎实现的思路:

  1. 监听用户复制操作
  2. 用户复制时,获取所选取的内容
  3. 对选取的内容二次加工
  4. 加工后的内容更新到剪贴板

第一步简单,有现成的copy事件。

第二步,如何获取用户选择的内容,之前模糊记得有相关的API,一查MDN文档,原来是selection这个神奇的对象。不过,通过实践浮现的一个问题:获取用户选取内容只是第一步,还需要在某个地方暂存这些内容,以备后续加工。在这里,我们通过动态创建一个不可见的元素tempDiv,并将内容innerHTML到该元素,最后appendChild到body,实现暂存。

第三步,第四步可以一气呵成,把加工后的内容innerHTML更新到第二步创建的元素里,然后调用selection.selectAllChildren(tempDiv)就可以更新到剪贴板啦。

实现

思路有了,代码写起来就快许多了。下面是最终的实现代码:

//需要监听copy事件的元素
const listenBlock = document.querySelector('#postContent');
// 给该元素绑定copy事件
listenBlock.addEventListener(
    'copy',
    evt => {
    // IE8以及更早版本,不支持 getSelection,做一下兼容回退处理。
    if (typeof window.getSelection === 'undefined') return; // IE8 or earlier...
    // 获取selection对象
    const selection = window.getSelection();

    // 复制内容过短的话,就不追加内容了,增强下用户体验。
    if (selection.toString().length < 30) return;

    // 动态创建不可见元素,并将复制选取的内容填入
    const tmpDiv = document.createElement('div');
    tmpDiv.style.position = 'absolute';
    tmpDiv.style.left = '-99999px';
    document.body.appendChild(tmpDiv);
    tmpDiv.appendChild(selection.getRangeAt(0).cloneContents());

    // 啊,遇到的坑:pre 标签需要特殊处理一下,否则换行将被清除。
    // 除了 selection 对象,还要了解下 Range: https://developer.mozilla.org/en-US/docs/Web/API/Range
    if (selection.getRangeAt(0).commonAncestorContainer.nodeName == 'PRE') {
        tmpDiv.innerHTML = `<pre>${tmpDiv.innerHTML}</pre>`;
    }
    // 注意换行使用br标签
    tmpDiv.innerHTML += `<br /><br /><br />作者:杨二 <br />微信:yanggc_2013 <br />链接:<a href='${
        document.location.href
    }'>${document.location.href}</a> <br />著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。`;
    // 更新复制内容
    selection.selectAllChildren(tmpDiv);
    // 不要忘了移除动态元素
    window.setTimeout(() => {
        document.body.removeChild(tmpDiv);
    }, 200);
    }
);

从上面代码可以看出,除了中间填了一个pre标签的坑,基本按照预想的思路写的。所用现代浏览器均支持selection,所以基本没有兼容问题。效果如何呢:

实现效果

大功告成!