一、效果图
二、navigator.clipboard
API
直奔主题。代码块复制使用现代浏览器原生 Clipboard API
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> )
关键点说明
- 状态管理:
使用 useState 跟踪复制状态,提供视觉反馈
2秒后自动重置复制状态提示
- 样式定位
绝对定位到代码块右上角
使用:
<div style={{position: 'relative'}}>//外层相对定位 //复制 {children && handCopyRender(children)}//复制块绝对定位 //代码块dom //例:<SyntaxHighlighter/> </div>
- 内容处理:
const codeContent = String(children).replace(/\n$/, "");
移除代码结尾的多余换行符确保复制内容格式正确
三、浏览器兼容处理
- 旧版浏览器不支持 Clipboard APIHTTP环境下无法生效浏览器安全限制:Clipboard API 要确保在 HTTPS 环境或 localhost 下使用
兼容性处理后:
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
window.isSecureContext // 检测 HTTPS本地环境
window.isSecureContext
是一个浏览器 API,用于检查当前页面是否在安全上下文中运行。如果页面在安全上下文中运行(即通过HTTPS加载),则该函数返回true,否则返回false。
- 关键思路:降级使用
document.execCommand('copy')
的旧方法。同时,还创建了一个临时的textarea
元素来执行复制操作,并处理了相关的用户体验问题,比如提示信息、样式调整等。具体到复制功能,临时创建textarea
元素的原因是为了避免界面干扰,并确保文本可以被正确选中。使用execCommand('copy')
需要文本处于可编辑区域且被选中,所以创建隐藏的textarea
是实现这一点的常用方法。同时,处理完复制操作后需要立即移除该元素,避免DOM
污染。