用戶上傳頭像然后截圖的需求很常見,很多做法是把圖像發送到后端,把裁剪后的結果發送給瀏覽器,這種方式會增加處理時延。最近正好學習了HTML5里的canvas,發現它的圖片處理功能比較強大,就打算用canvas提供的API實現純前端的剪切。這里頭關鍵有三步:顯示未經處理的圖片,得到裁剪區域,顯示裁剪后的區域。我們分別討論:
1. 顯示未經處理的圖片
創建一個canvas,用drawImage(img,0,0,canvas.width,canvas.height)就可以。主要這里的img是一個Image類的object, 用new Image創建。
var img = new Image(); img.src = "./beauty.jpg"; img.onload = function(){ cxt1.drawImage(img,0,0,canvas1.width,canvas1.height); //一定要寫在onload回調中,否則看不到圖片 }
2. 得到裁剪區域
用一個position:absolute的div框來選擇裁剪區域,通過javascript提供的方法能得到該div在canvas中所處的位置(x,y),然后用getImageData(srcX,srcY,width,height)得到選擇框中的像素點。 這里需要知道,通過canvas.getBoundingClientRect().left和canvas.getBoundingClientRect().top可以得到canvas相對於瀏覽器視圖的左邊和上邊位置。
3. 顯示裁剪后的區域
這部分是最復雜的。假設getImageData得到了imgData, 需要創建一個canvas2,用canvas2.putImageData(imgData,0,0,canvas2.width,canvas2.height)將選擇框里的像素繪制到這個臨時的canvas2里。然后用canvas2.toDataURL("image/png")將canvas2轉為dataurl類型的圖片。有了dataurl后,就可以正常顯示裁剪后的圖片了。
×××××××××××××××××××××此處為分割線,算法介紹完畢××××××××××××××××××
×××××××××××××××××××××此處為分割線,算法介紹完畢××××××××××××××××××
那么問題來了。為什么不能直接把getImageData得到的imgData通過putImageData繪制到最終要顯示的區域,而非要創建一個臨時的canvas2(不再頁面上顯示)呢?其實這是我想出來的一個折中方案。用putImageData繪制的畫布上,只能按照等比例或者更小比例顯示imgData,如果你想把剪切出來的圖片放大顯示,putImageData是不能支持的(這個結論是我經過測試發現的)。所以為了看到放大后的剪切區域(即使犧牲清晰度),就要用drawImage方法了,而drawImage的第一個參數不能是一堆像素數據,就只能用一個臨時的canvas來作為像素數據和dataurl之間的橋梁了。
最后,上一段測試代碼(可運行):
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .mark{ position:absolute; height:100px; width:100px; left:100px; top:180px; border:1px solid #000; cursor:move; } </style> </head> <body> <canvas id="c1"></canvas> //顯示原圖像 <div class="mark" id="mark"></div> <canvas id="c3"></canvas> //顯示剪切后的圖像 <script> var canvas1 = document.getElementById("c1") var oMark = document.getElementById("mark") var canvas3= document.getElementById("c3") canvas1.height = 300; canvas1.width=300; canvas3.height=100; canvas3.width=100; var cxt1 = canvas1.getContext("2d") var img = new Image(); img.src = "./beauty.jpg"; var srcX = oMark.offsetLeft-canvas1.getBoundingClientRect().left; var srcY = oMark.offsetTop-canvas1.getBoundingClientRect().top; var sWidth = oMark.offsetWidth; var sHeight = oMark.offsetHeight; var canvas2 = document.createElement("canvas") var cxt2=canvas2.getContext("2d") img.onload = function(){ cxt1.drawImage(img,0,0,canvas1.width,canvas1.height); var dataImg = cxt1.getImageData(srcX,srcY,sWidth,sHeight) canvas2.width = sWidth; canvas2.height = sHeight; cxt2.putImageData(dataImg,0,0,0,0,canvas2.width,canvas2.height) var img2 = canvas2.toDataURL("image/png"); var cxt3=canvas3.getContext("2d") var img3 = new Image(); img3.src = img2; img3.onload = function(){ cxt3.drawImage(img3,0,0,canvas3.width,canvas3.height) } } </script> </body> </html>