本文首發(fā)于公眾號(hào):水很大
做這個(gè)小Demo是因?yàn)榻?jīng)城缮В看見(jiàn)社交平臺(tái)的用戶上傳的圖片是自動(dòng)添加了水印的。我對(duì)這個(gè)自動(dòng)添加水印的功能比較好奇格二,想探究一下究竟是怎么實(shí)現(xiàn)這個(gè)功能的劈彪。
閱讀本文前您需要對(duì)canvas有一定的了解。
01 HTML結(jié)構(gòu)
因?yàn)槭且粋€(gè)簡(jiǎn)單的Demo,所以HTML只保留最核心的部分顶猜,未添加CSS沧奴。
<main>
<div>
<form id="upload-img">
<input type="file" value="上傳" id="select-img" name="select-img" style="display: none" >
<label for="select-img" class="select-label-img">選擇圖片</label>
</form>
<canvas id="imageCanvas" ></canvas>
</div>
</main>
</body>
canvas標(biāo)簽是用來(lái)展示添加了文字以后的照片。這里也可以將canvas標(biāo)簽轉(zhuǎn)換成img標(biāo)簽长窄。
02 分析
(1)input標(biāo)簽選擇文件后怎樣才能知道選擇已經(jīng)結(jié)束?
選擇文件的過(guò)程是:點(diǎn)擊按鈕滔吠,通過(guò)彈出的文件選擇器選擇文件,再點(diǎn)擊文件選擇器的確認(rèn)或者取消按鈕挠日。當(dāng)點(diǎn)擊確認(rèn)按鈕已經(jīng)代表文件選擇完畢疮绷,需要進(jìn)行下一步的操作。但是問(wèn)題來(lái)了:怎樣才能知道用戶點(diǎn)擊了確認(rèn)按鈕肆资?目前我做不到對(duì)這個(gè)按鈕的監(jiān)聽(tīng),所以考慮其他的方法矗愧。只能從input標(biāo)簽著手考慮:通過(guò)查閱資料得知input標(biāo)簽有一個(gè)change事件,只要通過(guò)input標(biāo)簽得到的數(shù)據(jù)發(fā)生變化就會(huì)觸發(fā)change事件,所以現(xiàn)在就從change事件入手:
document.getElementById('upload-img').querySelector('input').addEventListener('change',(e)=>{
});
到這里就做到了對(duì)用戶進(jìn)行的操作進(jìn)行監(jiān)聽(tīng)灶芝。
(2)怎樣獲取上傳的文件郑原?
input標(biāo)簽有一個(gè)files屬性,通過(guò)這個(gè)屬性可以得到上傳文件的數(shù)據(jù)唉韭。
this.imgFrom = document.getElementById("select-img");
this.files = this.imgFrom.files[0];
為什么是files[0]?
這是因?yàn)橹贿x擇了一個(gè)文件。this.imgFrom.files是一個(gè)FileList對(duì)象犯犁,因?yàn)樽鲞@個(gè)Demo的時(shí)候只上傳了一個(gè)文件,所以是files[0]属愤。
(3)驗(yàn)證文件類型
因?yàn)樾枰幚淼氖菆D片格式的文件,所以像pdf、mp3等格式的文件是不能被上傳的酸役,怎么辦住诸?
this.imgFrom.files[0]可以得到文件的type屬性,通過(guò)這個(gè)type屬性即可判斷。
let typeReg = new RegExp('png|jpg|jpeg')
if(typeReg.test(type) == false) {
//if成立則不是圖片
}
(4)怎樣讀取文件?
到目前為止只是通過(guò)input標(biāo)簽得到了文件,但是并沒(méi)有拿到文件的數(shù)據(jù)涣澡。所以現(xiàn)在要做的就是怎樣拿到上傳文件的數(shù)據(jù)贱呐。
let reader = new FileReader();
reader.readAsDataURL(this.files);
簡(jiǎn)單來(lái)講FileReader對(duì)象用于將文件轉(zhuǎn)換為二進(jìn)制字符串,而readAsDataURL則會(huì)讀取指定的Blob或者File對(duì)象(解釋來(lái)源于MDN)。當(dāng)讀取完成以后會(huì)有一個(gè)onload事件被觸發(fā):
reader.onload = function(e){
}
函數(shù)表達(dá)式里面的e是一個(gè)ProgressEvent入桂。對(duì)ProgressEvent只需知道里面存放著上傳圖片的地址奄薇。
(5)圖片地址有了,怎樣將上傳的圖片構(gòu)造出來(lái)?
let img = new Image();
reader.onload = function(e){
img.src = e.target.result;
}
new Image()相當(dāng)于
document.createElement('img')
(6)給圖片添加水印
通常將圖片放在canvas上面可能是這樣操作的:
let ctx=canvasId.getContext("2d");
ctx.drawImage(img,0,0)
但是現(xiàn)在需要將文字放到圖片上面。所以應(yīng)該先按照上面的方法在畫布上繪制圖片然后再繪制文字(這個(gè)順序是不能顛倒的)抗愁。
怎樣繪制文字?
ctx.font = "40px xxx";//xxx為字體,若不設(shè)置像素大小可能無(wú)效
ctx.fillText("Hello world", 0, 0);
執(zhí)行上面代碼可能在畫布中看不見(jiàn)文字,之所以這樣是因?yàn)樵诶L制文本時(shí)canvas有一個(gè)繪制當(dāng)前文本的基線屬性textBaseline馁蒂。textBaseline默認(rèn)值為alphabetic。繪制的水印圖片如:
Hello world
圖片
這就導(dǎo)致在圖片上顯示不了文字蜘腌。
為了解決這個(gè)問(wèn)題就要顯示的設(shè)置textBaseline的值沫屡。當(dāng)文字處于左上、右上時(shí)需將textBaseline設(shè)置為top撮珠。當(dāng)文字處于左下沮脖、右下時(shí)需將textBaseline設(shè)置為bottom。textBaseline總共可以有六個(gè)值可以選擇芯急,按照項(xiàng)目的需要選擇即可倘潜。
(7)水印圖片地址獲取
canvasId.toDataURL("image/jpeg", 1.0);
03 代碼
完整代碼請(qǐng)?jiān)L問(wèn)公眾號(hào):水很大
本文的主要目的是為了記錄水印功能的實(shí)現(xiàn),故有些細(xì)節(jié)問(wèn)題就沒(méi)有再記錄。