1. 程式人生 > >『HTML5夢幻之旅』

『HTML5夢幻之旅』

剛過完春節,想必大家收到了各種祝福和賀卡吧~Y某我今年也為同學和家人準備了賀卡。不一樣的是,我的賀卡可不是made from樹,而是一行行程式碼凝聚而來的。

考慮到本次開發需要的功能不多,所以就沒有用庫件了,利用純Html5 Canvas API來完成本次夢幻之旅:節日賀卡。雖然用到的Canvas API不多,但是效果還是蠻理想的~

首先上截圖吧:




哎呀,看到了截圖,各位是不是領悟了傳說中的炫酷華麗(luàn qī bā zāo)?

大家可以先到測試地址裡體驗一下玩法,順便觀察一下這些小正方形所組成的文字有什麼特點。

一,原理

每次寫部落格和大家分享技術的時候,我都會先把原理介紹給大家,因為這樣一來,大家對下文中的程式碼理解起來就快多了。所以原理很重要,得作為第一個研究話題。

無論是在測試地址裡還是截圖中,都不難發現這些文字的特點:是由小正方形拼接而成的,如果說一個小方塊是1px,那麼這裡的文字像不像畫素遊戲裡的文字?

如何實現這樣的文字呢?我們不妨先從畫畫素圖說起。首先,我們知道圖片都是由一畫素一畫素組成的,如果4px*4px的一張白色圖片可以看成陣列,那麼這個陣列可以表示為這樣:

[
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF]
]

如果把第一排第一列的那個畫素塗成黑色,那麼陣列變成:

[
[#000000, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF]
]
為了簡化開發,我們設黑色時數組裡表示為true,白色為false,那麼上述陣列又變成:
[
[true, false, false, false],
[false, false, false, false],
[false, false, false, false],
[false, false, false, false]
]

如果在這張圖上畫個11的字樣,則陣列可以表示為

[
[true, false, false, true],
[true, false, false, true],
[true, false, false, true],
[true, false, false, true]
]

可見如果我們通過陣列存取文字的各畫素顏色,然後遍歷這個陣列來獲取哪些點畫黑色哪些點畫白色,並根據得到的顏色在介面上繪製,就能得出想要的圖案。本次開發的原理就是和此類似。在demo中,粒子(在這裡定義為由多個小正方形組成帶有拖尾或者正在原地旋轉的一個顯示物件)的顏色是我隨機取出的,所以在數組裡,我們只用記載哪裡該有一個粒子,哪裡不該有。然後在新增噴射出的粒子時,我們記錄下點選的位置並在剩餘需要有粒子的位置列表中隨機找到這個粒子該到的位置,再隨機取出一個顏色並按照該顏色呼叫Canvas API進行渲染即可。

在本次開發中,記載哪裡該有粒子哪裡該是空白的陣列儲存在list.js中(true表示有粒子,false表示沒有粒子):

var list = [[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,true,true,true,true,false,true,true,true,true,true,false,true,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,false,true,false,true,false,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,true,true,true,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,true,true,true,true,true,true,true,false,false,true,true,true,true,true,false,true,true,true,true,true,false,false,true,true,true,true,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,true,false,false,false,false,false,true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,true,true,false,false,false,false,false,true,true,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,true,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false],[false,false,false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,true,false,true,false,false,false,true,false,true,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,false,true,false,true,false,false,true,false,false,true,true,true,true,false,false,true,false,true,false,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,true,false,true,false,false,true,false,false,false,false,false,true,false,true,false,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,false,true,false,false,false,true,true,true,true,false,false,false,true,false,true,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,true,false,true,true,true,true,false,false,false,false,true,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,true,false,true,false,false,false,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,true,true,true,false,true,true,true,true,false,false,true,false,false,false,true,false,false,true,true,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,true,false,true,false,false,false,false,false,true,true,true,true,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,true,true,true,true,false,true,true,true,true,false,true,true,false,false,false,true,true,false,true,false,true,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]];
這麼大一堆陣列就完成了“Happy New Year!”的字樣。

為了方便,我做了一個編輯文字的工具,線上使用地址:

使用方法很簡單,就是在畫板上用滑鼠點選格子。灰黑色的格子代表在demo中有粒子,白色則相反。


上圖就代表demo中的粒子需要構成“2015”的字樣。點選“Export”按鈕生成陣列,然後把陣列複製貼上到list.js中,並儲存到list變數下即可。

Ok,萬事俱備只欠程式碼了。

二,程式碼一覽

(1) index.html、common.js和Main.js

先來看html程式碼:

<!DOCTYPE html>
<html>
<head>
	<title>Greeting Card</title>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
	<script type="text/javascript" src="./Particle.js"></script>
	<script type="text/javascript" src="./Sprite.js"></script>
	<script type="text/javascript" src="./Txt.js"></script>
	<script type="text/javascript" src="./Stage.js"></script>
	<script type="text/javascript" src="./Main.js"></script>
	<script type="text/javascript" src="./list.js"></script>
	<script type="text/javascript" src="./common.js"></script>
</head>
<body style="margin: 0px; padding: 0px; font-size: 0px">
	<canvas id="mycanvas"></canvas>
</body>
</html>

為了方便上文所提到的文字編輯器和demo相通(如畫布的大小和文字畫板大小相同,粒子中小正方形大小和畫板格子大小相同),我們準備個common.js來儲存這些常量:

var particleW = particleH = 20,
angleToRad = Math.PI / 180,
stageW = 800,
stageH = 480;

particleW和particleH分別代表小正方形寬度、高度;angleToRad是角度和弧度換算率;stageW,stageH是指舞臺的寬度和高度。

然後再是Main.js。Main.js主要是用來處理事件,迴圈渲染,全屏顯示以及例項化舞臺和文字並提供了獲取哪些地方該有粒子哪些地方不該有粒子的函式介面,當然還有其他的功能不一一列舉了,大夥就直接拿著程式碼啃吧。程式碼如下:

window.addEventListener("load", main, false);

var canvasTag, ctx;
var canvasStyleWidth, canvasStyleHeight, marginLeft = 0, marginTop = 0;
var isFirefox = true, mobile = false;
var instructionsTxt;
var instructionsIndex = 0, instructionsContents = [
	"Tap to open my greeting card~",
	"Well, continue~",
	"Don't stop tapping until you know my meaning ^_^"
];
var showList = new Array();
var positionList = new Array();

(function (n) {
	isFirefox = (n.toLowerCase().indexOf('firefox') >= 0);

	if (
		n.indexOf("iPhone") > 0
		|| n.indexOf("iPad") > 0
		|| n.indexOf("iPod") > 0
		|| n.indexOf("Android") > 0
		|| n.indexOf("Windows Phone") > 0
		|| n.indexOf("BlackBerry") > 0
	) {
		mobile = true;
	}
})(navigator.userAgent);

function main () {
	canvasTag = document.getElementById("mycanvas");
	canvasTag.width = stageW;
	canvasTag.height = stageH;
	ctx = canvasTag.getContext("2d");

	fullScreen();
	addStage();
	addInstructions();
	getParticlesPosition();
	canvasTag.addEventListener(
		"mouseup",
		function (e) {
			if (instructionsIndex++ >= instructionsContents.length - 1) {
				instructionsTxt.visible = false;
			} else {
				instructionsTxt.text = instructionsContents[instructionsIndex];
			}

			if (e.offsetX == null && e.layerX != null) {
				e.offsetX = e.layerX;
				e.offsetY = e.layerY;
			}

			var startX = scaleOffsetX(e.offsetX),
			startY = scaleOffsetY(e.offsetY);

			for (var i = 0; i < 5; i++) {
				addParticle(startX, startY);
			}
		},
		false
	);

	setInterval(function () {
		loop();
	}, 1000/60);
}

function fullScreen () {
	var w = stageW, h = stageH, ww = window.innerWidth, wh = window.innerHeight;

	if (mobile) {
		if (ww / wh > stageW / stageH) {
			h = wh;
			w = stageW * wh / stageH;
		} else {
			w = ww;
			h = stageH * ww / stageW;
		}
	}

	canvasTag.style.width = w + "px";
	canvasTag.style.height = h + "px";
	canvasTag.style.marginLeft = (ww - w) / 2 + "px";
	canvasTag.style.marginTop = (wh - h) / 2 + "px";

	canvasStyleWidth = w;
	canvasStyleHeight = h;

	if (isFirefox) {
		marginLeft = parseInt(canvasTag.style.marginLeft);
		marginTop = parseInt(canvasTag.style.marginTop);
	}
}

function addStage () {
	var stage = new Stage();
	showList.push(stage);
}

function addInstructions () {
	instructionsTxt = new Txt(instructionsContents[instructionsIndex]);
	showList.push(instructionsTxt);
}

function getParticlesPosition () {
	for (var i = 0, l = list.length; i < l; i++) {
		var item = list[i];

		for (var j = 0, n = item.length; j < n; j++) {
			if (item[j]) {
				positionList.push({x : j * particleW, y : i * particleH});
			}
		}
	}
}

function addParticle (startX, startY) {
	var index = Math.floor(Math.random() * (positionList.length - 1)),
	pos = positionList[index];

	if (!pos) {
		return;
	}

	var particle = new Particle(startX, startY, pos.x, pos.y);
	showList.push(particle);

	positionList.splice(index, 1);
}

function scaleOffsetX (v) {
	return (v - marginLeft) * stageW / canvasStyleWidth;
}

function scaleOffsetY (v) {
	return (v - marginTop) * stageH / canvasStyleHeight;
}

function loop () {
	ctx.clearRect(0, 0, canvasTag.width, canvasTag.height);

	for (var i = 0, l = showList.length; i < l; i++) {
		showList[i].loop();
	}
}

由於要放到移動端執行,所以在針對移動端做了點處理。首先介紹幾個全域性變數:

canvasTag 通過document.getElementById取出的一個canvas標籤物件

ctx canvasTag.getContext獲取的一個CanvasRenderingContext2D物件

canvasStyleWidth、canvasStyleHeight 這兩個變數分別記載canvasTag的style屬性中這是的width和height;用於處理全屏拉伸後,滑鼠事件失靈的問題

marginLeft、marginTop 記載canvasTag的style中marginLeft,marginTop;用於處理Firefox等瀏覽器中,在canvasTag的位置移動後滑鼠事件取出的layerX和layerY不是相對畫布左上角座標的問題(換句話說就是讓點選的位置成為粒子發射的位置)

isFirefox、mobile 這倆是用來判斷是否是Firefox瀏覽器和移動端的變數

instructionsTxt 這個是一個Txt物件,負責顯示說明文字(說明文字是什麼?@[email protected]!就是demo一開始那個蠱惑你點選螢幕的傢伙)

instructionsIndex、instructionsContents 前者是記載說明到了那一步,後者是記載說明內容的一個數組

showList 顯示列表,把需要渲染的物件扔進這個陣列,就可以使該物件重複渲染了

positionList 記載哪些地方可以出現粒子

呼~全域性變數終於介紹完了,繼續往下看:

(function (n) {
	isFirefox = (n.toLowerCase().indexOf('firefox') >= 0);

	if (
		n.indexOf("iPhone") > 0
		|| n.indexOf("iPad") > 0
		|| n.indexOf("iPod") > 0
		|| n.indexOf("Android") > 0
		|| n.indexOf("Windows Phone") > 0
		|| n.indexOf("BlackBerry") > 0
	) {
		mobile = true;
	}
})(navigator.userAgent);
在這個匿名函式裡給isFirefox和mobile賦值。

在接下來的main函式中,任勞任怨的main需要呼叫全屏顯示函式,加入舞臺函式,加入說明文字函式,獲取粒子位置函式,加入事件函式,並且還要驅動迴圈渲染。

值得一看的是事件部分:

canvasTag.addEventListener(
	"mouseup",
	function (e) {
		if (instructionsIndex++ >= instructionsContents.length - 1) {
			instructionsTxt.visible = false;
		} else {
			instructionsTxt.text = instructionsContents[instructionsIndex];
		}

		if (e.offsetX == null && e.layerX != null) {
			e.offsetX = e.layerX;
			e.offsetY = e.layerY;
		}

		var startX = scaleOffsetX(e.offsetX),
		startY = scaleOffsetY(e.offsetY);

		for (var i = 0; i < 5; i++) {
			addParticle(startX, startY);
		}
	},
	false
);
在某些瀏覽器中獲取點選位置不是用offsetX和offsetY而是layerX和layerY。所以在這裡我們需要統一一下。由於在移動端我做了全屏拉伸處理,所以用scaleOffsetX和scaleOffsetY來矯正滑鼠位置。

(2) Stage.js和Txt.js

Stage這個類在Main.js中得到例項,並加入顯示列表。Stage類的程式碼如下:

function Stage () {
	var s = this;

	s.width = canvasTag.width;
	s.height = canvasTag.height;
	s.bgColor = ctx.createRadialGradient(s.width / 2, s.height / 2, 10, s.width / 2, s.height / 2, s.width * 0.6);
	s.bgColor.addColorStop(0.3, "#CCCCCC");
	s.bgColor.addColorStop(1.0, "#FFFFFF");
}

Stage.prototype = {
	loop : function() {
		var s = this;

		ctx.save();
		ctx.beginPath();
		ctx.fillStyle = s.bgColor;
		ctx.rect(0, 0, s.width, s.height);
		ctx.fill();
		if (s.isShowInstructions) {
			ctx.fillStyle = "black";
			ctx.font = "bold 20px sans-serif";
			ctx.textAlign = "center";
			ctx.textBaseline = "middle";
			ctx.fillText("Tap to open greeting card~", stageCenterX, stageCenterY);
		}
		ctx.restore();
	}
};
在loop函式中,我們進行渲染,把舞臺渲染出來。

再是Txt.js:

function Txt (text) {
	var s = this;

	s.x = stageW / 2;
	s.y = stageH / 2;
	s.text = text || "";
	s.visible = true;
}

Txt.prototype = {
	loop : function() {
		var s = this;

		if (!s.visible) {
			return;
		}

		ctx.save();
		ctx.fillStyle = "black";
		ctx.font = "bold 20pt sans-serif";
		ctx.textAlign = "center";
		ctx.textBaseline = "middle";
		ctx.fillText(s.text, s.x, s.y);
		ctx.restore();
	}
};
和Stage類似,通過loop來進行渲染。不同的是多了個visible屬性來控制是否顯示。畢竟Txt物件在demo中是可以消失的。

(3) Particle.js和Sprite.js

前面也介紹了,Particle是指許多小正方形組成的一個有拖尾的顯示物件。而小正方形就是Sprite了。

先來看看Particle的程式碼:

function Particle (startX, startY, endX, endY) {
	var s = this;

	s.x = startX;
	s.y = startY;
	s.rotation = 0;
	s.endX = endX;
	s.endY = endY;
	s.displacement = Math.sqrt((startX - endX) * (startX - endX) + (startY - endY) * (startY - endY));
	s.stepLength = 7;
	s.stepNum = s.displacement / s.stepLength;
	s.stepIndex = 0;
	s.stopAddSprite = false;
	s.moveCos = (endX - startX) / s.displacement;
	s.moveSin = (endY - startY) / s.displacement;
	s.childList = new Array();
	s.removeList = new Array();
	s.color = Particle.COLOR_LIST[Math.round(Math.random() * (Particle.COLOR_LIST.length - 1))];
}

Particle.COLOR_LIST = [
	"#990000",
	"#FF0000",
	"#CC3300",
	"#CC6600",
	"#CC0033",
	"#FFFF00",
	"#33FF00",
	"#33CC00",
	"#0066FF",
	"#00FF99",
	"#330099",
	"#990033",
	"#000099"
];

Particle.prototype = {
	loop : function () {
		var s = this;

		s.loopChild();
		s.clearRemoveList();
		s.updateShowProperites();
		s.addChildSprite();
	},

	loopChild : function () {
		var s = this;

		for (var i = 0, l = s.childList.length; i < l; i++) {
			var o = s.childList[i];

			if (!o) {
				continue;
			}

			o.loop();

			if (o.mode == Sprite.MODE_DISAPPEAR) {
				s.removeList.push(o);
			}
		}
	},

	clearRemoveList : function () {
		var s = this;

		for (var j = 0, m = s.removeList.length; j < m; j++) {
			var toRemoveObj = s.removeList[j];

			for (var k = 0, n = s.childList.length; k < n; k++) {
				if (s.childList[k].index == toRemoveObj.index) {
					s.childList.splice(k, 1);

					break;
				}
			}
		}

		s.removeList.splice(0, s.removeList.length);
	},

	updateShowProperites : function () {
		var s = this;

		s.x += s.stepLength * s.moveCos;
		s.y += s.stepLength * s.moveSin;
		s.rotation += 10;
	},

	addChildSprite : function () {
		var s = this;

		if (s.stopAddSprite) {
			return;
		}

		if (++s.stepIndex >= s.stepNum) {
			s.x = s.endX;
			s.y = s.endY;

			var sprite = new Sprite(s.x, s.y, s.rotation, true);
			sprite.color = s.color;
			s.childList.push(sprite);

			s.stopAddSprite = true;

			return;
		}

		var sprite = new Sprite(s.x, s.y, s.rotation, false);
		sprite.color = s.color;
		s.childList.push(sprite);
	}
};
這個類就相對於前幾個要複雜一些了,接受四個引數,分別是[開始x座標, 開始y座標, 終點x座標, 終點y座標]。首先是對其屬性的介紹:

x、y、rotation 分別表示x座標,y座標,旋轉角度

endX、endY 記錄粒子要到的位置

displacement 傳說中物理學裡的位移!!!(根據高中物理必修一的知識,位移是向量,但是這裡我直接賦值為標量了,祈禱俺的物理老師沒有看到這裡吧)

stepLength 粒子每次移動時,移動的長度

stepNum 計算一下粒子移動到目的地需要多少步

stepIndex 粒子已經移動了多少步,用於判斷粒子是否該停下了

stopAddSprite 用於判斷是否停止新增拖尾小正方形

moveCos、moveSin displacement代表起始點和終點中點連線的長度,stepLength的方向也是沿著該線的。我們移動物件只能移動x,y座標,所以計算出該線的cos和sin值以便算出x方向上的增量和y方向上的增量,從而達到按任意角度移動的物件。

childList 成員列表,裝載小正方形拖尾的陣列

removeList 小正方形的透明度降為0或小於0後,需要移除這些小正方形,所以把這些小正方形加到移除列表removeList中,然後等渲染完畢後遍歷移除列表,從childList中移除需要移除的物件。

color 粒子的顏色,是從Particle.COLOR_LIST中隨機取出的

Ok,再來看看成員函式介紹,具體的程式碼大家對照看吧,我只介紹一下函式執行的邏輯和功能:

loop 這個函式作為其他函式的入口

loopChild 從childList中取出小正方形Sprite物件進行渲染,並把透明度為0或小於0的小正方形放入removeList中。需要注意的是,我判斷小正方形是否需要移除使用的是Sprite的mode屬性,其實這個mode屬性在Sprite透明度變為0或小於0時就會變成Sprite.MODE_DISAPPEAR,具體的程式碼見下文Sprite部分。

clearRemoveList 進行清理需要移除的小正方形。為什麼不直接在loopChild函式裡進行移除操作而要準備個移除列表在渲染完成後進行移除呢?那是因為你在迴圈渲染時,是在遍歷childList,如果立刻刪除需要刪除的元素,就會破壞開始遍歷時childList的結構,這樣一來就可能出現小正方形閃爍的現象。

updateShowProperites 更新粒子的位置和旋轉的角度

addChildSprite 加入小正方形實現拖尾效果

至此,Particle介紹完畢。由程式碼可知,Particle並沒有進行渲染,而是通過childList中的Sprite物件來進行的。所以該到介紹Sprite的時候了:

function Sprite (x, y, rotation, cannotDisappear) {
	var s = this;

	s.x = x;
	s.y = y;
	s.index = Sprite.INDEX++;
	s.rotation = rotation;
	s.alpha = 1;
	s.mode = Sprite.MODE_APPEAR;
	s.cannotDisappear = cannotDisappear;
	s.color = "#FFFFFF";
	s.startDrawX = -particleW / 2;
	s.startDrawY = -particleH / 2;
}

Sprite.INDEX = 0;

Sprite.MODE_APPEAR = "appear";
Sprite.MODE_DISAPPEAR = "disappear";

Sprite.prototype = {
	loop : function () {
		var s = this;

		ctx.save();
		ctx.beginPath();
		ctx.translate(s.x, s.y);
		ctx.rotate(s.rotation * angleToRad);
		ctx.globalAlpha = s.alpha;
		ctx.rect(s.startDrawX, s.startDrawY, particleW, particleH);
		ctx.fillStyle = s.color;
		ctx.fill();
		ctx.restore();

		if (s.cannotDisappear) {
			s.rotation += 5;

			return;
		}

		s.alpha -= 0.05;

		if (s.alpha <= 0) {
			s.mode = Sprite.MODE_DISAPPEAR;
		}
	}
};

引數介紹:

x 繪製的x座標

y 繪製的y座標

rotation 旋轉角度

cannotDisppear 是否減少透明度並可以被移除。如果該Sprite作為的是拖尾中的小正方形那麼這個引數為false,如果是停止後原地旋轉的小正方形則為true

屬性介紹:

x、y、rotation 同Particle類的x、y、rotation

index 物件編號,移除Sprite時會用到,相當於物件的身份證

alpha 透明度

mode 為Sprite.MODE_APPEAR、Sprite.MODE_DISAPPEAR;前者表示正常顯示,後者表示透明度降為0或0以下,需要移除此物件

color 小正方形顏色,由裝載它的Particle的color決定

startDrawX、startDrawY 由於小正方形旋轉時是按中心旋轉的,所以繪製矩形時其實座標不能為0,而是由這兩個屬性決定矩形的起始點

cannotDisppear 見引數cannotDisppear

函式介紹:

loop 渲染函式

最後,奉上原始碼Github地址:

本章就到此為止了。歡迎大家交流~

----------------------------------------------------------------

歡迎大家轉載我的文章。

歡迎繼續關注我的部落格