前端自定义右键菜单与图片复制(兼容H5)
PC端实现自定义右键菜单与图片“复制图片并加水印”功能的完整实践(兼容H5)
背景
在Web应用开发中,我们常常需要自定义右键菜单,以提升用户体验或实现更高级的交互功能。另一方面,针对图片内容的操作(如加水印并复制)也是内容安全和品牌营销常用的需求。如果要实现一个 兼容PC与H5端的右键自定义菜单,并在图片上集成“复制图片(并加水印)”的功能(支持文字/图片水印、可自定义水印位置和尺寸),应该如何设计?本文将从技术分享者的角度,详细介绍实现思路、关键代码和插件化/兼容性优化建议。

一、需求分解与技术选型
- 自定义右键菜单:原生浏览器右键菜单无法完全控制,需拦截
contextmenu事件,渲染自定义菜单。 - 菜单项“复制图片(并加水印)”:
- 图片渲染至
canvas后添加水印(文字或图片方式均可,位置/尺寸可配置)。 - 将处理后的图片写入剪切板(PC端首选Clipboard API,H5端兼容降级为图片下载等)。
- 图片渲染至
- 兼容性要求:兼容主流桌面浏览器和移动端H5浏览器(触摸设备右键替代方案)。
- 扩展性/优雅性:代码结构应支持插件化,便于自定义菜单样式、扩展新功能。
二、实现流程详解
1. 右键菜单的实现
Step1: 拦截并阻止默认右键菜单
document.addEventListener('contextmenu', function(e) {// 判断目标为目标图片或容器if (e.target.classList.contains('custom-context-img')) {e.preventDefault();showCustomMenu(e);}
});
Step2: 渲染自定义菜单并定位
function showCustomMenu(e) {// 动态插入菜单DOM(可优化为组件)const menu = document.createElement('div');menu.className = 'custom-context-menu';menu.style.top = `${e.clientY}px`;menu.style.left = `${e.clientX}px`;menu.innerHTML = `<div class="custom-context-item" data-action="copy-watermark">复制图片(并加水印)</div><div class="custom-context-item" data-action="download">下载图片</div><!-- 其他菜单项 -->`;document.body.appendChild(menu);// 点击菜单项事件menu.addEventListener('click', (ev) => {const action = ev.target.dataset.action;handleMenuAction(action, e.target); // e.target为右击图片removeCustomMenu();});// 点击其他区域关闭菜单setTimeout(() => {document.addEventListener('click', removeCustomMenu, { once: true });}, 0);
}function removeCustomMenu() {const menu = document.querySelector('.custom-context-menu');menu && menu.remove();
}
Step3: 兼容移动端(H5)
移动端无右键,通常可用“长按”事件模拟:
let touchTimer = null;
document.addEventListener('touchstart', function(e) {if (e.target.classList.contains('custom-context-img')) {touchTimer = setTimeout(() => {showCustomMenu(e.touches[0]);}, 600); // 长按600ms触发}
});
document.addEventListener('touchend', () => { clearTimeout(touchTimer); });
2. “复制图片并加水印”功能详解
Step1: 将图片绘制到Canvas
function drawImageToCanvas(img, width, height) {const canvas = document.createElement('canvas');canvas.width = width || img.naturalWidth || 300;canvas.height = height || img.naturalHeight || 150;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0, canvas.width, canvas.height);return { canvas, ctx };
}
Step2: 添加文本或图片水印
function addTextWatermark(ctx, text, opts) {ctx.save();ctx.globalAlpha = opts.opacity ?? 0.5;ctx.font = `${opts.fontWeight || 'bold'} ${opts.fontSize || 24}px ${opts.fontFamily || 'sans-serif'}`;ctx.fillStyle = opts.color || 'rgba(0,0,0,0.2)';ctx.textAlign = opts.align || 'right';ctx.textBaseline = opts.baseline || 'bottom';// 支持自定义位置const x = opts.x || ctx.canvas.width - 20;const y = opts.y || ctx.canvas.height - 20;ctx.rotate((opts.angle || 0) * Math.PI / 180);ctx.fillText(text, x, y);ctx.restore();
}function addImageWatermark(ctx, watermarkImg, opts) {ctx.save();ctx.globalAlpha = opts.opacity ?? 0.5;// 尺寸自定义const w = opts.width || watermarkImg.width;const h = opts.height || watermarkImg.height;// 位置自定义const x = opts.x !== undefined ? opts.x : (ctx.canvas.width - w - 20);const y = opts.y !== undefined ? opts.y : (ctx.canvas.height - h - 20);ctx.drawImage(watermarkImg, x, y, w, h);ctx.restore();
}
Step3: 写入剪切板(PC端)或兼容处理(H5)
PC端 Clipboard API:
async function copyCanvasToClipboard(canvas) {try {// 仅安全环境支持canvas.toBlob(async (blob) => {await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);alert('已复制到剪贴板!');});} catch (err) {alert('复制失败,请使用图片下载');}
}
H5端降级:下载保存图片
function saveCanvasAsImage(canvas) {const url = canvas.toDataURL('image/png');const link = document.createElement('a');link.href = url;link.download = 'watermark.png';link.click();
}
Step4: 菜单动作处理
function handleMenuAction(action, imgElement) {if (action === 'copy-watermark') {const { canvas, ctx } = drawImageToCanvas(imgElement);// 示例:加文字水印addTextWatermark(ctx, 'Demo Watermark', {x: canvas.width - 120, y: canvas.height - 20,color: 'rgba(255,0,0,0.3)', fontSize: 28, angle: -15});// 示例:加图片水印/*const watermarkImg = new Image();watermarkImg.src = 'url_to_watermark.png';watermarkImg.onload = () => {addImageWatermark(ctx, watermarkImg, { x: 10, y: 10, width: 100, height: 60, opacity: 0.5 });} */// 兼容性处理if (navigator.clipboard && window.ClipboardItem) {copyCanvasToClipboard(canvas);} else {saveCanvasAsImage(canvas);}}// 其他action: download等
}
三、样式与易用性优化
可用CSS美化菜单外观:
.custom-context-menu {position: fixed;z-index: 999999;min-width: 180px;background: #fff;box-shadow: 0 2px 10px rgba(0,0,0,0.15);border-radius: 8px;overflow: hidden;font-family: sans-serif;
}
.custom-context-item {padding: 12px 18px;cursor: pointer;transition: background 0.15s;
}
.custom-context-item:hover {background: #4170ea;color: #fff;
}
四、兼容性&扩展性说明
- 跨平台兼容:基于DOM事件的右键/长按兼容绝大多数主流浏览器;Clipboard API自动降级到图片下载。
- 水印方式灵活:支持文字/图片任意组合、自定义参数,便于嵌入企业Logo或时效标记。
- 扩展性方案:将菜单渲染逻辑、图片处理逻辑模块解耦,便于后续增加菜单项或迁移到Vue/React等主流前端框架中。
- 安全性提示:部分Clipboard API特性需HTTPS和权限,移动端请适当引导用户下载图片。
五、完整Demo工程&总结
完整Demo代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><title>PC端自定义右键菜单与图片复制加水印 Demo</title><style>body { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; background: #f6f8fa; min-height: 100vh; margin: 0; padding: 0; }.img-container { margin: 32px auto; text-align: center; }.custom-context-img { border-radius: 10px; box-shadow: 0 4px 12px #0001; margin: 12px 0; max-width: 100%; }.custom-context-menu {position: fixed;z-index: 99999;min-width: 200px;background: #fff;border-radius: 7px;box-shadow: 0 4px 18px rgba(0,0,0,0.18);padding: 7px 0;transition: opacity 0.12s;user-select: none;font-family: inherit;}.custom-context-menu .custom-context-item {padding: 11px 22px;cursor: pointer;font-size: 16px;color: #434658;transition: background 0.12s, color 0.12s;}.custom-context-menu .custom-context-item:hover {background: #007aff;color: #fff;}@media (max-width: 600px) {.img-container { padding: 12px; }}</style>
</head>
<body><div class="img-container"><h2>自定义右键菜单图片演示</h2><imgclass="custom-context-img"src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=600"width="420"alt="演示图片"><br><imgclass="custom-context-img"src="https://images.unsplash.com/photo-1465101162946-4377e57745c3?w=600"width="420"alt="演示图片2"><p style="color:#888;margin-top:24px;font-size:15px;">支持PC右键,也可移动端长按图片</p></div><script>// -- 1. 显示自定义右键菜单 (兼容PC和H5端) --(function(){let menuEl;let longPressTimer = null;function removeMenu() {if (menuEl) {menuEl.remove();menuEl = null;}}// 主入口:在图片上右键document.addEventListener('contextmenu', function(e) {if (e.target.classList.contains('custom-context-img')) {e.preventDefault();showMenuAt(e.clientX, e.clientY, e.target);}});// 主入口:长按移动端图片document.addEventListener('touchstart', function(e) {const t = e.target;if (t.classList.contains('custom-context-img')) {longPressTimer = setTimeout(() => {const touch = e.touches[0];showMenuAt(touch.clientX, touch.clientY, t);}, 660);}});document.addEventListener('touchend', function(){ clearTimeout(longPressTimer); });document.addEventListener('touchmove', function(){ clearTimeout(longPressTimer); });// 关闭菜单document.addEventListener('click', removeMenu);document.addEventListener('scroll', removeMenu, true);function showMenuAt(x, y, imgEl) {removeMenu();menuEl = document.createElement('div');menuEl.className = 'custom-context-menu';menuEl.innerHTML = `<div class="custom-context-item" data-act="copy-wm">复制图片(并加水印)</div><div class="custom-context-item" data-act="download-wm">下载加水印图片</div><div class="custom-context-item" data-act="normal-download">直接下载原图片</div>`;menuEl.style.top = y + 'px';menuEl.style.left = x + 'px';// 溢出窗口自动适配setTimeout(() => {const rect = menuEl.getBoundingClientRect();if (rect.right > window.innerWidth) {menuEl.style.left = (window.innerWidth - rect.width - 14) + 'px';}if (rect.bottom > window.innerHeight) {menuEl.style.top = (window.innerHeight - rect.height - 8) + 'px';}}, 8);menuEl.onclick = (ev) => {const act = ev.target.dataset.act;if (act) {handleMenuAction(act, imgEl);removeMenu();}ev.stopPropagation();};document.body.appendChild(menuEl);}// -- 2. 菜单项操作逻辑 --async function handleMenuAction(action, img) {if (action === "copy-wm" || action === "download-wm") {// 加水印const {canvas} = await drawImageWithWatermark(img, {type: 'text', // 'text' or 'image'text: 'Copilot Demo', // 水印内容color: 'rgba(255,255,255,1)', // 水印颜色fontSize: 32, // 水印字体大小angle: 0, // 角度pos: 'br', // 'br'(右下),'tl'(左上)... 可自定义opacity: 0.9,offsetX: 24, offsetY: 24// 如为图片形式,加 { type:'image', watermarkUrl:..., ...}});if (action === "copy-wm") {if (navigator.clipboard && window.ClipboardItem) {canvas.toBlob(async (blob) => {try {await navigator.clipboard.write([new window.ClipboardItem({ [blob.type]: blob })]);// 复制成功弹窗预览showImagePreview(canvas.toDataURL('image/png'), '已复制带水印的图片!你可以Ctrl+V粘贴到聊天窗口或文档里查看。');} catch {alert("复制失败,建议使用下载保存方式");}}, 'image/png');} else {saveCanvas(canvas, 'watermarked.png');}}if (action === "download-wm") {saveCanvas(canvas, 'watermarked.png');}}if (action === "normal-download") {const a = document.createElement("a");a.href = img.src;a.download = "origin.png";a.click();}}// -- 3. Canvas 绘制和加水印支持自定义位置/字体等 --function drawImageWithWatermark(imgEl, options) {return new Promise((resolve) => {// 支持跨域图片const img = new window.Image();img.crossOrigin = "anonymous";img.onload = () => {const w = img.width, h = img.height;const canvas = document.createElement("canvas");canvas.width = w; canvas.height = h;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0, w, h);if (options.type === 'text') {const text = options.text || "Watermark";ctx.save();ctx.globalAlpha = options.opacity ?? 0.8;ctx.font = `bold ${options.fontSize||26}px sans-serif`;ctx.fillStyle = options.color || "rgba(255,0,0,0.25)";ctx.textAlign = "right";ctx.textBaseline = "bottom";// 计算位置let x=0, y=0;const marginX=options.offsetX||20, marginY=options.offsetY||20;if (options.pos === 'br') { x = w - marginX; y = h - marginY; }else if (options.pos === 'tr') { x = w - marginX; y = marginY + (options.fontSize||26);}else if (options.pos === 'tl') { x = marginX; y = (options.fontSize||26) + marginY; }else if (options.pos === 'bl') { x = marginX; y = h - marginY; }else { x = w - marginX; y = h - marginY; }// 旋转角度if (options.angle) {ctx.translate(x, y);ctx.rotate(options.angle * Math.PI / 180);ctx.translate(-x, -y);}ctx.fillText(text, x, y);ctx.restore();}if (options.type === 'image' && options.watermarkUrl) {const wm = new window.Image();wm.crossOrigin = "anonymous";wm.onload = () => {ctx.save();ctx.globalAlpha = options.opacity||0.5;// 尺寸const wmw = options.wmWidth||Math.floor(w*0.18), wmh = options.wmHeight||Math.floor(h*0.15);// 位置const wx = options.offsetX ? options.offsetX : (w - wmw - 18);const wy = options.offsetY ? options.offsetY : (h - wmh - 16);ctx.drawImage(wm, wx, wy, wmw, wmh);ctx.restore();resolve({canvas, ctx});};wm.src = options.watermarkUrl;return; // 异步resolve}resolve({canvas, ctx});};img.src = imgEl.src;});}// 保存canvas图片function saveCanvas(canvas, filename) {const url = canvas.toDataURL('image/png');const a = document.createElement("a");a.href = url;a.download = filename||"图片.png";a.click();}// 4. 复制成功后弹窗预览图片function showImagePreview(imgUrl, tip) {const div = document.createElement('div');div.className = 'image-preview-mask';div.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:999999;background:rgba(0,0,0,0.27);display:flex;flex-direction:column;align-items:center;justify-content:center;';div.innerHTML = `<div style="background:#fff;padding:22px 22px 15px 22px;border-radius:8px;box-shadow:0 4px 24px #0002;max-width:92vw;"><div style="font-size:16px;color:#222;margin-bottom:10px;">${tip||'预览'}</div><img src="${imgUrl}" style="max-width:460px;max-height:48vh;box-shadow:0 2px 10px #0001;border-radius:6px;" /><div style="text-align:center;margin-top:12px;"><button onclick="this.closest('.image-preview-mask').remove()" style="padding:4px 21px;font-size:15px;background:#007aff;color:#fff;border:none;border-radius:4px;cursor:pointer;">关闭</button></div></div>`;document.body.appendChild(div);}})();</script>
</body>
</html>
总结
本文系统分享了跨端自定义右键菜单与**图片“复制并加水印”**的实现思路与完整代码,解决了常见的兼容性与可扩展性痛点。核心思路为Canvas图片处理+剪贴板API按需降级,并通过优雅的事件解耦和样式设计确保良好用户体验。相关功能不只适用于日常项目,也适合二次封装输出为通用插件,提高团队开发效率。
react组件版本
// 组件封装
import React, { useRef, useEffect, useState } from "react";/*** WatermarkContextMenu* 可复用的图片右键自定义菜单 + 水印复制/保存组件** Props:* - imgSrc: 图片url(必传)* - render?: 传入自定义图片节点(可选,优先渲染)* - watermark: {* type: "text" | "image",* text?: string,* color?: string,* fontSize?: number,* angle?: number,* pos?: "br" | "bl" | "tr" | "tl",* opacity?: number,* offsetX?: number,* offsetY?: number,* imageUrl?: string, // type=image 时的水印图片* wmWidth?: number,* wmHeight?: number,* }* - menuItems?: 追加自定义菜单项(数组: { label: string, onClick: ()=>void }[])*/const menuBase = [{id: "copy-wm",label: "复制图片(并加水印)",},{id: "download-wm",label: "下载加水印图片",},{id: "normal-download",label: "直接下载原图片",},
];/** 工具方法,将图片绘制到canvas并加水印 */
function drawImageWithWatermark({ img, options }) {return new Promise((resolve, reject) => {const maybeLoad = img.complete? Promise.resolve(): new Promise((res, rej) => {img.onload = () => res();img.onerror = rej;});maybeLoad.then(() => {const w = img.naturalWidth || img.width,h = img.naturalHeight || img.height;const canvas = document.createElement("canvas");canvas.width = w;canvas.height = h;const ctx = canvas.getContext("2d");ctx.drawImage(img, 0, 0, w, h);if (options.type === "text") {const text = options.text || "Watermark";ctx.save();ctx.globalAlpha = options.opacity ?? 0.38;ctx.font = `bold ${options.fontSize || 26}px sans-serif`;ctx.fillStyle = options.color || "rgba(255,0,0,0.25)";ctx.textAlign = "right";ctx.textBaseline = "bottom";let x = 0,y = 0;const marginX = options.offsetX || 20,marginY = options.offsetY || 20;if (options.pos === "br") {x = w - marginX;y = h - marginY;} else if (options.pos === "tr") {x = w - marginX;y = marginY + (options.fontSize || 26);} else if (options.pos === "tl") {x = marginX;y = (options.fontSize || 26) + marginY;} else if (options.pos === "bl") {x = marginX;y = h - marginY;} else {x = w - marginX;y = h - marginY;}if (options.angle) {ctx.translate(x, y);ctx.rotate((options.angle * Math.PI) / 180);ctx.translate(-x, -y);}ctx.fillText(text, x, y);ctx.restore();resolve({ canvas, ctx });} else if (options.type === "image" &&options.imageUrl &&options.imageUrl.length > 0) {const wm = new window.Image();wm.crossOrigin = "anonymous";wm.onload = () => {ctx.save();ctx.globalAlpha = options.opacity || 0.5;const wmw = options.wmWidth || Math.floor(w * 0.18),wmh = options.wmHeight || Math.floor(h * 0.15);const wx =typeof options.offsetX === "number"? options.offsetX: w - wmw - 18;const wy =typeof options.offsetY === "number"? options.offsetY: h - wmh - 16;ctx.drawImage(wm, wx, wy, wmw, wmh);ctx.restore();resolve({ canvas, ctx });};wm.onerror = reject;wm.src = options.imageUrl;} else {resolve({ canvas, ctx });}}).catch(reject);});
}function saveCanvas(canvas, filename) {const url = canvas.toDataURL("image/png");const a = document.createElement("a");a.href = url;a.download = filename || "图片.png";a.click();
}/** 复制成功弹窗预览 */
function PreviewModal({ imgUrl, tip, onClose }) {useEffect(() => {// 支持键盘 Esc 关闭function handle(e) {if (e.key === "Escape") onClose();}window.addEventListener("keydown", handle);return () => window.removeEventListener("keydown", handle);}, [onClose]);return (<divclassName="wm-preview-mask"style={{position: "fixed",zIndex: 999999,left: 0,top: 0,right: 0,bottom: 0,background: "rgba(0,0,0,0.27)",display: "flex",flexDirection: "column",alignItems: "center",justifyContent: "center",}}tabIndex={-1}onClick={onClose}><divstyle={{background: "#fff",padding: 22,borderRadius: 8,boxShadow: "0 4px 24px #0002",maxWidth: "92vw",}}onClick={e => e.stopPropagation()}><divstyle={{fontSize: 16,color: "#222",marginBottom: 10,textAlign: "center",}}>{tip || "预览"}</div><imgsrc={imgUrl}alt="预览"style={{maxWidth: 460,maxHeight: "48vh",boxShadow: "0 2px 10px #0001",borderRadius: 6,}}/><div style={{ textAlign: "center", marginTop: 12 }}><buttononClick={onClose}style={{padding: "4px 21px",fontSize: 15,background: "#007aff",color: "#fff",border: "none",borderRadius: 4,cursor: "pointer",}}>关闭</button></div></div></div>);
}/** 主组件 */
export default function WatermarkContextMenu({imgSrc,render,watermark = {type: "text",text: "Copilot Demo",color: "rgba(0,0,0,0.25)",fontSize: 32,angle: -22,pos: "br",opacity: 0.55,offsetX: 24,offsetY: 24,},menuItems = [],
}) {const imgRef = useRef();const menuRef = useRef();const [menu, setMenu] = useState(null); // {x, y, visible}const [preview, setPreview] = useState(null); // {imgUrl, tip}// 弹菜单function openMenu(e) {e.preventDefault?.();const x = e.clientX || (e.touches && e.touches[0].clientX) || 100;const y = e.clientY || (e.touches && e.touches[0].clientY) || 100;setMenu({ x, y });}useEffect(() => {if (!menu) return;function close() {setMenu(null);}window.addEventListener("click", close);window.addEventListener("scroll", close, true);return () => {window.removeEventListener("click", close);window.removeEventListener("scroll", close, true);};}, [menu]);// 长按兼容H5useEffect(() => {let timer;const node = imgRef.current;if (!node) return;function onTouchStart(e) {timer = setTimeout(() => {openMenu(e);}, 660);}function clear() {clearTimeout(timer);}node.addEventListener("touchstart", onTouchStart);node.addEventListener("touchend", clear);node.addEventListener("touchmove", clear);return () => {node.removeEventListener("touchstart", onTouchStart);node.removeEventListener("touchend", clear);node.removeEventListener("touchmove", clear);};}, []);async function doMenuAction(id) {setMenu(null);if (id === "copy-wm" || id === "download-wm") {const img = imgRef.current;try {const { canvas } = await drawImageWithWatermark({img,options: watermark,});if (id === "copy-wm") {if (navigator.clipboard && window.ClipboardItem) {canvas.toBlob(async (blob) => {try {await navigator.clipboard.write([new window.ClipboardItem({ [blob.type]: blob }),]);setPreview({imgUrl: canvas.toDataURL("image/png"),tip:"已复制带水印的图片!你可以Ctrl+V粘贴到聊天窗口或文档里验证。",});} catch {saveCanvas(canvas, "watermarked.png");}}, "image/png");} else {saveCanvas(canvas, "watermarked.png");}} else if (id === "download-wm") {saveCanvas(canvas, "watermarked.png");}} catch (err) {alert("处理失败: " + err);}} else if (id === "normal-download") {const img = imgRef.current;const a = document.createElement("a");a.href = img.src;a.download = "origin.png";a.click();}}// 右键菜单样式const menuStyle =menu &&{position: "fixed",zIndex: 99999,minWidth: 200,background: "#fff",borderRadius: 7,boxShadow: "0 4px 18px rgba(0,0,0,0.18)",padding: "7px 0",userSelect: "none",fontFamily: "inherit",left: menu.x,top: menu.y,};return (<><spanstyle={{ display: "inline-block", cursor: "pointer", position: "relative" }}onContextMenu={openMenu}tabIndex={0}>{render ? (// 用户自定义子节点,自动绑定refReact.cloneElement(render, { ref: imgRef })) : (<imgref={imgRef}className="custom-context-img"src={imgSrc}alt="可自定义右键菜单图片"style={{borderRadius: 10,boxShadow: "0 4px 12px #0001",margin: "12px 0",maxWidth: "100%",cursor: "pointer",}}/>)}{/* 菜单 */}{menu && (<div ref={menuRef} style={menuStyle}>{menuBase.map((item) => (<divkey={item.id}style={{padding: "11px 22px",fontSize: 16,color: "#434658",cursor: "pointer",transition: "background 0.12s, color 0.12s",borderRadius: 3,marginBottom: 2,}}onClick={() => doMenuAction(item.id)}onTouchEnd={e => { e.preventDefault(); doMenuAction(item.id); }}>{item.label}</div>))}{menuItems.map((item, i) => (<divkey={`cus-${i}`}style={{padding: "11px 22px",fontSize: 16,color: "#434658",cursor: "pointer",borderRadius: 3,transition: "background 0.12s, color 0.12s",}}onClick={() => {setMenu(null);item.onClick && item.onClick();}}onTouchEnd={e => {e.preventDefault();setMenu(null);item.onClick && item.onClick();}}>{item.label}</div>))}</div>)}</span>{/* 复制/保存弹窗 */}{preview && (<PreviewModalimgUrl={preview.imgUrl}tip={preview.tip}onClose={() => setPreview(null)}/>)}</>);
}
// 组件使用示例
import React from "react";
import WatermarkContextMenu from "./WatermarkContextMenu";export default function App() {return (<div style={{ textAlign: "center", margin: 40 }}><h2>自定义右键菜单图片演示 - React 组件</h2><WatermarkContextMenuimgSrc="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=600"watermark={{type: "text",text: "Copilot Demo",color: "rgba(255,0,0,0.22)",fontSize: 34,angle: -20,pos: "br",opacity: 0.58,offsetX: 22,offsetY: 22,}}menuItems={[{label: "自定义菜单动作",onClick: () => alert("你点击了自定义菜单~"),},]}/><br /><WatermarkContextMenuimgSrc="https://images.unsplash.com/photo-1465101162946-4377e57745c3?w=600"/><p style={{ color: "#999" }}>支持PC右键,也可移动端长按图片弹出菜单</p></div>);
}
