JavaScript實(shí)現(xiàn)為圖片添加水印的方法詳解
背景
在很多地方,我們都可以看到,上傳圖片的時候,圖片都會被加上默認(rèn)的水印,水印的作用主要體現(xiàn)在以下幾個方面:
- 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)識來源:在社交媒體平臺上分享照片時,添加水印可以幫助其他用戶識別出照片的來源和作者。
- 5.識別真?zhèn)危簩τ谝恍┲匾恼掌蜃C件,如證書或合同等,加上水印可以幫助識別真?zhèn)危乐箓卧旌痛鄹摹?/li>
因此,我們在個人網(wǎng)站進(jìn)行圖片操作時,也可以給它加上自己獨(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è)置crossOrigin
屬性(img.setAttribute("crossOrigin",'Anonymous')
),不然會出現(xiàn)以下錯誤:
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
大概意思是 canvas 無法執(zhí)行 toDataURL 方法:污染的畫布無法輸出。受限于 CORS 策略,會存在跨域問題,雖然可以使用圖像(比如 append 到頁面上)但是繪制到畫布上會污染畫布,一旦一個畫布被污染,就無法提取畫布的數(shù)據(jù),比如無法使用使用畫布 toBlob(),toDataURL(),或 getImageData()方法;當(dāng)使用這些方法的時候 會拋出一個安全錯誤。
我們這里可以分為文字水印和圖片水印兩種:
文字水印
添加文字水印的大致步驟如下:
1、生成一個新的 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這兩個方法來繪制文字,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)文章
淺談JavaScript中等號、雙等號、 三等號的區(qū)別
這篇文章主要介紹了淺談JavaScript中等號、雙等號、 三等號的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08mock.js模擬數(shù)據(jù)實(shí)現(xiàn)前后端分離
這篇文章主要為大家詳細(xì)介紹了mock.js模擬數(shù)據(jù)實(shí)現(xiàn)前后端分離,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07微信小程序網(wǎng)絡(luò)層封裝的實(shí)現(xiàn)(promise, 登錄鎖)
這篇文章主要介紹了微信小程序網(wǎng)絡(luò)層封裝(promise, 登錄鎖),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05基于js與flash實(shí)現(xiàn)的網(wǎng)站flv視頻播放插件代碼
這篇文章主要介紹了基于js與flash實(shí)現(xiàn)的網(wǎng)站flv視頻播放插件代碼,該功能在很多網(wǎng)站上都有著廣泛的應(yīng)用,本文以實(shí)例形式對其進(jìn)行介紹,需要的朋友可以參考下2014-10-10使用bootstraptable插件實(shí)現(xiàn)表格記錄的查詢、分頁、排序操作
這篇文章主要介紹了 使用bootstraptable插件實(shí)現(xiàn)表格記錄的查詢、分頁、排序操作,需要的朋友可以參考下2017-08-08