JavaScript實(shí)現(xiàn)為圖片添加水印的方法詳解
背景
在很多地方,我們都可以看到,上傳圖片的時(shí)候,圖片都會被加上默認(rèn)的水印,水印的作用主要體現(xiàn)在以下幾個(gè)方面:
- 1.版權(quán)保護(hù):在商業(yè)用途的照片中添加水印可以幫助保護(hù)作者的版權(quán),防止他人未經(jīng)授權(quán)使用照片。
- 2.品牌推廣:將商業(yè)品牌、商標(biāo)或公司標(biāo)志添加到照片中,可以幫助提高品牌知名度和曝光率。
- 3.防止盜版:添加水印可以防止盜版和未經(jīng)授權(quán)的使用,因?yàn)樗黠@表明該照片的版權(quán)歸原作者所有。
- 4.標(biāo)識來源:在社交媒體平臺上分享照片時(shí),添加水印可以幫助其他用戶識別出照片的來源和作者。
- 5.識別真?zhèn)危簩τ谝恍┲匾恼掌蜃C件,如證書或合同等,加上水印可以幫助識別真?zhèn)?,防止偽造和篡改?/li>
因此,我們在個(gè)人網(wǎng)站進(jìn)行圖片操作時(shí),也可以給它加上自己獨(dú)特的水印,那么作為一名前端開發(fā),我們該如何實(shí)現(xiàn)給圖片加上水印呢?
實(shí)現(xiàn)
對圖片進(jìn)行處理,我們的首選當(dāng)然是canvas啦,使用canvas我們可以便捷地對圖片進(jìn)行操作,我們需要操作的圖片主要分為以下兩種:
- 1、本地上傳的圖片
- 2、線上鏈接圖片
file 轉(zhuǎn) base64
對于本地上傳的圖片,我們需要先將其轉(zhuǎn)換成 base64 再進(jìn)行后續(xù)處理:
我們可以通過FileReader來獲取圖片的 base64,F(xiàn)ileReader 是一種異步讀取文件機(jī)制。
FileReader 提供了如下方法:
- readAsArrayBuffer(file):按字節(jié)讀取文件內(nèi)容,結(jié)果用 ArrayBuffer 對象表示
- readAsBinaryString(file):按字節(jié)讀取文件內(nèi)容,結(jié)果為文件的二進(jìn)制串
- readAsDataURL(file):讀取文件內(nèi)容,結(jié)果用 data:url 的字符串形式表示
- readAsText(file,encoding):按字符讀取文件內(nèi)容,結(jié)果用字符串形式表示
- 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 給圖片加水印
使用在線圖片鏈接的時(shí)候需要注意給圖片設(shè)置crossOrigin屬性(img.setAttribute("crossOrigin",'Anonymous')),不然會出現(xiàn)以下錯(cuò)誤:
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
大概意思是 canvas 無法執(zhí)行 toDataURL 方法:污染的畫布無法輸出。受限于 CORS 策略,會存在跨域問題,雖然可以使用圖像(比如 append 到頁面上)但是繪制到畫布上會污染畫布,一旦一個(gè)畫布被污染,就無法提取畫布的數(shù)據(jù),比如無法使用使用畫布 toBlob(),toDataURL(),或 getImageData()方法;當(dāng)使用這些方法的時(shí)候 會拋出一個(gè)安全錯(cuò)誤。
我們這里可以分為文字水印和圖片水印兩種:
文字水印
添加文字水印的大致步驟如下:
1、生成一個(gè)新的 canvas 畫布;
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;2、將現(xiàn)有需要添加水印的圖片繪制到畫布上;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);3、繪制需要添加的水印文本并設(shè)置樣式。
我們可以使用fillText和strokeText這兩個(gè)方法來繪制文字,fillText繪制的是默認(rèn)的普通實(shí)線文本,strokeText繪制的是描邊文本,這里我使用了strokeText來進(jìn)行水印文本繪制。
完整代碼如下:
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:默認(rèn),文本在指定的位置開始。
end:文本在指定的位置結(jié)束。
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"));
};
});
}效果如下圖,左邊為原圖,右邊為加了文字水印的圖片:

圖片水印
//圖片轉(zhuǎn)為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實(shí)現(xiàn)為圖片添加水印的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript圖片添加水印的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于layui的下拉搜索框異步加載數(shù)據(jù)的解決方法
今天小編就為大家分享一篇關(guān)于layui的下拉搜索框異步加載數(shù)據(jù)的解決方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
Javascript排序算法之計(jì)數(shù)排序的實(shí)例
計(jì)數(shù)排序是一種高效的線性排序,它通過計(jì)算一個(gè)集合中元素楚翔的次數(shù)來確定集合如何排列,計(jì)數(shù)排序不需要進(jìn)行數(shù)據(jù)的比較,所有他的運(yùn)行效率前面介紹的都高2014-04-04
用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼
這篇文章主要介紹了用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼,有時(shí)候我們需要判斷是否為360瀏覽器,包括百度聯(lián)盟后臺就有這樣的提示需要的朋友可以參考下2015-01-01
JS實(shí)現(xiàn)點(diǎn)擊圖片放大縮小及拖拽功能
本文使用 vue創(chuàng)建一個(gè)可拖拽和縮放的圖片查看器組件,該組件不僅可以展示圖片,還支持用戶通過鼠標(biāo)拖動和縮放來查看細(xì)節(jié),本文將介紹如何封裝一個(gè)簡單的圖片拖拽與縮放組件,感興趣的小伙伴跟著小編一起來看看吧2025-02-02
微信小程序點(diǎn)擊頂部導(dǎo)航欄切換樣式代碼實(shí)例
這篇文章主要介紹了微信小程序點(diǎn)擊頂部導(dǎo)航欄切換樣式代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

