1. 程式人生 > >html5 的 canvas 想寫個小專案 畫板

html5 的 canvas 想寫個小專案 畫板

 最近在研究下 html5 的 canvas 想寫個小專案,練練手,結果寫了一個畫圖板,功能點有 繪製、直線、圓、方形、塗鴉、線條粗細、顏色切換、撤銷、回退、儲存、下載、外部圖片拖入等 , 用的技術是包含 html5 中的本地儲存、下載、canvas 等技術,上圖。



演示地址:http://chengxinwei.github.io/html5/2013/06/20/HTML5_CANVAS_%E7%94%BB%E5%9B%BE%E6%9D%BF/

這個專案中用到了 canvas 的很多基礎功能 。在這裡解釋一下核心程式碼的思路。

在這個專案中 我用到了 2 層 canvas , 原因是當用戶在畫部分圖形的時候希望看到繪畫的過程,比如在畫圓的時候,而canvas 目前支援的就是清空 和 繪製操作, 所以在這裡我用了 bak 層做了一個 假象。使用者一開始的所有繪製都是在 bak 層做的繪製 , 之後當滑鼠鬆開的時候 才會到 真正的 canvas 層儲存。這個是核心思路。

接下來我們來看一下程式碼的構造。

1.首先第一部。是做物件初始化, 包括有 初始化canvas , context , height, width  這個很簡單就不做多的解釋了 程式碼如下。
Javascript程式碼
複製程式碼
 收藏程式碼
  1. //初始化   
  2.         var initCanvas = function(){   
  3.             canvas =  document.getElementById("canvas");   
  4.             canvas.width = canvasWidth;   
  5.             canvas.height = canvasHeight;   
  6.             context = canvas.getContext('2d');   
  7.             canvasTop = $(canvas).offset().top   
  8.             canvasLeft = $(canvas).offset().left;   
  9.             canvas_bak =  document.getElementById("canvas_bak");   
  10.             canvas_bak.width = canvasWidth;   
  11.             canvas_bak.height = canvasHeight;   
  12.             context_bak = canvas_bak.getContext('2d');         
  13.         }     
//初始化
		var initCanvas = function(){
			canvas =  document.getElementById("canvas");
			canvas.width = canvasWidth;
			canvas.height = canvasHeight;
			context = canvas.getContext('2d');
			canvasTop = $(canvas).offset().top
			canvasLeft = $(canvas).offset().left;
	

			canvas_bak =  document.getElementById("canvas_bak");
			canvas_bak.width = canvasWidth;
			canvas_bak.height = canvasHeight;
			context_bak = canvas_bak.getContext('2d');		
		}	



2. 第二部 就是繪製了 , 思路是當滑鼠點下得時候 確定一個初始點, 當滑鼠移動的時候開始繪製。之前說過 滑鼠移動只是在bak 層繪製 , 當鬆開滑鼠時把bak層的 新增到 canvas 層 。 那麼這裡就包含三個事件 。 點選,移動,鬆開 , 對應了三個不同的方法 。程式碼如下:
Javascript程式碼 複製程式碼 收藏程式碼
  1. //滑鼠按下獲取 開始xy開始畫圖   
  2.     var mousedown = function(e){   
  3.         context.strokeStyle= color;   
  4.         context_bak.strokeStyle= color;   
  5.         context_bak.lineWidth = size;   
  6.         e=e||window.event;   
  7.         startX = e.clientX - canvasLeft;   
  8.         startY = e.clientY - canvasTop;   
  9.         context_bak.moveTo(startX ,startY );   
  10.         canDraw = true;            
  11.         if(graphType == 'pencil'){   
  12.             context_bak.beginPath();   
  13.         }else if(graphType == 'circle'){   
  14.             context.beginPath();   
  15.             context.moveTo(startX ,startY );   
  16.             context.lineTo(startX +2 ,startY+2);   
  17.             context.stroke();      
  18.         }else if(graphType == 'rubber'){                               
  19.             context.clearRect(startX - size * 10 ,  startY - size * 10 , size * 20 , size * 20);                   
  20.         }      
  21.     };     
  22.     //滑鼠離開 把蒙版canvas的圖片生成到canvas中   
  23.     var mouseup = function(e){   
  24.         e=e||window.event;   
  25.         canDraw = false;   
  26.         var image = new Image();   
  27.         if(graphType!='rubber'){       
  28.             image.src = canvas_bak.toDataURL();   
  29.             image.onload = function(){   
  30.                 context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);   
  31.                 clearContext();   
  32.                 saveImageToAry();   
  33.             }   
  34.             var x = e.clientX   - canvasLeft;   
  35.             var y = e.clientY  - canvasTop;    
  36.             context.beginPath();   
  37.             context.moveTo(x ,y );   
  38.             context.lineTo(x +2 ,y+2);   
  39.             context.stroke();      
  40.         }   
  41.     };   
  42.     // 滑鼠移動   
  43.     var  mousemove = function(e){   
  44.         e=e||window.event;   
  45.         var x = e.clientX   - canvasLeft;   
  46.         var y = e.clientY  - canvasTop;    
  47.         //方塊  4條直線搞定   
  48.         if(graphType == 'square'){   
  49.             if(canDraw){   
  50.                 context_bak.beginPath();   
  51.                 clearContext();   
  52.                 context_bak.moveTo(startX , startY);                           
  53.                 context_bak.lineTo(x  ,startY );   
  54.                 context_bak.lineTo(x  ,y );   
  55.                 context_bak.lineTo(startX  ,y );   
  56.                 context_bak.lineTo(startX  ,startY );   
  57.                 context_bak.stroke();   
  58.             }   
  59.         //直線   
  60.         }else if(graphType =='line'){                          
  61.             if(canDraw){   
  62.                 context_bak.beginPath();   
  63.                 clearContext();   
  64.                 context_bak.moveTo(startX , startY);   
  65.                 context_bak.lineTo(x  ,y );   
  66.                 context_bak.stroke();   
  67.             }   
  68.         //畫筆   
  69.         }else if(graphType == 'pencil'){   
  70.             if(canDraw){   
  71.                 context_bak.lineTo(e.clientX   - canvasLeft ,e.clientY  - canvasTop);   
  72.                 context_bak.stroke();                          
  73.             }   
  74.         //圓 未畫得時候 出現一個小圓   
  75.         }else if(graphType == 'circle'){                           
  76.             clearContext();   
  77.             if(canDraw){   
  78.                 context_bak.beginPath();               
  79.                 var radii = Math.sqrt((startX - x) *  (startX - x)  + (startY - y) * (startY - y));   
  80.                 context_bak.arc(startX,startY,radii,0,Math.PI * 2,false);                                      
  81.                 context_bak.stroke();   
  82.             }else{     
  83.                 context_bak.beginPath();                       
  84.                 context_bak.arc(x,y,20,0,Math.PI * 2,false);   
  85.                 context_bak.stroke();   
  86.             }   
  87.         //塗鴉 未畫得時候 出現一個小圓   
  88.         }else if(graphType == 'handwriting'){                                              
  89.             if(canDraw){   
  90.                 context_bak.beginPath();       
  91.                 context_bak.strokeStyle = color;   
  92.                 context_bak.fillStyle  = color;   
  93.                 context_bak.arc(x,y,size*10,0,Math.PI * 2,false);          
  94.                 context_bak.fill();   
  95.                 context_bak.stroke();   
  96.                 context_bak.restore();   
  97.             }else{     
  98.                 clearContext();   
  99.                 context_bak.beginPath();                       
  100.                 context_bak.fillStyle  = color;   
  101.                 context_bak.arc(x,y,size*10,0,Math.PI * 2,false);   
  102.                 context_bak.fill();   
  103.                 context_bak.stroke();   
  104.             }   
  105.         //橡皮擦 不管有沒有在畫都出現小方塊 按下滑鼠 開始清空區域   
  106.         }else if(graphType == 'rubber'){       
  107.             context_bak.lineWidth = 1;   
  108.             clearContext();   
  109.             context_bak.beginPath();               
  110.             context_bak.strokeStyle =  '#000000';                          
  111.             context_bak.moveTo(x - size * 10 ,  y - size * 10 );                           
  112.             context_bak.lineTo(x + size * 10  , y - size * 10 );   
  113.             context_bak.lineTo(x + size * 10  , y + size * 10 );   
  114.             context_bak.lineTo(x - size * 10  , y + size * 10 );   
  115.             context_bak.lineTo(x - size * 10  , y - size * 10 );       
  116.             context_bak.stroke();          
  117.             if(canDraw){                               
  118.                 context.clearRect(x - size * 10 ,  y - size * 10 , size * 20 , size * 20);   
  119.             }              
  120.         }   
  121.     };  
//滑鼠按下獲取 開始xy開始畫圖
	var mousedown = function(e){
		context.strokeStyle= color;
		context_bak.strokeStyle= color;
		context_bak.lineWidth = size;
		e=e||window.event;
		startX = e.clientX - canvasLeft;
		startY = e.clientY - canvasTop;
		context_bak.moveTo(startX ,startY );
		canDraw = true;			
		
		if(graphType == 'pencil'){
			context_bak.beginPath();
		}else if(graphType == 'circle'){
			context.beginPath();
			context.moveTo(startX ,startY );
			context.lineTo(startX +2 ,startY+2);
			context.stroke();	
			
		}else if(graphType == 'rubber'){							
			context.clearRect(startX - size * 10 ,  startY - size * 10 , size * 20 , size * 20);				
		}	
	};	

	//滑鼠離開 把蒙版canvas的圖片生成到canvas中
	var mouseup = function(e){
		e=e||window.event;
		canDraw = false;
		var image = new Image();
		if(graphType!='rubber'){	
			
			image.src = canvas_bak.toDataURL();
			image.onload = function(){
				context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
				clearContext();
				saveImageToAry();
			}
			var x = e.clientX   - canvasLeft;
			var y = e.clientY  - canvasTop;	
			context.beginPath();
			context.moveTo(x ,y );
			context.lineTo(x +2 ,y+2);
			context.stroke();	
		}
	};

	// 滑鼠移動
	var  mousemove = function(e){
		e=e||window.event;
		var x = e.clientX   - canvasLeft;
		var y = e.clientY  - canvasTop;	
		//方塊  4條直線搞定
		if(graphType == 'square'){
			if(canDraw){
				context_bak.beginPath();
				clearContext();
				context_bak.moveTo(startX , startY);						
				context_bak.lineTo(x  ,startY );
				context_bak.lineTo(x  ,y );
				context_bak.lineTo(startX  ,y );
				context_bak.lineTo(startX  ,startY );
				context_bak.stroke();
			}
		//直線
		}else if(graphType =='line'){						
			if(canDraw){
				context_bak.beginPath();
				clearContext();
				context_bak.moveTo(startX , startY);
				context_bak.lineTo(x  ,y );
				context_bak.stroke();
			}
		//畫筆
		}else if(graphType == 'pencil'){
			if(canDraw){
				context_bak.lineTo(e.clientX   - canvasLeft ,e.clientY  - canvasTop);
				context_bak.stroke();						
			}
		//圓 未畫得時候 出現一個小圓
		}else if(graphType == 'circle'){						
			clearContext();
			if(canDraw){
				context_bak.beginPath();			
				var radii = Math.sqrt((startX - x) *  (startX - x)  + (startY - y) * (startY - y));
				context_bak.arc(startX,startY,radii,0,Math.PI * 2,false);									
				context_bak.stroke();
			}else{	
				context_bak.beginPath();					
				context_bak.arc(x,y,20,0,Math.PI * 2,false);
				context_bak.stroke();
			}
		//塗鴉 未畫得時候 出現一個小圓
		}else if(graphType == 'handwriting'){											
			if(canDraw){
				context_bak.beginPath();	
				context_bak.strokeStyle = color;
				context_bak.fillStyle  = color;
				context_bak.arc(x,y,size*10,0,Math.PI * 2,false);		
				context_bak.fill();
				context_bak.stroke();
				context_bak.restore();
			}else{	
				clearContext();
				context_bak.beginPath();					
				context_bak.fillStyle  = color;
				context_bak.arc(x,y,size*10,0,Math.PI * 2,false);
				context_bak.fill();
				context_bak.stroke();
			}
		//橡皮擦 不管有沒有在畫都出現小方塊 按下滑鼠 開始清空區域
		}else if(graphType == 'rubber'){	
			context_bak.lineWidth = 1;
			clearContext();
			context_bak.beginPath();			
			context_bak.strokeStyle =  '#000000';						
			context_bak.moveTo(x - size * 10 ,  y - size * 10 );						
			context_bak.lineTo(x + size * 10  , y - size * 10 );
			context_bak.lineTo(x + size * 10  , y + size * 10 );
			context_bak.lineTo(x - size * 10  , y + size * 10 );
			context_bak.lineTo(x - size * 10  , y - size * 10 );	
			context_bak.stroke();		
			if(canDraw){							
				context.clearRect(x - size * 10 ,  y - size * 10 , size * 20 , size * 20);
										
			}			
		}
	};




順便提一下撤銷和回退的做法。之前有提過在滑鼠鬆開的時候,我們會把 bak 層的內容繪製到 canvas 層中, 那麼在這個時候,同步的會把一份 圖片資訊 存到一個 陣列中去,用於回滾 , 當點選撤銷的時候 只需要把上一個的 圖片資訊取出來,在繪製一遍canvas即可。撤銷回退同理



4.接下來講一下儲存功能實現。儲存圖片使用得 是html5 的 storage 的功能實現的。storage 是瀏覽器開闢了一個5M 的控制元件提供方開發者使用 存放key value 的鍵值對, 有點類似於 cookie ,那麼women儲存的實現就很簡單了,當點選儲存按鈕的時候 , 獲取圖片的 dataUrl 儲存與 storage  中即可,下次開啟瀏覽器 獲取再放入canvas中就可以了。程式碼如下:

Javascript程式碼 複製程式碼 收藏程式碼
  1. //儲存   
  2. var save = function(){   
  3.     for(var i = 1;i<=8;i++){   
  4.         var dataUrl = getStorage(i);   
  5.         if(dataUrl == null || dataUrl == ''){   
  6.             putStorage(i,canvas.toDataURL());   
  7.             $("#history_"+i).attr("src",canvas.toDataURL());   
  8.             initHistorty();   
  9.             return ;   
  10.         }   
  11.     }              
  12. }  
//儲存
var save = function(){
	for(var i = 1;i<=8;i++){
		var dataUrl = getStorage(i);
		if(dataUrl == null || dataUrl == ''){
			putStorage(i,canvas.toDataURL());
			$("#history_"+i).attr("src",canvas.toDataURL());

			initHistorty();
			return ;
		}
	}			
}



5.最後說一下 下載,可能很多人因為這個頭疼,因為沒有後臺的處理,怎麼能做到下載圖片呢。其實在html5中 對於 a 標籤提供了一個新的屬性 【download】 如:
Java程式碼 複製程式碼 收藏程式碼
  1. <a href="javascript:void(0);" id="history_download_1" download="picture.png">下載</a></td>  
<a href="javascript:void(0);" id="history_download_1" download="picture.png">下載</a></td>

瀏覽器預設會把他當做一個下載連結去處理,下載的檔名就是 download 中的 picture.png 下載的內容對應的是src 中的值。所以我們只需要把 圖片的dataUrl 動態賦值上去 即可。

今天就先講到這裡哈,有問題可以給我留言。


--------------//2013-06-258 ---------------

昨天新加了 拖拽圖片的功能, 從資料夾中 拖到畫圖板裡面可以直接覆蓋。
內碼表很簡單 如下:
Javascript程式碼 複製程式碼 收藏程式碼
  1. // 處理檔案拖入事件,防止瀏覽器預設事件帶來的重定向     
  2.        function handleDragOver(evt) {     
  3.         evt.stopPropagation();     
  4.         evt.preventDefault();     
  5.         }   
  6.     // 判斷是否圖片     
  7.     function isImage(type) {     
  8.         switch (type) {     
  9.         case 'image/jpeg':     
  10.         case 'image/png':     
  11.         case 'image/gif':     
  12.         case 'image/bmp':     
  13.         case 'image/jpg':     
  14.             return true;     
  15.         default:     
  16.             return false;     
  17.         }     
  18.     }     
  19.      // 處理拖放檔案列表     
  20.     function handleFileSelect(evt) {     
  21.         evt.stopPropagation();     
  22.         evt.preventDefault();     
  23.         var files = evt.dataTransfer.files;     
  24.         for (var i = 0, f; f = files[i]; i++) {       
  25.             var t = f.type ? f.type : 'n/a';   
  26.             reader = new FileReader();   
  27.             isImg = isImage(t);   
  28.             // 處理得到的圖片     
  29.             if (isImg) {     
  30.                 reader.onload = (function (theFile) {     
  31.                     return function (e) {     
  32.                         var  image = new Image();    
  33.                         image.src =  e.target.result ;   
  34.                         image.onload = function(){   
  35.                             context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);   
  36.                         }   
  37.                     };     
  38.                 })(f)     
  39.                 reader.readAsDataURL(f);     
  40.             }      
  41.         }       
  42.     }     
  43.     //初始化拖入效果   
  44.     var initDrag= function(){   
  45.         var dragDiv  = document.getElementById("canvas_bak");   
  46.         dragDiv.addEventListener('dragover', handleDragOver, false);     
  47.         dragDiv.addEventListener('drop', handleFileSelect, false);     
  48.     }  
 // 處理檔案拖入事件,防止瀏覽器預設事件帶來的重定向  
        function handleDragOver(evt) {  
			evt.stopPropagation();  
			evt.preventDefault();  
         }
		 

		// 判斷是否圖片  
		function isImage(type) {  
			switch (type) {  
			case 'image/jpeg':  
			case 'image/png':  
			case 'image/gif':  
			case 'image/bmp':  
			case 'image/jpg':  
				return true;  
			default:  
				return false;  
			}  
		}  


		 // 處理拖放檔案列表  
		function handleFileSelect(evt) {  
			evt.stopPropagation();  
			evt.preventDefault();  
  
			var files = evt.dataTransfer.files;  
  
			for (var i = 0, f; f = files[i]; i++) {    
				var t = f.type ? f.type : 'n/a';
				reader = new FileReader();
				isImg = isImage(t);
				  
				// 處理得到的圖片  
				if (isImg) {  
					reader.onload = (function (theFile) {  
						return function (e) {  
							var  image = new Image(); 
							image.src =  e.target.result ;
							image.onload = function(){
								context.drawImage(image , 0 ,0 , image.width , image.height , 0 ,0 , canvasWidth , canvasHeight);
							}

						};  
					})(f)  
					reader.readAsDataURL(f);  
				}   
			}    
		}  

		//初始化拖入效果
		var initDrag= function(){
			var dragDiv  = document.getElementById("canvas_bak");
			dragDiv.addEventListener('dragover', handleDragOver, false);  
			dragDiv.addEventListener('drop', handleFileSelect, false);  
		}



簡單解釋一下 , 在html5支援的瀏覽器中, 有drop的回撥函式 , 在其中獲得event之後 裡面有一個物件 dataTransfer.files , 獲取的是 file 檔案資訊 , 最後通過 FileReader.readAsDataURL  的函式讀入,可以獲取到 html5 支援的圖片資訊 , 最後通過建立 image 物件,把圖片繪製進去就可以了。