图片上传前的自定义水印实践
背景
项目需要在图片上传前叠加动态水印。水印内容不是固定文案,而是由多个动态元素组成(如时间、地点、业务字段等)。
虽然七牛云支持图片水印能力,但更适合静态水印或固定参数场景。对于“多元素动态组合”场景,如果完全依赖云端参数拼接,通常会遇到:
- 动态内容表达能力有限;
- 需要额外生成并维护水印资源;
- 存储与流量成本增加。
因此,本方案采用“前端本地合成水印后再上传”的方式,降低云端处理复杂度与成本。
水印效果示意:
方案一:传统 canvas 直接绘制
思路是将原图绘制到 canvas,再通过 fillText/drawImage 绘制水印元素。
优点:
- 依赖少、性能可控;
- 输出过程清晰。
不足:
- 复杂水印(多行文本、图标、富样式)需要手动计算大量坐标;
- 布局变化时维护成本较高。
对于动态字段较多、样式复杂的场景,不是最优方案。
方案二:使用 html2canvas 组合 DOM 后导出
该方案适合“水印样式复杂、元素动态”的场景。
核心流程:
- 在页面中准备一个“离屏渲染容器”(可见区域外,但仍可被渲染);
- 上传前将原图转为对象 URL,并等待图片加载完成;
- 将原图与水印 DOM 组合到离屏容器,并设置为原图尺寸;
- 使用
html2canvas将容器转为canvas; canvas -> blob -> File,得到可上传的新文件。
示例代码:
this.getDom("#file-input").addEventListener("change", function (e) {
// 仅演示单图上传场景
const file = e.target.files[0];
if (!file) return;
const tempImg = new Image();
tempImg.src = URL.createObjectURL(file);
tempImg.addEventListener("load", function () {
const node = _this.getDom("#node");
const imgDom = _this.getDom("img");
// 将原图写入离屏容器,并同步尺寸
imgDom.setAttribute("src", tempImg.src);
node.style.width = `${tempImg.width}px`;
node.style.height = `${tempImg.height}px`;
html2canvas &&
html2canvas(node).then(function (canvas) {
canvas.toBlob((blob) => {
if (!blob) return;
// 生成可上传文件
const newFile = new File([blob], file.name, { type: file.type });
console.log(newFile);
// 本地预览
const imgSrc = canvas.toDataURL("image/jpeg", 1);
_this.getDom(".temp-container").innerHTML = `
<img
class="show"
style="width:${tempImg.width}px;height:${tempImg.height}px"
src="${imgSrc}"
alt=""
/>
`;
});
});
});
});
方案三:SVG 片段 + canvas 合并
如果水印结构相对固定,也可以先将水印模板写成 SVG,再与原图绘制到同一个 canvas 中。
核心步骤:
- 生成水印
SVG片段; - 将
SVG转为Image; - 先绘制原图,再在指定位置绘制水印图层;
- 导出新文件并上传。
示例代码:
// 水印 SVG 片段
const data = `
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
<div style="color:red">这里是水印元素</div>
</div>
</foreignObject>
</svg>
`;
const imgSvg = new Image();
const blob = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
function img2Canvas(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
const svgUrl = URL.createObjectURL(blob);
imgSvg.onload = function () {
ctx.drawImage(imgSvg, 20, canvas.height - 120);
URL.revokeObjectURL(svgUrl);
};
imgSvg.src = svgUrl;
return canvas;
}
this.getDom("#file-input2").addEventListener("change", function (e) {
const file = e.target.files[0];
if (!file) return;
const tempImg = new Image();
tempImg.src = URL.createObjectURL(file);
tempImg.addEventListener("load", () => {
img2Canvas(tempImg).toBlob((blob) => {
if (!blob) return;
const newFile = new File([blob], file.name, { type: file.type });
console.log(newFile);
});
});
});
实施建议
- 图片处理完成后及时调用
URL.revokeObjectURL释放对象 URL; - 对超大图增加压缩或尺寸限制,避免浏览器内存峰值过高;
- 优先异步处理并给出上传中状态,避免主线程卡顿;
- 若涉及跨域图片,请确保资源支持 CORS,否则
canvas可能被污染。
相关文章
防篡改水印
在各类管理后台、SaaS 平台或内部系统中,页面水印已经是非常常见的能力:一方面在截图时携带账号、姓名、时间等信息,降低截图外传的风险;另一方面在上传图片或文档预览时,通过水印标记上传者与时间,满足审计和合规需求。
GridView 宫格加载渲染优化
系统首页 GridView 宫格模块接口耗时不高,但首次进入总耗时接近 22s。复盘耗时分层定位过程、代码层面的瓶颈(约 2000 行、100 个 tab 重复节点)、优化手段与最终指标对比。
老系统升级与兼容改造实践
公司原有业务系统采用 Node + jQuery 架构,长期运行后暴露出典型遗留系统问题:
新商家系统性能优化实践
商家系统新版本上线后,团队持续针对构建效率与用户体验做了一轮系统性优化。本文记录核心方案与落地结果,供后续版本复用。
键盘弹起导致底部被顶起问题(H5 适配)
在移动端 H5 页面中,当用户聚焦 input、textarea 等可编辑元素、软键盘弹出时,常见表现包括:
企业微信 uni-app H5:OAuth 回退白屏与列表缓存
第三方 BI 报表项目,企业微信内嵌 H5,uni-app 编译,history 模式。主页面 BiLink 同时承担静默授权和列表展示,上线后碰到两个问题:授权完清掉 URL 参数,iOS 侧滑返回还是白屏;列表加了缓存以后,刷新行…
Series
team documents
10 / 22