XVIT

Jul 06, 2026

前端实现图片加水印

1. 前情提要

现在存在很多网站是给图片加水印,防止用户直接下载图片,那么前端如何实现图片加水印呢?这次我们采用的是纯前端HTML+JS实现的功能。

2. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas给图片加水印</title>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div class="container">
<div class="wpic_container">
<input type="file" class="uploadImage" accept="image/*" />
<canvas class="wpic" />
</div>
<div class="wpic_footer">
<button class="fbutton">下载图片</button>
</div>
</div>
<script src="/index.js"></script>
</body>
</html>

这里我们先初始化一下基本的架构,这里我们使用了canvas来绘制水印,然后通过input标签来上传图片,最后通过button标签来下载图片。我们可以点击wpic_container这里来上传图片,之后浏览器会自动将本地图片加载到缓存中,之后我们就可以通过canvas来绘制水印了。button就是用来下载我们添加好水印后的图片的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*::-webkit-scrollbar {display: none;}

html, body {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: linear-gradient(60deg, #11ffac, #00ebd4);
}
.container {
display: flex;
flex-direction: column;
width: 30%;
background: #fff;
border-radius: 1rem;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.wpic_container {
width: 100%;
min-height: 14rem;
max-height: 90vh;
flex: 1;
background: #08efff;
}
.uploadImage {
display: none;
}

.wpic_footer {
width: 100%;
height: 4rem;
padding: 1rem 1.2rem;
}
.fbutton {
outline: none;
border: none;
background: #08efff;
padding: .5rem 1.5rem;
border-radius: 2rem;
color: #fff;
letter-spacing: .1rem;
font-family: "Micorosoft YaHei UI", sans-serif;
font-weight: bold;
transition: box-shadow .5s;
&:hover {
box-shadow: 0 0 1rem #08efff46;
}
}
.wpic {
display: none;
width: 100%;
height: 100%;
}

这里的排布样式仅供参考,您可以按照自己的需求去设计。

这里就是我们的重头戏了,这里就是实现了加载图片到给图片加水印再到下载的功能。

1
2
3
4
5
6
7
const uploadBox = document.querySelector(".wpic_container");
const fileInput = document.querySelector(".uploadImage");
const canvas = document.querySelector(".wpic");
const downloadBtn = document.querySelector(".fbutton");

const ctx = canvas.getContext("2d");
let sourceImg = null;

我们先获取到所有的需要的框架的元素,这里我们获取到了uploadBoxfileInputcanvasdownloadBtn。后面的ctxcanvas的上下文,sourceImg是我们要添加水印的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 点击容器唤起文件选择
uploadBox.addEventListener("click", () => {
fileInput.click();
})

// 监听图片上传
fileInput.addEventListener("change", (e) => {
const file = e.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = (res) => {
sourceImg = new Image();
sourceImg.src = res.target.result;
sourceImg.onload = drawImageWithWatermark;
}
reader.readAsDataURL(file);
})

这两个函数相对的简单,uploadBox就是我们前面设计的框架盒子,点击后可以打开文件上传的弹窗。至于fileInput就是我们的文件上传的标签,当用户选择好图片后,就会触发change事件,我们就可以获取到用户选择的图片了。这里我们通过FileReader来读取图片,当读取完成后,我们就可以将图片加载到sourceImg中,然后调用drawImageWithWatermark函数来给图片添加水印。接下来我们就开始实现drawImageWithWatermark函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const drawImageWithWatermark = () => {
canvas.width = sourceImg.width;
canvas.height = sourceImg.height;
ctx.drawImage(sourceImg, 0, 0);

const watermarkText = "xvit";
const fontSize = 60;
ctx.fillStyle = "rgba(255, 255, 255, 0.6)";
ctx.font = `${fontSize}px Arial`;

const padding = 30; // 水印与画布边缘的距离
const textWidth = ctx.measureText(watermarkText).width; // 计算文字的宽度
const x = canvas.width - textWidth - padding; // 计算文字的x坐标:画布的宽度-文字宽度-偏移
const y = canvas.height - padding;
ctx.fillText(watermarkText, x, y); // 绘制文字

canvas.style.display = "block";
}

这样的话他就会生成水印并且写在右下角。

接下来我们就开始写下载图片的函数了。

1
2
3
4
5
6
7
8
9
10
11
// 下载按钮逻辑
downloadBtn.addEventListener("click", () => {
if (!sourceImg) {
alert("请先上传图片!");
return;
}
const link = document.createElement("a");
link.href = canvas.toDataURL("image/png");
link.download = `${Date.now()}.png`;
link.click();
})

由于前端无法直接下载文件,所以我们就先创建一个a标签,然后设置hrefcanvastoDataURL,然后设置download为下载的文件名,最后调用click方法来下载图片。

完整代码

html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas给图片加水印</title>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div class="container">
<div class="wpic_container">
<input type="file" class="uploadImage" accept="image/*" />
<canvas class="wpic" />
</div>
<div class="wpic_footer">
<button class="fbutton">下载图片</button>
</div>
</div>
<script src="/index.js"></script>
</body>
</html>

css>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*::-webkit-scrollbar {display: none;}

html, body {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: linear-gradient(60deg, #11ffac, #00ebd4);
}
.container {
display: flex;
flex-direction: column;
width: 30%;
background: #fff;
border-radius: 1rem;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.wpic_container {
width: 100%;
min-height: 14rem;
max-height: 90vh;
flex: 1;
background: #08efff;
}
.uploadImage {
display: none;
}

.wpic_footer {
width: 100%;
height: 4rem;
padding: 1rem 1.2rem;
}
.fbutton {
outline: none;
border: none;
background: #08efff;
padding: .5rem 1.5rem;
border-radius: 2rem;
color: #fff;
letter-spacing: .1rem;
font-family: "Micorosoft YaHei UI", sans-serif;
font-weight: bold;
transition: box-shadow .5s;
&:hover {
box-shadow: 0 0 1rem #08efff46;
}
}
.wpic {
display: none;
width: 100%;
height: 100%;
}

js>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const uploadBox = document.querySelector(".wpic_container");
const fileInput = document.querySelector(".uploadImage");
const canvas = document.querySelector(".wpic");
const downloadBtn = document.querySelector(".fbutton");

const ctx = canvas.getContext("2d");
let sourceImg = null;

// 点击容器唤起文件选择
uploadBox.addEventListener("click", () => {
fileInput.click();
})

// 监听图片上传
fileInput.addEventListener("change", (e) => {
const file = e.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = (res) => {
sourceImg = new Image();
sourceImg.src = res.target.result;
sourceImg.onload = drawImageWithWatermark;
}
reader.readAsDataURL(file);
})

// 绘制图片+右下角水印(修复文字截断、拼写错误)
const drawImageWithWatermark = () => {
canvas.width = sourceImg.width;
canvas.height = sourceImg.height;
ctx.drawImage(sourceImg, 0, 0);

const watermarkText = "xvit";
const fontSize = 60;
// 白色半透明水印,深色图清晰可见
ctx.fillStyle = "rgba(255, 255, 255, 0.6)";
ctx.font = `${fontSize}px Arial`;

const padding = 30;
const textWidth = ctx.measureText(watermarkText).width;
// 修复拼写错误 width
const x = canvas.width - textWidth - padding;
// 文字向上偏移,防止贴底被截断
const y = canvas.height - padding;
ctx.fillText(watermarkText, x, y);

canvas.style.display = "block";
}

// 下载按钮逻辑
downloadBtn.addEventListener("click", () => {
if (!sourceImg) {
alert("请先上传图片!");
return;
}
const link = document.createElement("a");
link.href = canvas.toDataURL("image/png");
link.download = `${Date.now()}.png`;
link.click();
})

总结

通过上面的代码,我们就可以实现一个简单的图片加水印的功能了。当然,这只是一个简单的示例,实际应用中可能需要更复杂的逻辑和样式。但是,通过这个示例,我们可以了解到如何使用canvas来实现图片加水印的功能。当然如果你要开发的是更加专业化的话,我更推荐使用前后端结合的方式去处理,前端读取到文件之后通过接口传给后端去处理,当处理完毕之后再返回给前端,这样就可以避免在前端处理图片带来的性能问题。同时也可以按照权限控制用户是否可以上传图片,以及上传图片的大小等。

样图

前端实现图片加水印1

OLDER > < NEWER