代码块点击复制

发布: (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 };

该函数:

  1. 查找所有带有 .highlight 类的元素(代码块容器)。
  2. 在前面插入一个包含复制图标的按钮。
  3. 点击时读取相邻代码元素的文本,并通过 navigator.clipboard 写入系统剪贴板。
  4. 成功时显示对号图标,失败时显示炸弹图标,随后在 iconChangeTimeout 毫秒后恢复原始的复制图标。

结论

从 ClipboardJS 切换到原生 Clipboard API 可以减小打包体积,去除不必要的依赖,同时保持用户体验。最终脚本轻量、易于维护,并在所有支持 Clipboard API 的现代浏览器中工作。

Back to Blog

相关文章

阅读更多 »