线上复制按钮失效?也许是这个原因

35 阅读2分钟

问题描述

开发聊天组件时,在本地测试复制功能完全正常,部署到服务器后点击复制按钮却毫无反应。没有报错,没有提示,就像什么都没发生一样。

问题定位

翻了半天代码,发现复制逻辑是这样的:

const handleCopy = useCallback(async () => {
  try {
    await navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  } catch {
    // 空的,什么都没做
  }
}, [text]);

逻辑看起来没问题,但问题出在 navigator.clipboard.writeText

navigator.clipboard API 需要在安全上下文(Secure Context)下才能工作。所谓安全上下文,就是页面通过 HTTPS 加载,或者在 localhost 下运行。

本地开发时,浏览器把 localhost 视为安全上下文,所以 Clipboard API 正常工作。部署到服务器后,如果你的域名是 HTTP 而非 HTTPS,navigator.clipboard 虽然存在,但调用 writeText 时会直接静默失败。由于 catch 块是空的,用户点击后什么反馈都没有,看起来就像按钮坏了。

解决方案

提供一个不依赖安全上下文的降级方案:

const handleCopy = useCallback(async () => {

  try {
    if (navigator?.clipboard?.writeText) {
      await navigator.clipboard.writeText(text);
    } else {
      throw new Error('Clipboard API not available');
    }
  } catch {
    // Fallback: textarea + execCommand,不需要安全上下文
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    document.body.appendChild(textarea);
    textarea.focus();
    textarea.select();
    try {
      document.execCommand('copy');
    } catch {
      // 所有复制方式均失败,静默处理
    } finally {
      document.body.removeChild(textarea);
    }
  }
}, [text, copied]);

这个方案有两个层次:

第一层,优先尝试 navigator.clipboard.writeText,这是现代标准 API,体验最好。

第二层,当 Clipboard API 不可用或失败时,降级到 textarea + execCommand。这个方式不需要 HTTPS,在任何环境下都能工作,是最后的兜底。

更好的做法

降级方案虽然能解决问题,但 execCommand 已经是被废弃的 API,长期来看不是最优解。根本的解决方式是给服务器配置 HTTPS。

上了 HTTPS 后,Clipboard API 在所有现代浏览器下都能正常工作,就不需要降级方案了。

总结

这个问题本质上是安全上下文的要求导致的:本地 localhost 默认是安全的,线上非 HTTPS 则不安全。解决方案是两层兜底:优先用标准 Clipboard API,失败后降级到 execCommand。但更推荐的做法是直接给站点加上 HTTPS,一劳永逸。


关键词:navigator.clipboard、安全上下文、HTTPS、execCommand、复制功能失效