面向对象+模块化设计绘制canvas星空动画

时间:2022-04-27
本文章向大家介绍面向对象+模块化设计绘制canvas星空动画,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

require.js的相关内容已在我的博文

《requireJs的使用,以canvas绘制星空为例》中描述,

可查看:https://cloud.tencent.com/developer/article/1040858

下面说一下面向对象设计canvas绘制星空的各种对象。

1、静态元素

如背景(Backgound)、土地(Land)、房屋(House)、大树(Tree),这些元素的属性如坐标(x,y)、长度(width)、高度(height)等信息是固定不变,因此我们只用向其绘制函数内传入常量参数就可以维持其状态。

2、随机元素

所谓随机,是指元素的参数信息是随机生成的,在星空绘制的canvas中,存在大量五角星,我们不可能一个个为之赋属性(太过麻烦);也不可能用定步长迭代赋值得方法赋属性(这样会使得星空失去无序性)。

所以我们采用随机+迭代的方法为星星赋值属性。

如坐标可用(Math.random()*width,+Math.random()*height/2)使星星均匀且无序填充在canvas画布上半部分。

3、动画元素

动画元素是指在canvas画布中具有动画效果的元素。在本例中包含流星和上下摆动的文本。

在动画设计中,需要不断重画canvas画布,因此需要不断调用元素的绘制函数。由于随机元素的属性实际上是随机生成固定不变的,动画元素的属性需要在原属性的基础上不断改变,所以这两种元素都需要运用面向对象的封装来保存元素状态。

元素对象模块代码如下:

element.js

define(function(){
	var TextNode=function(offUnitX,offUnitY,str,direction,offLimitY){
		var x,y,str;
		var fillStyle,font;
		var zx,zy,offX,offY,offUnitX,offUnitY;
		var speed,direction,topLimit,bottomLimit,offLimitY;

		//direction标识上下波动的方向
		//topLimit,bottomLimit标识上下波动的界限

		//默认参数
		this.zx=420;
		this.zy=420;
		this.offX=22;
		this.offY=-5;
		this.speed=1;
		this.fillStyle="#70DBDB";
		this.font="30px Georgia,Arial";


		this.test=function(){
			// alert(this.str.length+","+this.offY);
			// alert(this.str.length*this.offY/4);
			// alert(this.topLimit+","+this.bottomLimit);
		}

		//初始化变量
		this.init=function(){
			//接收参数
			this.offUnitX=offUnitX;
			this.offUnitY=offUnitY;
			this.str=str;
			this.direction=direction;

			this.x=this.zx+offUnitX*this.offX;
			this.y=this.zy-offUnitY*this.offY;
			this.topLimit=this.zy+this.offY*offLimitY;
			this.bottomLimit=this.zy-this.offY*offLimitY;
		}

		//初始化,创建即调用
		this.init();

		//摆动文本
		this.wave=function(){
			if(this.y+this.direction*this.speed<this.topLimit||
				this.y+this.direction*this.speed>this.bottomLimit)
				this.direction=-this.direction;
			this.y+=this.direction*this.speed;
		}

		//绘制文本
		this.draw=function(cxt){
			cxt.font=this.font;
			cxt.fillStyle=this.fillStyle;
			cxt.fillText(this.str,this.x,this.y);
		}

		this.setZX=function(zx){
			this.zx=zx;
		}

		this.setZY=function(zy){
			this.zy=zy;
		}

		this.setOffX=function(offX){
			this.offX=offX;
		}

		this.setOffY=function(offY){
			this.offY=offY;
		}

		this.setSpeed=function(speed){
			this.speed=speed;
		}

		this.setFillStyle=function(fillStyle){
			this.fillStyle=fillStyle;
		}

		this.setFont=function(font){
			this.font=font;
		}
	};

	var Star=function(){
		var x,y,R,rotate;
		var fillStyle;

		this.fillStyle="yellow";

		this.init=function(){
			this.x=Math.random()*780+10;
			this.y=(Math.random()*580+10)*3/5;
			this.R=Math.random()*5+3;
			this.rotate=Math.random()*72;
		}

		//创建即初始化
		this.init();

		this.draw=function(cxt){
			// 设置填充样式为黄色
			cxt.fillStyle=this.fillStyle;
			//beginPath()新建路径
			cxt.beginPath();
			//五角星有10个顶点,循环设置顶点
			for(var i=0;i<5;i++){
				cxt.lineTo(Math.cos((72*i+this.rotate)*Math.PI/180)*this.R+this.x,
					Math.sin((72*i+this.rotate)*Math.PI/180)*this.R+this.y);
				// 在这里使用小圆半径为大圆的一半
				cxt.lineTo(Math.cos((72*i+36+this.rotate)*Math.PI/180)*this.R/2+this.x,
					Math.sin((72*i+36+this.rotate)*Math.PI/180)*this.R/2+this.y);
			}
			//closePath()闭合路径
			cxt.closePath();
			//绘制
			cxt.fill();
		}

		this.setFillStyle=function(fillStyle){
			this.fillStyle=fillStyle;
		}
	};

	var ShootStar=function(){
		var x,y,R,angle,arc,rotate;
		var fillStyle;
		var speed,delay,counter,bottom;
		//arc为根据角度计算出的弧度,用以计算流星的运动轨迹
		//bottom为流星下落的下边界,超界则重置流星属性
		//设置delay并在类中初始化为常量,标识在多少次间隔后开始本流星对象的降落
		//counter为计数器,每次间隔,counter自增,当counter>=delay时,流星开始降落
		//isBegin标识是否开始降落


		this.bottom=Math.random()*100+500;
		this.delay=Math.random()*500;
		this.counter=0;
		this.fillStyle="yellow";

		this.init=function(){
			this.x=Math.random()*600+200;
			this.y=Math.random()*100+20;
			this.R=Math.random()*5+3;
			this.angle=Math.random()*30+30;
			this.arc=this.angle*Math.PI/180;
			this.rotate=Math.random()*72;
			this.speed=Math.random()*1+1;
		}

		this.draw=function(cxt){
			cxt.fillStyle=this.fillStyle;
			cxt.beginPath();
			for(var i=0;i<5;i++){
				cxt.lineTo(Math.cos((72*i+this.angle+this.rotate)*Math.PI/180)*this.R+this.x,
					Math.sin((72*i+this.angle+this.rotate)*Math.PI/180)*this.R+this.y);
				cxt.lineTo(Math.cos((72*i+36+this.angle+this.rotate)*Math.PI/180)*this.R/2+this.x,
					Math.sin((72*i+36+this.angle+this.rotate)*Math.PI/180)*this.R/2+this.y);
			}
			cxt.closePath();
			cxt.fill();
		}

		//判断是否开始降落流星
		this.isBegin=function(){
			return ++this.counter>this.delay;
		}

		this.move=function(){
			//如果流星降到canvas距离顶部bottom之内的范围,继续下降,否则流星消失
			if(this.y<this.bottom&&this.x>=0){
				this.x-=this.speed*Math.cos(this.arc);
				this.y+=this.speed*Math.sin(this.arc);
				this.rotate++;
			}else this.init();
		}

		this.setFillStyle=function(fillStyle){
			this.fillStyle=fillStyle;
		}
	};

	var foo=function(){
		alert(1);
	};

	return {
		TextNode:TextNode,
		Star:Star,
		ShootStar:ShootStar,
		foo:foo
	};
});

在本模块中为了方便外部调用,并未使用任何私有元素。

所有不是随机的变量和控制运动的变量全部设置了默认值,并添加了setter函数方便外部更改。

每种对象都包含draw(cxt)函数用于对象实例的绘制。

动画元素包含控制动画进行的函数。

程序主函数如下:

main.js

require.config({
	paths:{
		"jquery"	:"jquery.min",
		"common"	:"common",
		"element"	:"element"
	}
});

require(['common','jquery'],function(common,$){
	$(document).ready(function(){
		var context=common.getContext("canvas",800,600);

		common.run(context,"SO BEAUTIFUL A NIGHT!",200,10);
	});
});

元素绘制模块代码如下:

common.js

define(['element'],function(element){
	var textSet=new Array();
	var starSet=new Array();
	var shootStarSet=new Array();

	//获取文本对象
	var getContext=function(id,width,height){
		var canvas=document.getElementById(id);
		canvas.width=width;
		canvas.height=height;
		var context=canvas.getContext("2d");
		return context;
	};

	var initTextSet=function(str){
		var lowMid=Math.floor(str.length/4);
		var mid=Math.floor(str.length/2);
		var highMid=Math.floor(str.length/4*3);

		//创建中点文本节点
		textSet[mid]=new element.TextNode(0,0,str[mid],1,str.length/4);
		textSet[mid].test();

		for(var i=1;i<=mid;i++){
			//创建lowMid-highMid之间的节点
			if(i<mid-lowMid){
				textSet[mid-i]=new element.TextNode(-i,-i,str[mid-i],1,str.length/4);
				if(str[mid+i]!=null)
					textSet[mid+i]=new element.TextNode(i,i,str[mid+i],1,str.length/4);
			//创建0-lowMid,highMid-str.length之间的节点
			}else{
				textSet[mid-i]=new element.TextNode(-i,-(2*lowMid-i),str[mid-i],-1,str.length/4);
				if(str[mid+i]!=null)
					textSet[mid+i]=new element.TextNode(i,2*highMid-2*mid-i,str[mid+i],-1,str.length/4);
			}
		}
	};

	var initStarSet=function(num){
		for(var i=0;i<num;i++)
			starSet[i]=new element.Star();
	};

	var initShootStarSet=function(num){
		for(var i=0;i<num;i++)
			shootStarSet[i]=new element.ShootStar();
	};

	//绘制背景层
	var drawBackground=function(cxt,width,height){
		var grd=cxt.createLinearGradient(0,0,0,height);
		//设置渐变点(范围0-1,添加渐变色),在这里我们起始设置为黑色,渐变到墨蓝色
		grd.addColorStop(0,"black");
		grd.addColorStop(1,"#054696");
		//设置填充样式为设置好的渐变模式
		cxt.fillStyle=grd;
		//使用设置好的模式绘制矩形,在这里的矩形作为背景层
		cxt.fillRect(0,0,width,height);
	};

	//绘制地面
	var drawLand=function(cxt){
		//保存原有状态
		cxt.save();
		cxt.beginPath();
		cxt.moveTo(0,500);
		//贝塞尔三次曲线函数绘制曲线
		cxt.bezierCurveTo(330,400,700,550,800,500);
		cxt.lineTo(800,600);
		cxt.lineTo(0,600);
		cxt.closePath();
		cxt.restore();

		//设置地面样式为渐变的绿色
		var landStyle=cxt.createLinearGradient(0,800,0,0);
		landStyle.addColorStop(0,"#040");
		landStyle.addColorStop(1,"#5a0");
		cxt.fillStyle=landStyle;
		cxt.fill();
	};

	//绘制房子
	var drawHouse=function(cxt,x,y,scale){
		cxt.save();
		//图形变换更改坐标系原点所在
		cxt.translate(x,y);
		//图形变换进行缩放
		cxt.scale(scale,scale);

		//屋顶,三角形
		cxt.beginPath();
		cxt.moveTo(2.5,0);
		cxt.lineTo(0,4);
		cxt.lineTo(5,4);
		cxt.closePath();

		cxt.fillStyle="#b71";
		cxt.fill();

		//屋体,正方形
		cxt.fillStyle="#ddd";
		cxt.fillRect(0.5,4,4,4);
		
		//窗户,正方形
		cxt.fillStyle="#9de";
		cxt.fillRect(1,5,1,1);

		cxt.restore();
	};

	//绘制大树
	var drawTree=function(cxt,x,y,scale){
		cxt.save();
		cxt.translate(x,y);
		cxt.scale(scale,scale);

		//绘制树干
		cxt.fillStyle="#b71";
		cxt.fillRect(-0.2,0,0.4,2.5);

		//绘制树冠,圆
		cxt.beginPath();
		cxt.arc(0,0,1,0,2*Math.PI);
		cxt.closePath();

		//设置渐变色,深绿->浅绿
		var grd=cxt.createLinearGradient(0,2,0,0);
		grd.addColorStop(0,"#040");
		grd.addColorStop(1,"#5a0");
		cxt.fillStyle=grd;
		cxt.fill();
		
		cxt.restore();
	};

	//绘制一个星星
	var drawStar=function(cxt){
		var star=new element.Star();
		star.init();
		star.draw(cxt);
	};

	//绘制starSet中的所有星星
	var drawStars=function(cxt){
		for(var i=0;i<starSet.length;i++)
			starSet[i].draw(cxt);
	};

	//绘制文本
	var drawTexts=function(cxt){
		for(var i=0;i<textSet.length;i++)
			textSet[i].draw(cxt);
	};

	//流星下落
	var shootStar=function(cxt){
		for(var i=0;i<shootStarSet.length;i++){
			if(shootStarSet[i].isBegin()){
				shootStarSet[i].move();
				shootStarSet[i].draw(cxt);
			}
		}
	};

	//文本摆动
	var waveTexts=function(cxt){
		for(var i=0;i<textSet.length;i++){
			textSet[i].wave();
			textSet[i].draw(cxt);
		}
	};

	//静态元素
	var drawStaticElements=function(cxt){
		drawBackground(cxt,800,600);
		drawLand(cxt);
		drawStars(cxt);
		drawHouse(cxt,100,400,10);
		drawTree(cxt,680,480,16);
		drawTree(cxt,720,460,25);
	};


	//动态运行效果
	var run=function(cxt,str,starNum,shootStarNum){
		// 初始化文本集合
		initTextSet(str);
		initStarSet(starNum);
		initShootStarSet(shootStarNum);
		// 使流星降落
		setInterval(function(){
			cxt.clearRect(0,0,800,600);

			//绘制静态元素
			drawStaticElements(cxt);
			//流星下落
			shootStar(cxt);
			//文字摆动
			waveTexts(cxt);
			// drawTexts(cxt);
		},10);
	};

	var foo=function(){
		alert(1);
	};

	return{
		getContext:getContext,
		initTextSet:initTextSet,
		initStarSet:initStarSet,
		initShootStarSet:initShootStarSet,
		drawBackground:drawBackground,
		drawLand:drawLand,
		drawHouse:drawHouse,
		drawTree:drawTree,
		drawStar,drawStar,
		drawStars:drawStars,
		drawTexts:drawTexts,
		drawStaticElements:drawStaticElements,
		run:run,
		foo:foo
	};
});

最后程序入口:

index.html

<!DOCTYPE html>
<html>
<head>
	<title>Beautiful Night</title>
	<meta charset="utf8"></meta>
	<style type="text/css">

	</style>
</head>
<body>
	<canvas id="canvas" style="border:1px solid #ddd;"></canvas>
	<script type="text/javascript" data-main="js/main" src="js/require.js" defer async="true"></script>
</body>
</html>

在线演示