文章详情
canvas基础—简单粒子动画(上)
标签:
  • canvas
  • javascript
日期:2019-5-17 18:33
摘要:canvas是h5新增的组件,它就像一块幕布,可以用javascript在上面绘制各种图表、动画等...

一、画布概述

canvas是h5新增的组件,它就像一块幕布,可以用js操作它的专属api在上面绘制各种图像和动画,目前兼容到ie9,例如下面这个动画,仅用了四五十行代码就实现了:


二、创建画布

canvas在dom结构里是一个标签,有一个默认的宽度。此时注意,如果我们要设置宽度、高度,必须设置在html标签上面或者用js控制,而不能直接设置在css上:
<canvas id="myCanvas" width="600" height="400" style="background:#f1f1f1;"></canvas>
<script>
var myCanvas = document.getElementById('myCanvas');
myCanvas.width = 600;
myCanvas.height = 400;
</script >

要使用canvas,首先在结构里创建一个canvas标签,然后设置它的宽高,再获取canvas的节点。

拿到节点后再通过api来获取这个画布的上下文对象:

// 初始化canvas,获取上下文对象
var ctx = myCanvas.getContext('2d');

get表示得到,context是上下文的意思。
开头必须这么写,王八的屁股—龟腚,getContext()只有一个参数"2d",在未来,如果canvas标签扩展到支持3d绘图,getContext()方法可能允许传递一个"3d"字符串参数。

拿到上下文对象后,就可以开始画东西了,canvas的坐标系是以左上角作为原点(0,0),例如一块长宽高为600*400的画布,左上角的点坐标是(0,0),右上角的点坐标是(600,0),左下角的点坐标是(0,400),右下角的点坐标是(600,400)。如图:


三、绘制矩形

先绘制一个实心矩形:

// 设置填充的颜色
ctx.fillStyle = '#f60';
// 绘制实心矩形, 参数意思为在坐标(200,100)的位置,宽高为120*60
ctx.fillRect(200, 100, 120, 60);

再绘制一个描边矩形:

// 设置描边的颜色
ctx.strokeStyle = '#f60';
// 设置描边粗细, 不设置也默认为1
ctx.lineWidth = '1';
// 绘制描边矩形, 参数意思为在坐标(200,200)的位置,宽高为120*60
ctx.strokeRect(200, 200, 120, 60);


四、绘制线条和多边形

canvas中可以允许我们绘制自定义的笔触,绘制直线非常简单:

// 开始画路径
ctx.beginPath();
// 将画笔移动到一个位置
ctx.moveTo(100, 100);
// 设置一个点连接上一个位置,画一条线, 暂时看不见
ctx.lineTo(150, 150);
// 描边渲染出来, 此时可以看见了
ctx.stroke();

绘制多边形,其实就是用lineTo绘制多条线,然后闭合:

ctx.beginPath();
ctx.moveTo(350, 100);
ctx.lineTo(500, 200);
ctx.lineTo(500, 80);
// 最后要返回原点闭合, 或者直接使用ctx.closePath()自动闭合
ctx.lineTo(350, 100);
// 设置填充的颜色
ctx.fillStyle = 'skyblue';
// 进行填充
ctx.fill();
// 设置描边的颜色
ctx.strokeStyle = '#000';
// 设置描边粗细
ctx.lineWidth = '2';
// 进行描边
ctx.stroke();


五、绘制弧和圆形

绘制圆弧的api:
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

参数x、y是这个圆弧的圆心,radius是圆弧的半径,startAngle是圆弧开始的位置,endtAngle是圆弧结束的位置,anticlockwise是可选参数,默认为false,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

特别注意参数startAngle和endtAngle在这里并不是角度制,而是弧度制,一个满圆的弧度是2π。

来绘制顺时针和逆时针两个圆弧比较:

ctx.beginPath();
// 顺时针, -2弧度开始到2弧度结束
ctx.arc(100, 330, 40, -2, 2, false);
ctx.stroke();

ctx.beginPath();
// 逆时针, -2弧度开始到2弧度结束
ctx.arc(200, 330, 40, -2, 2, true);
ctx.stroke();

再来绘制一个满圆,从0弧度开始到2π的弧度结束,无所谓顺逆时针,不描边只填充:

ctx.beginPath();
ctx.arc(420, 300, 60, 0, 2 * Math.PI);
ctx.fillStyle = 'seagreen';
ctx.fill();


六、画布的动画和原理

在canvas上制作动画的方式有些特别,先说一个画布的特点,只要是渲染到画布上的元素,它立刻就被像素化,也就是说,画布里的任何元素,你都得不到它的引用对象,不能再拿到这个元素做任何修改。
举个例子,你在画布坐标(100, 100)的地方画了一个圆A,一秒后你想让圆A变到(200, 200)的位置,此时并不能像操作dom结构那样直接改变圆A的位置,而是需要把画布清空,然后在(200, 200)的位置画一个和圆A一模一样的圆B...
这就意味着,要实现动画,必须每帧重新画一次:
清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → ......

清屏的api:

ctx.clearRect(x, y, w, h);

意思是清除一个矩形区域,清除从坐标xy开始,宽w高h的矩形区域,如果画布是是600*400的尺寸,那ctx.clearRect(0, 0, 600, 400)就是清屏的作用了。

来做一个简单的小球动画:

// 把圆心坐标抽成变量
var _x = 30;
var _y = 30;
// 为画布创建定时器, 一秒50帧
setInterval(function() {
// 清屏
ctx.clearRect(0, 0, 600, 400);
ctx.beginPath();
// 画圆
ctx.arc(_x, _y, 15, 0, 2 * Math.PI);
ctx.fillStyle = '#f60';
ctx.fill();
// 画完后改变圆心坐标变量
_x += 6;
_y += 4;
if (_y > 400) {
_x = 30;
_y = 30;
}
}, 20)


七、canvas动画与面向对象

画布上不管有几个元素在运动,一定只有一个定时器,这个定时器负责每一帧的清屏和绘制下一帧内容,如果页面上有很多元素在运动,这些元素的信息和状态真的难以维护,这时需要使用面向对象的思想来管理。

<body>
<canvas id="myCanvas" width="600" height="400" style="background: #f1f1f1;"></canvas>
<script type="text/javascript">
// 获取画布节点
var myCanvas = document.getElementById('myCanvas');
// 设置画布的宽高
myCanvas.width = 600;
myCanvas.height = 400;
// 获取画布的上下文对象
var ctx = myCanvas.getContext('2d');
// 小球的构造函数, 传坐标, 半径和速度形参
function Ball(x, y, r, speed) {
this.x = x;
this.y = y;
this.r = r;
this.speed = speed;
// new出来的时候直接调用下面写的渲染方法把小球放到画布上
this.render();
}
// 渲染小球的方法
Ball.prototype.render = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = 'skyblue';
ctx.fill();
}
// 更新小球实例的字段的方法
Ball.prototype.update = function() {
this.x += this.speed * 3;
this.y += this.speed * 2;
if (this.y > 400) {
this.x = 30;
this.y = 30;
}
}

// 创建出来的小球实例放在这个数组里
var ballArr = [];
// 每个小球实例的实参都可以不同
ballArr[0] = new Ball(30, 30, 10, 2);
ballArr[1] = new Ball(30, 70, 15, 1.5);
ballArr[2] = new Ball(30, 110, 20, 1);

// 画布唯一定时器, 每秒50帧, 每一帧都先清屏
setInterval(function() {
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
for (var i = 0; i < ballArr.length; i++) {
// 每个小球的实例调用自己的update方法来改变自己的坐标字段
ballArr[i].update();
// 每个小球的实例调用自己的render方法来绘制改变坐标后的小球
ballArr[i].render();
}
}, 20);
</script>
</body>

我们不要用全局变量来维护某一个小球的x、y等各种信息,而是应该用对象来封装它,把画布上的每一个元素都看成一个演员,把他们放入数组,每一帧都让这些演员做出相应的改变,然后再渲染出新的一帧。


发送
评论(0)