最近剛接觸到<canvas>標簽,雖然知道它可以對像素進行一些處理,但卻想不出實戰中能做點什么。百度找到了haorooms博客中的一篇文章,還挺實用的,就用自己的理解給他的實例做個注釋方便我這個沙比以后回顧時能快點想起來代碼的作用和這些功能的原理。
注意:使用跟ImageData對象相關的功能的時候,Chrome本地運行會出現跨域的問題,導致JS代碼不生效。有一個解決方法就是把文件丟到Web服務器上,然后通過域名去訪問這個網頁就沒問題了,但這得會搭建服務器,還得開着服務器(虛擬機啊,實體機啊什么的),還是挺麻煩的。用IE、Edge和Firefox(我電腦就這四個瀏覽器)就不會出現這個問題,代碼能夠正常運行。反正下面的內容要是在本地測試的話別用Chrome打開就好了!
應用1:顏色選擇器(拾色器)
原文的作者稱呼這個功能叫顏色選擇器,我因為有時候會用到FSCapture中的拾色器功能,就感覺效果很相近,都是獲取一個區域中的某個顏色值,還是下意識的叫它拾色器,標記一下,雖然知道不是一種東西。
原理分析:
我們寫一個JS函數綁定畫布上的鼠標指針移動事件(onmousemove),這個函數會獲取當前鼠標的坐標,然后以鼠標的坐標作為ImageData區域的左上角定位點,繪制一個1px的ImageData區域,然后提取這個區域中的顏色值(ImageData.data),得到顏色值之后就可以修改文本和指定區域的背景顏色了。

1 <body> 2 <!-- 顯示在畫布上的圖片 --> 3 <img id="isImg" src = "image/001.png" style="display:none"/> 4 5 <!-- 畫布區域 --> 6 <canvas id="drawEle"> 7 您的瀏覽器不支持該標簽 8 </canvas> 9 10 <!-- 顯示顏色的區域 --> 11 <div id="color"> 12 還沒開始 13 </div> 14 15 <script> 16 //獲取畫布 17 var canvas = document.getElementById("drawEle"); 18 var ctx = canvas.getContext("2d"); 19 //獲取圖像 20 var getImg = document.getElementById("isImg"); 21 //在圖像加載完成后再將圖像繪制到畫布上 22 getImg.onload = function() { 23 ctx.drawImage(getImg,0,0,300,150); 24 } 25 26 //獲取顯示顏色的div區域 27 var color = document.getElementById("color"); 28 29 //創建一個函數,用於獲取鼠標放在元素上面時獲取指定位置像素顏色參數 30 function getColor(event) { 31 //layerX、layerY:鼠標在觸發元素上的坐標 32 var x = event.layerX; 33 var y = event.layerY; 34 //鼠標的坐標為基點,獲取一個1px大小的像素區域 35 var pixel = ctx.getImageData(x, y, 1, 1); 36 //因為只有1px大小的區域,所以data中正好是一組RGBA值 37 var data = pixel.data; 38 //在控制台打印,測試用 39 console.log(data); 40 //將data中分散的RGBA值整合成一個字符串(原本是data[0]對應R,data[1]對應G這樣的格式) 41 var rgbaValue = "rgba("+ data[0] + "," + data[1] + "," + data[2] + "," + data[3] + ")" 42 //設置顯示顏色div區域的背景樣式(修改背景顏色) 43 color.style.background = rgbaValue; 44 //修改顯示顏色div區域中的內容 45 color.textContent = rgbaValue; 46 } 47 48 //將函數綁定在鼠標移動事件(mousemove)上 49 canvas.addEventListener('mousemove',getColor); 50 51 </script> 52 </body>
運行效果如下:
(這個就沒有haorooms加注釋版了,因為我覺得兩個版本都差不多就放一個就行了)
應用2:過濾純色背景
haorooms中這個應用是過濾視頻的純色背景,這里我就先嘗試過濾圖片背景顏色先。
在看到標題的時候我腦中就有這樣一個想法:在使用getImageData獲取到這個元素之后,使用for循環遍歷一遍所有像素,然后用if判斷哪個像素是我們要排除的顏色,然后設置Alpha通道的值為0來隱藏這個像素。既然有自己的想法就先做一做試試看:

1 <head> 2 <meta charset="utf-8" /> 3 <title>過濾純色背景(靈光一現版)</title> 4 <style> 5 body { 6 background-color:#AAAAAA; 7 } 8 canvas { 9 background-color:#CCCCCC; 10 margin:0px 5px; 11 } 12 #newDrawEle { 13 background-image:url(image/002.jpg); 14 background-size:cover; 15 } 16 </style> 17 </head> 18 19 <body> 20 <!-- 顯示在畫布上的圖片 --> 21 <img id="isImg" src = "image/001.png" style="display:none"/> 22 23 <!-- 畫布區域 --> 24 <canvas id="drawEle"> 25 您的瀏覽器不支持該標簽 26 </canvas> 27 28 <!-- 過濾后圖像在的區域 --> 29 <canvas id="newDrawEle"> 30 31 </canvas> 32 33 <script> 34 //獲取畫布1(過濾前的畫布) 35 var canvas1 = document.getElementById("drawEle"); 36 var ctx1 = canvas1.getContext("2d"); 37 38 //獲取畫布2(過濾后的畫布) 39 var canvas2 = document.getElementById("newDrawEle"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //獲取圖像 43 var getImg = document.getElementById("isImg"); 44 //在圖像加載完成后再將圖像繪制到畫布上 45 getImg.onload = function() { 46 ctx1.drawImage(getImg,0,0,300,150); 47 48 //獲取畫布1上的全部區域 49 var zone = ctx1.getImageData(0,0,300,150); 50 51 //遍歷一遍區域中的像素 52 for(var i = 0;i<zone.data.length;i+=4) { 53 //將RGBA值格式化 54 var rgbaValue = "rgba(" + zone.data[i] + "," + zone.data[i+1] + "," + zone.data[i+2] + "," + zone.data[i+3] + ")"; 55 //判斷RGBA值 56 if(rgbaValue == "rgba(0,255,0,255)") { 57 //符合過濾條件就把指定元素的Alpha通道調成0(透明) 58 zone.data[i+3] = 0; 59 } 60 } 61 62 //將畫布1上的區域繪制到畫布2上面 63 ctx2.putImageData(zone,0,0); 64 } 65 </script> 66 </body>
隨便搞了張游戲截圖,然后把背景搞成了純色綠色背景(rgba(0,255,0,255))試試效果:
有沒處理干凈的綠色圍繞在人物身邊,效果並不是很完美。我的身上有種不可思議的光線
目前想到的解決的方法就是增加顏色判定范圍,比如g的值小於多少多少這樣,於是誕生了下面這個版本的代碼(僅修改了for循環中的內容):

1 //遍歷一遍區域中的像素 2 for(var i = 0;i<zone.data.length;i+=4) { 3 //獲取rgb值 4 let r = zone.data[i + 0]; 5 let g = zone.data[i + 1]; 6 let b = zone.data[i + 2]; 7 8 //判斷RGBA值 9 if(g > 240 && r >= 0 && b >= 0) { 10 //符合過濾條件就把指定元素的Alpha通道調成0(透明) 11 zone.data[i+3] = 0; 12 } 13 }
效果如下:
跟之前的比起來,人物周圍綠色的部分是少了許多,再調整精細點。。。我得去學點美術相關的知識才好做咕咕咕。
原理分析:
首先它創建了一個對象名為processor(處理器),所有對視頻的處理的方法等相關的一切都放在processor對象中。
processor中第一個方法:doLoad(進行加載),用於初始化參數,給標簽綁定事件函數。在這個方法中,在js中獲取要進行操作的元素,如<video>標簽、畫布。給<video>標簽的play事件(視頻播放事件)綁定函數,綁定的函數設置視頻繪制到畫布上時的寬度和高度,然后執行一次timerCallback()(計時器回調)方法。
processor中第二個方法:timerCallback(計時器回調)。在里面做判斷,id=video的標簽是否處於暫停播放(this.video.paused)或者(||)停止播放(this.video.ended)的狀態,是則跳出timerCallback(),不執行后面的內容。如果為false,則繼續執行后面的內容。為false,調用一次computeFrame(計算框架)來過濾顏色,然后使用setTimeout()方法來循環調用自身(timerCallback()方法),直到視頻停止播放或者暫停播放為止。
processor中第三個方法:computeFrame(計算框架),該方法用於過濾視頻純色背景。首先繪制id=video中的畫面到畫布1(不做純色過濾的畫布)上,然后創建一個ImageData對象,對象的內容就是畫布1上的內容。創建一個變量計算區域內像素的數量。然后使用for遍歷一遍區域內的所有像素,使用if對像素進行判斷,判斷要過濾的顏色。如果if為true,則將這個像素的alpha通道值改為0(透明)。過濾完畢后將處理后的內容打印到畫布2上。
最終,給<body>元素的onload事件綁定processor.doLoad()方法。
下面是加了注釋的源碼:

1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms過濾視頻純色背景</title> 4 <style> 5 body { 6 background: black; 7 color:#CCCCCC; 8 } 9 div { 10 float: left; 11 border :1px solid #444444; 12 padding:10px; 13 margin: 10px; 14 background:#3B3B3B; 15 } 16 </style> 17 <script> 18 //創建一個processor對象,里面包含所有的要處理的內容 19 let processor = { 20 //processor對象中的第一個方法:doLoad(進行加載) 21 doLoad: function() { 22 //獲取<video>標簽 23 this.video = document.getElementById("video"); 24 //獲取第一個畫布:不做純色過濾的畫布 25 this.c1 = document.getElementById("c1"); 26 this.ctx1 = this.c1.getContext("2d"); 27 //獲取第二個畫布:會做純色過濾的畫布 28 this.c2 = document.getElementById("c2"); 29 this.ctx2 = this.c2.getContext("2d"); 30 //儲存this指向processor內部的狀態(不知道該怎么介紹這個) 31 let self = this; 32 //綁定id="video"對象 33 this.video.addEventListener("play", function() { 34 //設置視頻繪制到畫布上時的寬度和高度 35 self.width = self.video.videoWidth / 2; 36 self.height = self.video.videoHeight / 2; 37 //執行一次timerCallback()函數 38 self.timerCallback(); 39 }, false); //冒泡階段執行 40 }, 41 42 //processor對象中的第二個方法:timerCallback(計時器回調) 43 timerCallback: function() { 44 //判斷video標簽是否處於暫停或者停止播放狀態,是跳出該函數 45 if (this.video.paused || this.video.ended) { 46 return; 47 } 48 //調用computeFrame(計算框架)做顏色過濾 49 this.computeFrame(); 50 //儲存this指向processor內部的狀態(不知道該怎么介紹這個) 51 let self = this; 52 53 setTimeout(function () { 54 self.timerCallback(); 55 }, 0); 56 }, 57 58 //processor對象中的第三個方法:computeFrame(計算框架) 59 computeFrame: function() { 60 //繪制video中的畫面到畫布1(不做純色過濾的畫布)上 61 this.ctx1.drawImage(this.video, 0, 0, this.width, this.height); 62 //創建一個ImageData對象,這個對象的內容等於畫布1上的內容 63 let frame = this.ctx1.getImageData(0, 0, this.width, this.height); 64 //計算區域內像素的數量(除以4是因為一個完整的RGBA值由四個參數組成) 65 let l = frame.data.length / 4; 66 //遍歷一遍區域內的像素 67 for (let i = 0; i < l; i++) { 68 let r = frame.data[i * 4 + 0]; 69 let g = frame.data[i * 4 + 1]; 70 let b = frame.data[i * 4 + 2]; 71 //判斷是否符合過濾顏色條件,是則修改alpha通道參數值 72 if (g > 100 && r > 100 && b < 43) { 73 frame.data[i * 4 + 3] = 0; 74 } 75 } 76 //打印到畫布上 77 this.ctx2.putImageData(frame, 0, 0); 78 return; 79 } 80 }; 81 </script> 82 </head> 83 84 <body onload="processor.doLoad()"> 85 <div> 86 <video id="video" src="video/haorooms.ogv" controls="true"> 87 </video></div> 88 <div> 89 <canvas id="c1" width="160" height="96"></canvas> 90 <canvas id="c2" width="160" height="96"></canvas> 91 </div> 92 </body>
效果如下(視頻還是用人家的視頻,找新的視頻還得設置顏色范圍,我懶_(:з」∠)_):
應用3:圖片灰度和反相顏色
圖片灰度和反相顏色就像下面這樣:
實現這個只需要將RGB值計算后再繪制到畫布上就行了,關鍵就是RGB值的計算公式。
轉換成灰度圖:
R = (R + G + B) /3
G = (R + G + B) /3
B = (R + G + B) /3
將圖片反色:
R = 255 - R
G = 255 - G
B = 255 - B
灰度圖的公式也可以用加權運算平衡,詳細可百度,這里使用相加然后除以3。
haorooms中的例子是用兩個按鈕來控制將圖片反色或者轉換成灰度圖,但並不會將圖片還原。這里我們先給haorooms中的例子加上注釋,然后自己再寫一個像上面展示的什么是灰度圖什么是反色圖那樣,同時顯示原圖、灰度圖、反色圖。
原理分析:
首先先實例化一個Image對象,然后給它指定一張圖片。
然后創建一個方法draw(),這個方法調用的時候需要傳遞一個img類型的參數。
在draw()內,我們引用畫布,將圖像繪制到畫布上,然后把原圖隱藏(display:none)。創建ImageData對象,內容為畫布上的所有像素。
在draw()內創建一個函數invert(),這個函數內使用for循環將所有像素的RGB值轉換成反色值,然后繪制到畫布上。在draw()內創建第二個函數grayscale(),這個函數內使用for循環將RGB值轉換成灰度值,然后繪制到畫布上。
最后將這兩個函數綁定到按鈕的click事件(點擊事件)上

1 <body> 2 <canvas id="canvas" width="600" height="300"></canvas> 3 <br> 4 <input type="button" value="灰度" id="grayscalebtn"> 5 <input type="button" id="invertbtn" value="反向"> 6 7 <script> 8 //創建一個img對象 9 var img = new Image(); 10 //給img對象指定圖片 11 img.src = 'image/001.png'; 12 //給img對象的onload事件綁定一個函數,這個函數調用方法draw() 13 img.onload = function () { 14 //調用draw(),傳遞一個img參數 15 draw(this); 16 }; 17 18 //創建方法draw 19 function draw(img) { 20 //引用畫布 21 var canvas = document.getElementById('canvas'); 22 var ctx = canvas.getContext('2d'); 23 //繪制圖像 24 ctx.drawImage(img, 0, 0); 25 //隱藏原圖 26 img.style.display = 'none'; 27 //創建ImageData對象,獲取畫布上的全部內容 28 var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 29 //獲取ImageData中的data集合 30 var data = imageData.data; 31 32 //轉換成反色的方法 33 var invert = function () { 34 for (var i = 0; i < data.length; i += 4) { 35 data[i] = 225 - data[i]; // red 36 data[i + 1] = 225 - data[i + 1]; // green 37 data[i + 2] = 225 - data[i + 2]; // blue 38 } 39 //繪制圖像 40 ctx.putImageData(imageData, 0, 0); 41 }; 42 43 //轉換成灰度的方法 44 var grayscale = function () { 45 for (var i = 0; i < data.length; i += 4) { 46 var avg = (data[i] + data[i + 1] + data[i + 2]) / 3; 47 data[i] = avg; // red 48 data[i + 1] = avg; // green 49 data[i + 2] = avg; // blue 50 } 51 //繪制圖像 52 ctx.putImageData(imageData, 0, 0); 53 }; 54 55 //獲取反色按鈕 56 var invertbtn = document.getElementById('invertbtn'); 57 //綁定點擊事件 58 invertbtn.addEventListener('click', invert); 59 //獲取灰度按鈕 60 var grayscalebtn = document.getElementById('grayscalebtn'); 61 //綁定點擊事件 62 grayscalebtn.addEventListener('click', grayscale); 63 } 64 </script> 65 </body>
運行效果如下:
那接下來我們自己做一個三個同時顯示的版本練練手吧:

1 <body> 2 <!-- 原圖 --> 3 <div> 4 <img id="isImg" src = "image/001.jpg" /> 5 </div> 6 <!-- 灰度圖的畫布 --> 7 <canvas id="gray-scale"> 8 您的瀏覽器不支持該標簽 9 </canvas> 10 <!-- 反色圖的畫布 --> 11 <canvas id="invert-color"> 12 您的瀏覽器不支持該標簽 13 </canvas> 14 15 <script> 16 //獲取圖像 17 var getImg = document.getElementById("isImg"); 18 19 //在圖像加載完成后再將圖像繪制到畫布上 20 getImg.onload = function() { 21 draw(this); 22 } 23 24 //繪制圖像的函數 25 function draw(img) { 26 27 //獲取灰度圖的畫布 28 var c1 = document.getElementById("gray-scale"); 29 var ctx1 = c1.getContext("2d"); 30 31 //獲取反色圖的畫布 32 var c2 = document.getElementById("invert-color"); 33 var ctx2 = c2.getContext("2d"); 34 35 //設置兩個畫布繪圖區域的大小 36 c1.width = "100"; 37 c1.height = "100"; 38 c2.width = "100"; 39 c2.height = "100"; 40 41 //繪制圖像到兩個畫布上 42 ctx1.drawImage(getImg,0,0,100,100); 43 ctx2.drawImage(getImg,0,0,100,100); 44 45 //獲取灰度圖中的內容 46 var inGrayscale = ctx1.getImageData(0,0,c1.width,c1.height); 47 //遍歷灰度圖中的內容 48 for(var i = 0;i<inGrayscale.data.length;i+=4) { 49 //計算轉換成灰度值后的顏色值 50 var avg = (inGrayscale.data[i+0] + inGrayscale.data[i+1] + inGrayscale.data[i+2]) /3; 51 //設置成灰度圖 52 inGrayscale.data[i + 0] = avg; 53 inGrayscale.data[i + 1] = avg; 54 inGrayscale.data[i + 2] = avg; 55 } 56 //繪制到灰度圖的畫布上 57 ctx1.putImageData(inGrayscale,0,0); 58 59 //獲取反色圖中的內容 60 var inInvertcolor = ctx2.getImageData(0,0,c2.width,c2.height); 61 //遍歷反色圖中的內容 62 for(var i = 0;i<inInvertcolor.data.length;i+=4) { 63 //進行反色處理 64 inInvertcolor.data[i + 0] = 255 - inInvertcolor.data[i + 0]; 65 inInvertcolor.data[i + 1] = 255 - inInvertcolor.data[i + 1]; 66 inInvertcolor.data[i + 2] = 255 - inInvertcolor.data[i + 2]; 67 } 68 //繪制到反色圖的畫布上 69 ctx2.putImageData(inInvertcolor,0,0); 70 } 71 72 </script> 73 </body>
最終運行效果如下:
(沒錯開始的圖片就是我自己做版本的效果噠!)
應用4:縮放和反鋸齒
縮放的做法其實很好猜到,截取畫布a上的一部分,在畫布b上繪制a的一部分時放大顯示。
反鋸齒其實不太好猜。實際上<canvas>中有自帶的屬性可以設置是否開啟反鋸齒。imageSmoothingEnable屬性,設置為true表示圖片平滑(默認值),為false表示圖片不平滑。
原理分析:
首先先實例化一個Image對象,然后給它指定一張圖片。
然后創建一個方法draw(),這個方法調用的時候需要傳遞一個img類型的參數。
在draw()內,我們引用原圖畫布,將圖像繪制到原圖畫布上,然后把原圖隱藏(display:none)。引用顯示縮放內容區域,還有控制反鋸齒是否啟動的開關。
在draw()內創建一個函數toggleSmoothing()用於進行反鋸齒的設置。這個函數中將反鋸齒開關的布爾值設置成imageSmoothingEnable的值,為了兼容性還要給每種內核的imageSmoothingEnable都賦上同樣的值。然后給反鋸齒開關綁定toggleSmoothing()函數。
在draw()內創建一個函數zoom()用於坐標縮放。先獲取鼠標的坐標,然后將鼠標的坐標作為裁剪區域的中心點(做減法運算,讓左上角的位置變成裁剪區域中心,用Math.abs取絕對值保證不出現負數)。然后把zoom()函數綁定在原圖畫布的mousemove(鼠標移動事件)上。

1 <body> 2 <canvas id="canvas" width="300" height="227"></canvas> 3 <canvas id="zoom" width="300" height="227"></canvas> 4 <div> 5 <label for="smoothbtn"> 6 <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn"> 7 Enable image smoothing 8 </label> 9 </div> 10 <script type="text/javascript"> 11 //創建圖片對象 12 var img = new Image(); 13 //給img對象指定圖片 14 img.src = 'image/001.jpg'; 15 //給img對象的onload事件綁定一個函數,這個函數調用方法draw() 16 img.onload = function () { 17 //調用draw(),傳遞一個img參數 18 draw(this); 19 }; 20 21 //創建方法draw 22 function draw(img) { 23 //引用原圖像的畫布 24 var canvas = document.getElementById('canvas'); 25 var ctx = canvas.getContext('2d'); 26 //繪制圖像 27 ctx.drawImage(img, 0, 0); 28 //隱藏原圖 29 img.style.display = 'none'; 30 //引用顯示縮放內容的區域 31 var zoomctx = document.getElementById('zoom').getContext('2d'); 32 //引用反鋸齒開關 33 var smoothbtn = document.getElementById('smoothbtn'); 34 35 //對顯示縮放區域的內容做反鋸齒處理 36 var toggleSmoothing = function (event) { 37 //imageSmoothingEnabled:設置圖片是否平滑,true表示平滑,false表示不平滑 38 zoomctx.imageSmoothingEnabled = this.checked; 39 //下面是對不同瀏覽器的兼容imageSmoothingEnable設置,效果一樣 40 //moz:火狐內核。-moz代表火狐的私有屬性 41 zoomctx.mozImageSmoothingEnabled = this.checked; 42 //webkit:webkit內核,常用於safari和chrome。-webkit代表Safari和chrome的私有屬性 43 zoomctx.webkitImageSmoothingEnabled = this.checked; 44 //ms:IE內核。-ms代表IE的私有屬性 45 zoomctx.msImageSmoothingEnabled = this.checked; 46 }; 47 //給反鋸齒開關的change(input標簽發生變化時觸發)事件綁定反鋸齒處理的函數 48 smoothbtn.addEventListener('change', toggleSmoothing); 49 50 //縮放功能的函數 51 var zoom = function (event) { 52 //獲取鼠標坐標 53 var x = event.layerX; 54 var y = event.layerY; 55 /* 在顯示縮放區域的位置上繪制圖像 56 裁剪區域左上角的點為鼠標當前坐標-5(Math.abs) 57 裁剪范圍為10x10 58 在顯示縮放區域的畫布上左上角坐標為0,0 59 大小為200x200 60 */ 61 zoomctx.drawImage(canvas, 62 Math.abs(x - 5), 63 Math.abs(y - 5), 64 10, 10, 65 0, 0, 66 200, 200); 67 }; 68 //給顯示原圖像的畫布的mousemove(鼠標移動事件)綁定縮放功能的函數 69 canvas.addEventListener('mousemove', zoom); 70 } 71 </script> 72 </body>
運行效果如下:
接下來就是自己動手環節了

1 <body> 2 <!-- 顯示在畫布上的圖片 --> 3 <img id="isImg" src = "image/001.jpg" style="display:none"/> 4 5 <!-- 顯示圖片的畫布區域 --> 6 <canvas id="drawEle"> 7 您的瀏覽器不支持該標簽 8 </canvas> 9 10 <!-- 顯示放大內容的畫布區域 --> 11 <canvas id="zoom"> 12 您的瀏覽器不支持該標簽 13 </canvas> 14 15 <!-- 反鋸齒開關 --> 16 <input type="checkbox" name="smoothBtn" id="smoothBtn" checked='checked'> 17 反鋸齒開關 18 </input> 19 20 <script> 21 //獲取圖像 22 var getImg = document.getElementById("isImg"); 23 24 //在圖像加載完成后執行一系列操作 25 getImg.onload = function() { 26 draw(getImg); 27 } 28 29 //實現縮放功能和反鋸齒功能的函數draw() 30 function draw(img) { 31 //獲取圖像處理前的畫布 32 var canvas1 = document.getElementById("drawEle"); 33 var ctx1 = canvas1.getContext("2d"); 34 35 //將圖像繪制到圖像處理前的畫布上 36 ctx1.drawImage(getImg,0,0,300,150); 37 38 //獲取縮放區域的畫布 39 var canvas2 = document.getElementById("zoom"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //縮放功能的函數 43 var zoom = function(event) { 44 //獲取鼠標坐標 45 var x = event.layerX; 46 var y = event.layerY; 47 /* 在顯示縮放區域上繪制圖像 48 裁剪區域左上角的點坐標偏移,為了讓裁剪中心點處於裁剪區域的中間(用Math.abs求絕對值,防止出現負數) 49 裁剪范圍為10x10 50 顯示在縮放畫布上左上角坐標為0,0 51 大小為150x150 52 */ 53 ctx2.drawImage(canvas1, Math.abs(x - 5), Math.abs(y - 5), 10, 10, 0, 0, 150, 150); 54 } 55 //將縮放功能的函數綁定在圖像處理前畫布的mousemove(鼠標移動事件)上 56 canvas1.addEventListener("mousemove",zoom); 57 58 //獲取反鋸齒開關 59 var smoothBtn = document.getElementById("smoothBtn"); 60 61 //對顯示縮放區域中的畫面進行反鋸齒處理 62 var toggleSmoothing = function(event) { 63 //設置通用的imageSmoothingEnabled屬性,true為平滑,false為不平滑 64 ctx2.imageSmoothingEnabled = this.checked; 65 //設置兼容火狐的imageSmoothingEnabled屬性 66 ctx2.mozImageSmoothingEnabled = this.checked; 67 //設置兼容Safari和Chrome的imageSmoothingEnabled屬性 68 ctx2.webkitImageSmoothingEnabled = this.checked; 69 //設置兼容IE的imageSmoothingEnabled屬性 70 ctx2.msImageSmoothingEnabled = this.checked; 71 } 72 //將反鋸齒開關功能的函數綁定在反鋸齒開關的change(表單發生變化)事件上 73 smoothBtn.addEventListener("change",toggleSmoothing); 74 } 75 </script> 76 </body>
應用5:在canvas中手繪並下載圖片
在體驗了haorooms中實例的效果后,突然有了靈感,就先按照自己的想法做一遍試試看效果。
關於下載畫布上內容這個功能,我們會用到canvas中的一個方法:toDataURL(),該方法返回一個包含畫布上內容的data URL,可以傳遞參數指定導出圖片類型,默認格式為png,分辨率為96dpi。我們就用這個方法來下載我們繪制到畫布上的內容。
原理分析:
首先先獲取畫布,然后獲取下載用的a標簽。創建一個變量brushesDown,初始值為false。這個變量是用來判斷畫筆是處於激活(按下鼠標左鍵)還是禁用(抬起鼠標左鍵)的狀態。
接下來定義一個函數burshesMove(),在這個函數中先獲取鼠標的x軸和y軸坐標,然后創建一個ImageData對象(createImageData())作為筆刷的樣式,用遍歷設置一遍這個對象中的像素為#000000(黑色)。做一個if判斷,判斷筆刷是否激活(if(brushesDown)),激活則使用putImageData()函數在畫布上鼠標指定的位置繪制內容。最后更新下載用的a標簽的鏈接。
將burshesMove()函數綁定在畫布的mousemove(鼠標移動)事件上。
給畫布的mousedown(鼠標按下事件)設置一個函數:當鼠標按下時,brshesDown設置為true。
給畫布的mouseup(鼠標抬起事件)設置一個函數:當鼠標抬起時,brshesUp設置為false。

1 <body> 2 <canvas id="drawEle"> 3 您的瀏覽器不支持該標簽 4 </canvas> 5 <a id="drawDownload" download="在canvas中手繪並下載圖片.png">下載圖片</a> 6 <script> 7 //引用畫布 8 var canvas = document.getElementById("drawEle"); 9 var ctx = canvas.getContext("2d"); 10 11 //獲取a標簽 12 var imageDownload = document.getElementById("drawDownload"); 13 14 //判斷畫筆的狀態,是按下去還是抬起來 15 var brushesDown = false; 16 //鼠標在畫布上繪制圖形的方法 17 function brushesMove(event){ 18 //獲取鼠標坐標 19 var x = event.pageX; 20 var y = event.pageY; 21 22 //創建筆刷(偽),筆刷大小為3x3 23 var brushes = ctx.createImageData(3,3); 24 //設置筆刷的樣式 25 for(var i = 0;i<brushes.data.length;i+=4) { 26 brushes.data[i + 0] = 0; 27 brushes.data[i + 1] = 0; 28 brushes.data[i + 2] = 0; 29 brushes.data[i + 3] = 255; 30 } 31 //判斷畫筆有沒有激活 32 if(brushesDown) { 33 //在畫布上繪制內容 34 ctx.putImageData(brushes,Math.abs(x-10),Math.abs(y-10)); 35 } 36 37 //更新下載鏈接URL 38 imageDownload.href = canvas.toDataURL(); 39 40 } 41 42 //綁定在畫布上繪制圖形的方法在畫布的mousemove(鼠標移動事件)上 43 canvas.addEventListener("mousemove",brushesMove); 44 45 //鼠標按下時激活畫筆 46 canvas.onmousedown = function() { 47 brushesDown = true; 48 } 49 50 //鼠標抬起時禁用畫筆 51 canvas.onmouseup = function() { 52 brushesDown = false; 53 } 54 </script> 55 </body>
運行效果如下:
下載的圖片:
定位鼠標的時候有對坐標進行減法運算,是因為我在實際測試的時候,如果直接使用鼠標坐標,返回的坐標是鼠標的中心點,我希望筆刷的中心點在鼠標箭頭上,所以就做了個減法運算。
接下來我們來分析haorooms中的例子吧:
(這個例子我下載在本地運行的時候是會顯示兩個a標簽的,第二個a標簽是用js生成的。在它網站上運行的例子沒有兩個a標簽,本地就有。)

1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms_canvas應用</title> 4 <style type="text/css"> 5 /* 設置畫布樣式 */ 6 canvas { 7 /* 設置畫布的背景顏色為桃紅色 */ 8 background: peachpuff; 9 } 10 11 /* 設置有download屬性的a標簽 */ 12 a[download]{ 13 display: block; 14 width: 280px; 15 background: #369; 16 color: #fff; 17 text-decoration: none; 18 padding: 5px 10px; 19 } 20 21 /* 設置有download屬性的a標簽和 22 h1標簽中的span標簽 */ 23 a[download], h1 span { 24 opacity: 0; 25 } 26 27 /* 設置painted類中 有download屬性的a標簽和 28 painted類中 h1標簽下的 span標簽*/ 29 .painted a[download], .painted h1 span { 30 opacity: 1; 31 transition: 0.5s; 32 } 33 </style> 34 </head> 35 36 <body class="painted"> 37 38 <a href="http://resource.haorooms.com/uploads/demo/canvas/drawanddownload.html#" download="haorooms.png">下載圖片</a> 39 <canvas width="300" height="300"></canvas> 40 41 <script> 42 //給瀏覽器窗口的load(加載完成)事件綁定函數 43 window.addEventListener('load', function(ev) { 44 //使用css選擇器查找文檔中第一個img元素(可是這個變量后面沒用上啊= =) 45 var sourceimage = document.querySelector('img'); 46 //獲取文檔中第一個canvas標簽 47 var canvas = document.querySelector('canvas'); 48 //獲取文檔中第一個a標簽 49 var link = document.querySelector('a'); 50 //獲取畫布的繪圖環境 51 var context = canvas.getContext('2d'); 52 //聲明五個變量並賦值:mouseX(鼠標x軸坐標)、mouseY(鼠標y軸坐標)、width(畫布寬度)、height(畫布高度)、mousedown(鼠標是否按下) 53 var mouseX = 0, mouseY = 0, 54 width = 300, height = 300, 55 mousedown = false; 56 //設置畫布的寬度和高度 57 canvas.width = width; 58 canvas.height = height; 59 //設置畫筆的顏色 60 context.fillStyle = 'hotpink'; 61 //定義在畫布上鼠標按下(mousedown)后繪制線條的方法 62 function draw(ev) { 63 //判斷mousedown(鼠標是否按下)是否為true 64 if (mousedown) { 65 //獲取鼠標的坐標,作為畫筆的中心點 66 var x = ev.layerX; 67 var y = ev.layerY; 68 //修改中心點的位置,原本鼠標的中心點在鼠標正中間,這樣計算后在Chrome中是在鼠標左上角,但在Firefox中在鼠標下方(個人測試) 69 x = (Math.ceil(x / 10) * 10) - 10; 70 y = (Math.ceil(y / 5) * 5) - 5; 71 //繪制內容到畫布上,連續拖拽就可以形成線條。這個畫筆寬10px,高5px。 72 context.fillRect(x, y, 10, 5); 73 } 74 } 75 //創建一個a標簽 76 var link = document.createElement('a'); 77 //設置a標簽顯示的文本 78 link.innerHTML = '下載圖片'; 79 //設置a標簽的鏈接 80 link.href = "#"; 81 //設置這個a標簽點擊后會下載文件,這個文件的名字為haorooms.png 82 link.download = "haorooms.png"; 83 //在body元素中插入我們剛剛創建的a標簽,插入在canvas標簽前面 84 document.body.insertBefore(link, canvas); 85 //給畫布的mouseover(鼠標放置在元素上方)事件綁定一個函數 86 canvas.addEventListener('mouseover', function(ev) { 87 //給body元素插入名稱為"painted"的類(可是你body已經屬於painted類了啊= =) 88 document.body.classList.add('painted'); 89 }, false); //冒泡階段運行 90 91 //給畫布的mousemnove(鼠標移動事件)事件綁定draw(鼠標按下后繪制線條的函數)函數,冒泡階段運行 92 canvas.addEventListener('mousemove', draw, false); 93 //給畫布的mousedown(鼠標按下事件)事件綁定一個函數,這個函數修改mousedown(鼠標是否按下)的值 94 canvas.addEventListener('mousedown', function(ev) { 95 //修改mousedown(鼠標是否按下)的值為true 96 mousedown = true; 97 }, false ); //冒泡階段運行 98 99 //給畫布的mouseup(鼠標抬起事件)事件綁定一個函數,這個函數修改mousedown(鼠標是否按下)的值 100 canvas.addEventListener('mouseup', function(ev) { 101 //修改a標簽的下載鏈接為畫布內容的dataURL,點擊后就可以下載畫布的內容 102 link.href = canvas.toDataURL(); 103 //修改mousedown(鼠標是否按下)的值為false 104 mousedown = false; 105 }, false ); //冒泡階段運行 106 } ,false); //冒泡階段運行 107 </script> 108 </body>
在Chrome中運行效果如下:
在Firefox中運行效果如下:
下載下來是沒有什么問題,就不展示了。
好的,在haorooms上的五個例子都分析過了一遍,雖然有些功能並不是很完美,但咱這篇文章的目的並不是完善這些功能,就到此為止啦。我哪里注釋寫錯了啊或者是哪里代碼有問題請留言告訴我,大家相互交流,一起進步嘛!
參考資料:haorooms博客 - canvas的ImageData對象的介紹:https://www.haorooms.com/post/canvas_imageData
MDN - 像素操作:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
MDN - CanvasRenderingContext2D.imageSmoothingEnable:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality
MDN - HTMLCanvasElement.toDataURL:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL