面向对象+模块化设计绘制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>
- GITCHAT系列2:个性化推荐
- org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call
- 【Keras】完整实现‘交通标志’分类、‘票据’分类两个项目,让你掌握深度学习图像分类
- PaddlePaddle发布新版API,简化深度学习编程
- 13(01)总结StringBuffer,StringBuilder,数组高级,Arrays,Integer,Character
- thymeleaf模板引擎调用java类中的方法(附源码)
- 由hugepage设置导致的数据库事故(r4笔记第28天)
- 判断js引擎是javascriptCore或者v8
- Springboot系列:Springboot与Thymeleaf模板引擎整合基础教程(附源码)
- 同样的sql执行结果不同的原因分析 (r4笔记第27天)
- 情感分析的新方法,使用word2vec对微博文本进行情感分析和分类
- 垂直属性
- Spring+SpringMVC+MyBatis+easyUI整合进阶篇(二)RESTful API实战笔记(接口设计及Java后端实现)
- 13(02)总结StringBuffer,StringBuilder,数组高级,Arrays,Integer,Character
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法