代码块点击复制
发布: (2025年12月10日 GMT+8 04:51)
4 min read
原文: Dev.to
Source: Dev.to
我是一名偏后端的全栈开发者,经过一天的 JavaScript 调试后,我决定在站点的代码块上添加“复制到剪贴板”按钮。最初的实现使用了 ClipboardJS 库,但我很快意识到,只用原生 Clipboard API 就能实现相同的效果,而且开销更小。
使用 ClipboardJS 的原始实现
为每个代码块添加按钮
const containers = document.querySelectorAll(`.${containerClass}`);
containers.forEach(container => {
const button = document.createElement('button');
button.className = buttonClass;
button.innerHTML = icons.faCopyRegular;
container.prepend(button);
});
创建 ClipboardJS 实例
const clipboard = new ClipboardJS(`.${buttonClass}`, {
target: function (trigger) {
return trigger.nextElementSibling;
}
});
处理复制成功
clipboard.on('success', (e) => {
if (e.action === 'copy') {
const originalIcon = e.trigger.innerHTML;
e.trigger.innerHTML = icons.faCheck;
setTimeout(() => {
e.trigger.innerHTML = originalIcon;
e.clearSelection();
}, iconChangeTimeout);
}
});
处理错误
clipboard.on('error', (e) => {
console.error('ClipboardJS Error:', e.action, e.trigger);
const originalIcon = e.trigger.innerHTML;
e.trigger.innerHTML = icons.faBomb; // e.g., a cross or “times” icon
setTimeout(() => {
e.trigger.innerHTML = originalIcon;
}, iconChangeTimeout);
});
虽然功能完整,但这种做法需要加载整个 ClipboardJS 库(以及它的依赖),仅仅是为了复制几行文字。
重构为原生 Clipboard API
原生 API 使用 Promise,使实现更加简洁。
添加按钮(保持不变)
const containers = document.querySelectorAll(`.${containerClass}`);
containers.forEach(container => {
const button = document.createElement('button');
button.className = buttonClass;
button.innerHTML = icons.faCopyRegular;
container.prepend(button);
});
使用 navigator.clipboard.writeText
containers.forEach(container => {
const button = container.querySelector(`.${buttonClass}`);
button.addEventListener('click', function () {
const text = this.nextElementSibling.textContent;
window.navigator.clipboard.writeText(text)
.then(() => {
const originalIcon = this.innerHTML;
this.innerHTML = icons.faCheck;
setTimeout(() => {
this.innerHTML = originalIcon;
}, iconChangeTimeout);
})
.catch((e) => {
console.error('Error copying to clipboard:', e);
const originalIcon = this.innerHTML;
this.innerHTML = icons.faBomb;
setTimeout(() => {
this.innerHTML = originalIcon;
}, iconChangeTimeout);
});
});
});
这个版本去除了外部依赖,并保持了原来的行为:成功时显示对号,失败时显示炸弹图标,随后在短暂的超时后恢复原始的复制图标。
最终实现
// icons.js should export the required SVG strings, e.g.:
// export const faCopyRegular = '';
// export const faCheck = '';
// export const faBomb = '';
import * as icons from './icons.js';
function addCopyToClipboardButtons() {
const iconChangeTimeout = 1300;
const containers = document.querySelectorAll('.highlight');
containers.forEach(container => {
const button = document.createElement('button');
button.className = 'copy-button';
button.innerHTML = icons.faCopyRegular;
container.prepend(button);
button.addEventListener('click', function () {
const originalIcon = this.innerHTML;
const text = this.nextElementSibling.textContent;
window.navigator.clipboard.writeText(text)
.then(() => {
this.innerHTML = icons.faCheck;
setTimeout(() => {
this.innerHTML = originalIcon;
}, iconChangeTimeout);
})
.catch(() => {
console.error('Error copying to clipboard');
this.innerHTML = icons.faBomb;
setTimeout(() => {
this.innerHTML = originalIcon;
}, iconChangeTimeout);
});
});
});
}
export { addCopyToClipboardButtons };
该函数:
- 查找所有带有
.highlight类的元素(代码块容器)。 - 在前面插入一个包含复制图标的按钮。
- 点击时读取相邻代码元素的文本,并通过
navigator.clipboard写入系统剪贴板。 - 成功时显示对号图标,失败时显示炸弹图标,随后在
iconChangeTimeout毫秒后恢复原始的复制图标。
结论
从 ClipboardJS 切换到原生 Clipboard API 可以减小打包体积,去除不必要的依赖,同时保持用户体验。最终脚本轻量、易于维护,并在所有支持 Clipboard API 的现代浏览器中工作。