编辑器、代码块、大模型AI对话中代码复制功能实现

一、效果图

二、navigator.clipboard API

直奔主题。代码块复制使用现代浏览器原生 Clipboard API

typescript 复制代码
	  const [copied, setCopied] = useState(false);

      // 处理复制逻辑
      const handleCopy = async (code: string) => {
        try {
          await navigator.clipboard.writeText(code);
          setCopied(true);
          setTimeout(() => setCopied(false), 2000);
        } catch (err) {
          console.error('复制失败:', err);
        }
      };
     const codeContent = String(children).replace(/\n$/, '')
     return(
      		<button
              onClick={() => handleCopy(codeContent)}
              style={{
                position: 'absolute',
                right: 8,
                top: 8,
                padding: '4px 8px',
                background: '#f5f5f5',
                border: '1px solid #ddd',
                borderRadius: 4,
                cursor: 'pointer',
                zIndex: 1,
                opacity: copied ? 1 : 0.6,
                 transition: "opacity 0.2s",
              }}
            >
              {copied ? '复制成功' : '⎘ 复制'}
           </button>
     )

关键点说明

  1. 状态管理:

使用 useState 跟踪复制状态,提供视觉反馈

2秒后自动重置复制状态提示

  1. 样式定位

绝对定位到代码块右上角

使用:

typescript 复制代码
 <div style={{position: 'relative'}}>//外层相对定位
 //复制
  {children && handCopyRender(children)}//复制块绝对定位
 //代码块dom
 //例:<SyntaxHighlighter/>
 </div>
  1. 内容处理:
typescript 复制代码
const codeContent = String(children).replace(/\n$/, "");

移除代码结尾的多余换行符 确保复制内容格式正确

三、浏览器兼容处理

  1. 旧版浏览器不支持 Clipboard API
  2. HTTP环境下无法生效
  3. 浏览器安全限制:Clipboard API 要确保在 HTTPS 环境或 localhost 下使用

兼容性处理后:

typescript 复制代码
const handCopyRender = (value: React.ReactNode) => {
  /** 复制功能组件 */
  const [copied, setCopied] = useState(false);
  
  // 兼容性复制方法
  const copyToClipboard = (text: string) => {
    return new Promise<boolean>((resolve) => {
      try {
        // 现代浏览器方案
        if (navigator.clipboard && window.isSecureContext) {
          navigator.clipboard.writeText(text)
            .then(() => resolve(true))
            .catch(() => resolve(false));
        } 
        // 旧浏览器兼容方案
        else {
          // 创建临时文本域
          const textArea = document.createElement('textarea');
          textArea.value = text;
          textArea.style.position = 'fixed';  // 避免滚动跳转
          textArea.style.opacity = '0';        // 透明不可见
          
          document.body.appendChild(textArea);
          textArea.focus();//文本处于可编辑区域
          textArea.select();//且被选中

          // 执行复制命令
          const success = document.execCommand('copy');
          document.body.removeChild(textArea);
          resolve(success);
        }
      } catch (err) {
        console.error("复制失败:", err);
        resolve(false);
      }
    });
  };

  const handleCopy = async () => {
    try {
      // 转换内容为字符串
      const codeContent = String(value).replace(/\n$/, "");
      
      // 执行复制操作
      const success = await copyToClipboard(codeContent);
      
      if (success) {
        setCopied(true);
        setTimeout(() => setCopied(false), 2000);
      } else {
        // 降级提示
        alert("自动复制失败,请手动选择文本后按 Ctrl+C");
      }
    } catch (err) {
      console.error("复制异常:", err);
    }
  };

  return (
    		<button
              onClick={handleCopy}
              style={{
                position: 'absolute',
                right: 8,
                top: 8,
                padding: '4px 8px',
                background: '#f5f5f5',
                border: '1px solid #ddd',
                borderRadius: 4,
                cursor: 'pointer',
                zIndex: 1,
                opacity: copied ? 1 : 0.6,
                transition: "opacity 0.2s",
              }}
            >
              {copied ? '复制成功' : '⎘ 复制'}
           </button>
  );
};

关键点说明

  • 环境检测 navigator.clipboard && window.isSecureContext
typescript 复制代码
window.isSecureContext // 检测 HTTPS本地环境

window.isSecureContext是一个浏览器 API,用于检查当前页面是否在安全上下文中运行。如果页面在安全上下文中运行(即通过HTTPS加载),则该函数返回true,否则返回false。

  • 关键思路:
  • 降级使用document.execCommand('copy')的旧方法。同时,还创建了一个临时的textarea元素来执行复制操作,并处理了相关的用户体验问题,比如提示信息、样式调整等。
  • 具体到复制功能,临时创建textarea元素的原因是为了避免界面干扰,并确保文本可以被正确选中。使用execCommand('copy')需要文本处于可编辑区域且被选中,所以创建隐藏的textarea是实现这一点的常用方法 。同时,处理完复制操作后需要立即移除该元素,避免DOM污染。
相关推荐
Jackson__几秒前
面试官:如何在老项目中使用新框架,并实现通信?
前端·javascript·面试
拾年2753 分钟前
React Hooks 保姆级教程:把组件想象成失忆的打工人,4 个 Hook 带你原地起飞
前端·javascript·react.js
默_笙11 分钟前
😝 5 亿次循环卡死页面?我用 Web Worker 把它扔进后台线程,页面丝滑如初
前端·javascript
CAD老兵13 分钟前
一行代码集成 DWG/DXF 图纸查看:测量批注,数据不出站
前端·javascript·github
小林ixn14 分钟前
单例模式:从弹窗管理到全局状态,一个模式搞定
前端·javascript·设计模式
名字还没想好☜34 分钟前
React 用 useEffect 做轮询实战:setInterval 拿到旧 state 的闭包陷阱与正确清理
前端·javascript·react.js·react·useeffect
SendTomo2 小时前
P2P文件传输工具对比:SendTomo vs send.wang
javascript·网络协议·webrtc·html5·p2p
SendTomo4 小时前
WebRTC文件互传工具实测对比
javascript·网络·webrtc·html5·p2p
雪碧聊技术4 小时前
Vue + Element Plus 实现文本溢出显示省略号及悬浮提示
前端·javascript·css·vue.js·文本溢出省略
张元清7 小时前
React useLatest Hook:在异步回调里读到最新状态 (2026)
javascript·react.js