iT邦幫忙

DAY 12
2

且戰且走HTML5系列 第 12

且戰且走HTML5(12) Canvas基本繪圖-圖形

今天先建立一個可以比較彈性加入繪圖功能的機制,然後先嘗試塗鴉及長方形繪製來驗證這個機制。
主要的方向是,利用一個物件來集中管理2D Context的屬性及繪圖方法,並且可以隨時新增繪圖方法,而且讓所有操作有一致性。在製作繪圖工具時,通常會在工具列做幾件事情,包括選擇繪圖方法(畫線、畫圖形等)、挑選顏色(線條或填色顏色)等操作。做完這些操作後,才會到Canvas開始使用這些繪圖方法。

為了達到這樣的目的,設計了一個簡單的PaintTools物件constructor,以及一個工廠方法($C)來建立PaintTools物件及設定一些預設繪圖方法然後回傳。程式碼如下:

 (function(window, undefined) {
 	var document = window.document;
 	var tools = {}, queue = [], clones = [];
 	var PaintToolsProto = {
 		get fillStyle() {
 			return  this.ctx.fillStyle;
 		},
 		set fillStyle(val) {
 			 this.ctx.fillStyle = val;
 		},
 		get strokeStyle() {
 			return  this.ctx.strokeStyle;
 		},
 		set strokeStyle(val) {
 			 this.ctx.strokeStyle = val;
 		},
 		get lineWidth() {
 			return this.ctx.lineWidth;
 		},
 		set lineWidth(val) {
 			this.ctx.lineWidth = val;
 		},
 		get lineCap() {
 			return  this.ctx.lineCap;
 		},
 		set lineCap(val) {
 			 this.ctx.lineCap = val;
 		},
 		get lineJoin() {
 			return  this.ctx.lineJoin;
 		},
 		set lineJoin(val) {
 			 this.ctx.lineJoin = val;
 		},
 	};
 	window['$C'] = function(ctx) {
 		var ret = new PaintTools(ctx);
 		ret.regist(drawLine);
 		ret.regist(strokeRect);
 		ret.regist(fillRect);
 		return ret;
 	}
 	var PaintTools = function(_ctx) {
 		this.ctx = _ctx;
 		this.drawType = '';
 	};
 	PaintTools.prototype = PaintToolsProto;
 	PaintTools.prototype.regist = function(tool) {
 		if(typeof tool.type !== 'undefined' && typeof tool.run !== 'undefined') {
 			tools[tool.type] = tool;
 		}
 	};
 	PaintTools.prototype.do = function() {
 		var args = [];
 		for(var i=0; i<arguments.length; i++) {
 			args.push(arguments[i]);
 		}
 		if(typeof tools[this.drawType] !== 'undefined') {
 			tools[this.drawType].run.apply(this, args);
 		}
 	};
 	PaintTools.prototype.pushCanvas = function() {
 		clones.push(this.ctx.getImageData(0, 0, this.ctx.canvas.width, this.ctx.canvas.height));
 	};
 	PaintTools.prototype.popCanvas = function() {
 		if(clones.length>0) {
 			this.ctx.putImageData(clones.pop(), 0, 0);
 		}
 	};
 	PaintTools.prototype.restoreCanvas = function() {
 		if(clones.length>0) {
 			this.ctx.putImageData(clones[0], 0, 0);
 		}
 	};
 	PaintTools.prototype.dropCanvas = function() {
 		if(clones.length>0) {
 			clones.pop();
 		}
 	};
 	var drawLine = {
 		type: 'drawLine',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.moveTo(x,y);
 			this.ctx.lineTo(x1,y1);
 			this.ctx.closePath();
 			this.ctx.stroke();
 		}
 	};
 	var strokeRect = {
 		type: 'strokeRect',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.rect(x, y, x1-x, y1-y);
 			this.ctx.closePath();
 			this.ctx.stroke();
 		}
 	};
 	var fillRect = {
 		type: 'fillRect',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.rect(x, y, x1-x, y1-y);
 			this.ctx.closePath();
 			this.ctx.fill();
 		}
 	};
 
 })(window);

物件用constructor建立後,就可以利用regist()來加入繪圖方法,設定好對應的drawType然後呼叫do(),就會執行對應的繪圖方法。繪圖方法的類型,則定義在每個繪圖方法物件的type屬性中。實際進行繪圖的方法則定義在繪圖方法物件的run屬性。

為了可以簡單地變更2D Context的全域設定,利用幾個getter/setter來使操作PaintTools屬性時,實際去讀寫2D Context的屬性,這樣可以少打一些字。

除此之外,加入了幾個簡單管理Canvas的ImageData的方法,方便在繪製長方形時,做出預覽的效果。這樣在按下滑鼠鍵後,長方形會隨著滑鼠的移動跟著滑鼠游標改變大小,直到放開滑鼠鍵才把長方形真正繪製出來。

繪製長方形的方法其實跟塗鴉的方法有點類似,只是細節有一點不一樣。長方形在繪製過程中,每次移動時,繪製的長方形左上角座標是不變的(就是按下滑鼠時的座標),只有右下角座標是隨著滑鼠游標而移動。但是對於塗鴉來說,每次都是從上一次繪製的終點畫直線到事件發生的座標。

設計這個PaintTool花了一些時間跟嘗試,所以繪圖的部分就先只做出簡單的長方形繪製功能,其他的就留到後面吧。

還是先看一下目前為止的程式碼:

 <meta charset='utf-8'>
 
 <link rel="StyleSheet" type="text/css" href="reset.css">
 <style>
 canvas {
 	border: solid 1px gray;
 }
 .panel {
 	margin: 5px 5px 5px 5px;
 	padding: 5px 5px 5px 5px;
 	border: solid 2px #336699;
 	width: 778px;
 }
 .tools {
 	margin-right: 5px;
 	padding: 2px 2px 2px 2px;
 	border: solid 2px #6699CC;
 	width: 120px;
 	height: 474px;
 	float:left;
 }
 label {
 	font-size: 10px;
 }
 </style>
 <link rel="stylesheet" href="js/colorPicker.css" type="text/css" />
 <script src='http://code.jquery.com/jquery-1.8.2.js'></script>
 <script src='js/jquery.colorPicker.js'></script>
 <script>
 (function(window, undefined) {
 	var document = window.document;
 	var tools = {}, queue = [], clones = [];
 	var PaintToolsProto = {
 		get fillStyle() {
 			return  this.ctx.fillStyle;
 		},
 		set fillStyle(val) {
 			 this.ctx.fillStyle = val;
 		},
 		get strokeStyle() {
 			return  this.ctx.strokeStyle;
 		},
 		set strokeStyle(val) {
 			 this.ctx.strokeStyle = val;
 		},
 		get lineWidth() {
 			return this.ctx.lineWidth;
 		},
 		set lineWidth(val) {
 			this.ctx.lineWidth = val;
 		},
 		get lineCap() {
 			return  this.ctx.lineCap;
 		},
 		set lineCap(val) {
 			 this.ctx.lineCap = val;
 		},
 		get lineJoin() {
 			return  this.ctx.lineJoin;
 		},
 		set lineJoin(val) {
 			 this.ctx.lineJoin = val;
 		},
 	};
 	window['$C'] = function(ctx) {
 		var ret = new PaintTools(ctx);
 		ret.regist(drawLine);
 		ret.regist(strokeRect);
 		ret.regist(fillRect);
 		return ret;
 	}
 	var PaintTools = function(_ctx) {
 		this.ctx = _ctx;
 		this.drawType = '';
 	};
 	PaintTools.prototype = PaintToolsProto;
 	PaintTools.prototype.regist = function(tool) {
 		if(typeof tool.type !== 'undefined' && typeof tool.run !== 'undefined') {
 			tools[tool.type] = tool;
 		}
 	};
 	PaintTools.prototype.do = function() {
 		var args = [];
 		for(var i=0; i<arguments.length; i++) {
 			args.push(arguments[i]);
 		}
 		if(typeof tools[this.drawType] !== 'undefined') {
 			tools[this.drawType].run.apply(this, args);
 		}
 	};
 	PaintTools.prototype.pushCanvas = function() {
 		clones.push(this.ctx.getImageData(0, 0, this.ctx.canvas.width, this.ctx.canvas.height));
 	};
 	PaintTools.prototype.popCanvas = function() {
 		if(clones.length>0) {
 			this.ctx.putImageData(clones.pop(), 0, 0);
 		}
 	};
 	PaintTools.prototype.restoreCanvas = function() {
 		if(clones.length>0) {
 			this.ctx.putImageData(clones[0], 0, 0);
 		}
 	};
 	PaintTools.prototype.dropCanvas = function() {
 		if(clones.length>0) {
 			clones.pop();
 		}
 	}
 	var drawLine = {
 		type: 'drawLine',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.moveTo(x,y);
 			this.ctx.lineTo(x1,y1);
 			this.ctx.closePath();
 			this.ctx.stroke();
 		}
 	}
 	var strokeRect = {
 		type: 'strokeRect',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.rect(x, y, x1-x, y1-y);
 			this.ctx.closePath();
 			this.ctx.stroke();
 		}
 	}
 	var fillRect = {
 		type: 'fillRect',
 		run: function(x, y, x1, y1) {
 			this.ctx.beginPath();
 			this.ctx.rect(x, y, x1-x, y1-y);
 			this.ctx.closePath();
 			this.ctx.fill();
 		}
 	}
 
 })(window);
 </script>
 <script>
 $(document).ready(function() {
 	var canvas = $('#canvas');
 	var context = canvas[0].getContext('2d');
 	var drawing = false;
 	var queue = [];
 	var tool = $C(context);
 	tool.lineCap = 'round';
 	tool.lineJoin = 'round';
 	$('#color1').colorPicker({
 		pickerDefault: 'ffffff',
 		onColorChange: function(id, val) {
 			tool.fillStyle = val;
 		}
 	});
 	$('#color2').colorPicker({
 		pickerDefault: 'ffffff',
 		onColorChange: function(id, val) {
 			tool.strokeStyle = val;
 		}
 	});
 
 	canvas.bind('mousedown', function(e) {
 		e.preventDefault();
 		if(!drawing) {
 			$(this).data('cursor', $(this).css('cursor'));
 			$(this).css('cursor', 'pointer');
 			drawing = true;
 			if(tool.drawType=='strokeRect' || tool.drawType=='fillRect') {
 				tool.pushCanvas();
 			}
 			var offset = $(e.currentTarget).offset()
 			var x = e.pageX - offset.left;
 			var y = e.pageY - offset.top;
 			tool.do(x,y,x,y);
 			queue.push([x,y]);
 		}
 	});
 	canvas.bind('mousemove', function(e) {
 		e.preventDefault();
 		if(drawing) {
 			var old = queue.shift();
 			if(tool.drawType=='strokeRect' || tool.drawType=='fillRect') {
 				tool.restoreCanvas();
 				queue.push(old);
 			}
 			var offset = $(e.currentTarget).offset();
 			var x = e.pageX - offset.left;
 			var y = e.pageY - offset.top;
 			tool.do(old[0],old[1],x,y);
 			if(tool.drawType == 'drawLine') {
 				queue.push([x,y]);
 			}
 		}
 	});
 	canvas.bind('mouseup', function(e) {
 		if(drawing) {
 			$(this).css('cursor', $(this).data('cursor'));
 			tool.dropCanvas();
 			var old = queue.shift();
 			var offset = $(e.currentTarget).offset();
 			var x = e.pageX - offset.left;
 			var y = e.pageY - offset.top;
 			tool.do(old[0], old[1], x, y);
 			drawing = false;
 		}
 	});
 	canvas.bind('mouseleave', function(e) {
 		if(drawing) {
 			$(this).css('cursor', $(this).data('cursor'));
 			tool.dropCanvas();
 			var old = queue.shift();
 			var offset = $(e.currentTarget).offset();
 			var x = e.pageX - offset.left;
 			var y = e.pageY - offset.top;
 			tool.do(old[0], old[1], x, y);
 			drawing = false;
 		}
 	});
 	$('#linewidth').bind('change', function() {
 		context.lineWidth = $(this).val();
 	});
 	$('#freestyle').bind('click', function() {
 		tool.drawType = 'drawLine';
 	});
 	$('#strokerect').bind('click', function() {
 		tool.drawType = 'strokeRect';
 	});
 	$('#fillrect').bind('click', function() {
 		tool.drawType = 'fillRect';
 	});
 });
 </script>
 
 
 <div class='panel'>
 	<div class='tools' id='tools'>
 		<div>
 			<button id="freestyle">freesytle</button>
 		</div>
 		<div>
 			<label for='color1'>Fill:</label><div style='display:inline-block'><input type='text' id='color1' name='color1' value='#000000'></div><br>
 			<label for='color2'>Stroke:</label><div style='display:inline-block'><input type='text' id='color2' name='color2' value='#000000'></div><br>
 			<label for='linewidth'>Line Width</label><select id='linewidth'>
 				<option value="1">1</option>
 				<option value="3">3</option>
 				<option value="5">5</option>
 				<option value="7">7</option>
 			</select>
 		</div>
 		<div>
 			<button id='strokerect'>Stroke Rectangle</button>
 			<button id='fillrect'>Fill Rectangle</button>
 		</div>
 	</div>
 	<canvas id="canvas" width='640' height='480'></canvas>
 </div>
 
 

畫面長得像這樣:

塗鴉及繪製長方形後:

明天再來嘗試其他圖形的繪製,順利的話看看能不能把輸入文字的功能也做出來。


上一篇
且戰且走HTML5(11) Canvas基本繪圖-塗鴉
下一篇
且戰且走HTML5(13) Canvas基本繪圖-更多圖形
系列文
且戰且走HTML530

尚未有邦友留言

立即登入留言