JavaScript 实现为图片添加水印的方法详解
本文详解如何利用 JavaScript 和 Canvas 为本地及在线图片添加水印。涵盖 FileReader 读取本地文件、处理跨域 CORS 问题,以及分别实现文字水印和图片水印的具体代码逻辑,帮助开发者在前端轻松完成图片版权保护与品牌标识。
背景
在许多应用场景中,上传图片时系统会自动添加默认水印。水印的主要作用包括以下几个方面:
- 1. 版权保护:在商业用途的照片中添加水印有助于保护作者版权,防止他人未经授权使用。
- 2. 品牌推广:将商业品牌、商标或公司标志添加到照片中,有助于提高品牌知名度和曝光率。
- 3. 防止盗版:添加水印可以防止盗版和未经授权的使用,因为水印会明确表明该照片的版权归原作者所有。
- 4. 标识来源:在社交媒体平台上分享照片时,添加水印可以帮助其他用户识别出照片的来源和作者。
- 5. 识别真伪:对于一些重要的照片或证件,如证书或合同等,加上水印可以帮助识别真伪,防止伪造和篡改。
因此,在个人网站进行图片操作时,我们也可以为其添加独特的水印。那么作为一名前端开发人员,该如何实现给图片添加水印呢?
实现
对图片进行处理,我们的首选当然是 canvas 啦,使用 canvas 我们可以便捷地对图片进行操作,我们需要操作的图片主要分为以下两种:
- 1、本地上传的图片
- 2、线上链接图片
file 转 base64
对于本地上传的图片,我们需要先将其转换成 base64 再进行后续处理:
我们可以通过 FileReader 来获取图片的 base64,FileReader 是一种异步读取文件机制。
FileReader 提供了如下方法:
- readAsArrayBuffer(file):按字节读取文件内容,结果用 ArrayBuffer 对象表示
- readAsBinaryString(file):按字节读取文件内容,结果为文件的二进制串
- readAsDataURL(file):读取文件内容,结果用 data:url 的字符串形式表示
- readAsText(file,encoding):按字符读取文件内容,结果用字符串形式表示
- abort():终止文件读取操作
readAsDataURL 方法会读取指定的 Blob 或 File 对象。并生成 data URl(base64 编码)。这里我们可以使用readAsDataURL来获取上传图片的 base64 编码:
function fileToBase64Async(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
resolve(e.target.result);
};
});
}使用 canvas 给图片加水印
使用在线图片链接的时候需要注意给图片设置crossOrigin属性(img.setAttribute("crossOrigin",'Anonymous')),不然会出现以下错误:
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
大概意思是 canvas 无法执行 toDataURL 方法:污染的画布无法输出。受限于 CORS 策略,会存在跨域问题,虽然可以使用图像(比如 append 到页面上)但是绘制到画布上会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布 toBlob(),toDataURL(),或 getImageData()方法;当使用这些方法的时候 会抛出一个安全错误。
我们这里可以分为文字水印和图片水印两种:
文字水印
添加文字水印的大致步骤如下:
1、生成一个新的 canvas 画布;
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;2、将现有需要添加水印的图片绘制到画布上;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);3、绘制需要添加的水印文本并设置样式。
我们可以使用fillText和strokeText这两个方法来绘制文字,fillText绘制的是默认的普通实线文本,strokeText绘制的是描边文本,这里我使用了strokeText来进行水印文本绘制。
完整代码如下:
const remFontSize = canvas.width / 35;
ctx.font = "bolder " + remFontSize + "px Verdana";
ctx.textAlign = "center";
ctx.strokeStyle = "#fff";
const name = "@JYeontu";
const spaceH = remFontSize * 0.3;
ctx.strokeText(name, canvas.width / 2, canvas.height - remFontSize - spaceH);function fillTextToImg(base64) {
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const remFontSize = canvas.width / 35;
ctx.font = "bolder " + remFontSize + "px Verdana";
ctx.textAlign = "center";
/**
ctx.textAlign = "center|end|left|right|start";
start:默认,文本在指定的位置开始。
end:文本在指定的位置结束。
center:文本的中心在指定的位置。
left:文本左对齐。
right:文本右对齐。
**/
ctx.strokeStyle = "#fff";
const name = "@JYeontu";
const spaceH = remFontSize * 0.3;
ctx.strokeText(
name,
canvas.width / 2,
canvas.height - remFontSize - spaceH
);
resolve(canvas.toDataURL("image/jpeg"));
};
});
}效果如下图,左边为原图,右边为加了文字水印的图片:

图片水印
//图片转为base64
async function getImgBase64(base64, width = 50) {
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = (img.height * width) / img.width;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL("image/jpeg"));
};
});
}
function fillImgToImg(base64, waterMark = imgLink) {
waterMark =
"https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e";
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = async () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const waterMarkSrc = await getImgBase64(waterMark, 100);
const waterMarkImg = new Image();
waterMarkImg.src = waterMarkSrc;
waterMarkImg.setAttribute("crossOrigin", "Anonymous");
waterMarkImg.onload = () => {
ctx.drawImage(
waterMarkImg,
canvas.width / 2 - waterMarkImg.width / 2,
canvas.height - waterMarkImg.height - 10,
waterMarkImg.width,
waterMarkImg.height
);
resolve(canvas.toDataURL("image/jpeg"));
};
};
});
}效果如下图,左边为原图,右边为加了图片水印的图片:

完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 500px;
}
</style>
</head>
<body>
<input type="file" id="fileUplodBox" />
<img alt="原图" id="originPic" />
<img alt="水印图" id="waterMark" />
</body>
<script>
const imgLink =
"https://img2.baidu.com/it/u=2048195462,703560066&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=a0c977f68632303e7dac2196e8ad2866";
document.getElementById("originPic").setAttribute("src", imgLink);
const fileUplodBox = document.getElementById("fileUplodBox");
fileUplodBox.addEventListener("change", (e) => {
const file = e.target.files[0];
dealFile(file);
});
test();
async function test() {
const img = await fillImgToImg(imgLink);
document.getElementById("waterMark").setAttribute("src", img);
}
async function dealFile(file) {
const base64 = await fileToBase64Async(file);
document.getElementById("originPic").setAttribute("src", base64);
const img = await fillTextToImg(base64);
document.getElementById("waterMark").setAttribute("src", img);
}
function fileToBase64Async(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
resolve(e.target.result);
};
});
}
// 给图片加文字水印
function fillTextToImg(base64) {
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const remFontSize = canvas.width / 35;
ctx.font = "bolder " + remFontSize + "px Verdana";
ctx.textAlign = "center";
ctx.strokeStyle = "#fff";
const uploadTime = new Date();
const name = "@JYeontu";
const spaceH = remFontSize * 0.3;
ctx.strokeText(
name,
canvas.width / 2,
canvas.height - remFontSize - spaceH
);
resolve(canvas.toDataURL("image/jpeg"));
};
});
}
async function getImgBase64(base64, width = 50) {
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = (img.height * width) / img.width;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL("image/jpeg"));
};
});
}
// 给图片加图片水印
function fillImgToImg(base64, waterMark = imgLink) {
waterMark =
"https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e";
const img = new Image();
img.src = base64;
img.setAttribute("crossOrigin", "Anonymous");
return new Promise((resolve, reject) => {
img.onload = async () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const waterMarkSrc = await getImgBase64(waterMark, 100);
const waterMarkImg = new Image();
waterMarkImg.src = waterMarkSrc;
waterMarkImg.setAttribute("crossOrigin", "Anonymous");
waterMarkImg.onload = () => {
ctx.drawImage(
waterMarkImg,
canvas.width / 2 - waterMarkImg.width / 2,
canvas.height - waterMarkImg.height - 10,
waterMarkImg.width,
waterMarkImg.height
);
resolve(canvas.toDataURL("image/jpeg"));
};
};
});
}
</script>
</html>以上就是JavaScript实现为图片添加水印的方法详解的详细内容,更多关于JavaScript图片添加水印的资料请关注本站其它相关文章!
JS在setTimeout或Ajax回调中打开新标签页失败怎么处理
2026-11-05 15:50:12
JS直播协议选型:HLS、FLV与WebRTC的延迟与兼容性对比
2026-11-05 12:16:14
JS实现回到顶部:scrollTop赋值与浏览器兼容性处理
2026-11-04 23:43:35
JS使用classList动态添加、移除和切换CSS类名
2026-11-04 23:40:58
在 D3.js 中实现平滑过渡动画的技巧
2026-11-03 23:46:09
