#起因 从知乎上面找了个别人的文章复制了一些内容,当复制的内容超过了一定字数的时候,就发现粘贴出来的东西多了一些内容,如下图所示:
挺有意思的功能,于是想到把这个功能在自己博文里也实现一下。 前段时间用clipboard.js写过一键复制代码功能,大概知道document.execCommand这个强大API的作用与用法了。不过在此处,根据多年技术的直觉,我想应该不会用到。
#思路 先猜测下知乎实现的思路:
- 监听用户复制操作
- 用户复制时,获取所选取的内容
- 对选取的内容二次加工
- 加工后的内容更新到剪贴板
第一步简单,有现成的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
,所以基本没有兼容问题。效果如何呢:
大功告成!