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;
|
我们先获取到所有的需要的框架的元素,这里我们获取到了uploadBox、fileInput、canvas和downloadBtn。后面的ctx是canvas的上下文,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; 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标签,然后设置href为canvas的toDataURL,然后设置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; 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来实现图片加水印的功能。当然如果你要开发的是更加专业化的话,我更推荐使用前后端结合的方式去处理,前端读取到文件之后通过接口传给后端去处理,当处理完毕之后再返回给前端,这样就可以避免在前端处理图片带来的性能问题。同时也可以按照权限控制用户是否可以上传图片,以及上传图片的大小等。
样图
