canvas 制作动画(上)

  • canvas 制作动画(上)
    • 1. 画布中的动画
    • 2. 创建动画循环
      • 2.1 循环
      • 2.2 更新、清除、绘制
    • 3. 记忆要绘制的形状
      • 3.1 错误的方法
      • 3.2 正确的方法
      • 3.3 随机产生现状

canvas 制作动画(上)

1. 画布中的动画

实际上,画布中的动画与一般的动画在理论上并没有太大的区别。深入研究动画的本质可以发现:动画只是一连串的图像,每个图像之间的差别非常微小,并且它们以极快的速度连续显示。其中的奥妙就在于,每秒钟显示的图像非常多,人的肉眼通常认为自己看到的是一个正在运动的物体,而不是一张张连续显示的静态图像。其原理就像翻书一样,如果翻书的速度足够快,一些小图形看上去就好像在运动。

当然,你不能在画布中翻页,因此需要采用其他方法来创建动画效果。仔细思考一下,你就会发现当使用代码创建动画时,只有一张纸可用,这张纸就是计算机屏幕。这意味着不可能载入一张张图像并通过手工翻页的方式来创建动画,因此需要寻找其他途径解决这个问题。连续载入一系列图像其实和翻页的效果是相同的,首先在屏幕上绘制一些对象(第一个页面),接着清除屏幕上的对象(第一页与第二页之间的过渡),然后快速在屏幕上绘制其他对象——更新图像(第二个页面)。这和翻书的原理其实是相同的,只是执行方式略有不同而已。

现在,你已经了解了在画布中创建动画所需的有关知识。你知道如何在屏幕上绘制对象,以及如何从屏幕上清除对象。这部分内容比较简单。较难的部分是如何使整
个过程自动化,使动画在每秒钟能够发生很多次。另外,准确记忆你需要绘制的动画内容及其位置也比较难。当然,虽然我说这部分内容比较难,但其实当你真正学习起来就会觉得并不难。

2. 创建动画循环

动画循环是创建动画效果的基础,创建动画循环其实非常简单。需要说明的是,虽然这里我们称它为循环,但其实它和循环并没有什么联系,其工作原理也不同。这里之所以称作循环,是因为它在重复发生,稍后你将看到这种效果。

动画循环的三要素是:更新需要绘制的对象(如移动对象的位置)、清除画布在画布上重新绘制对象。本文即将创建的动画与你执行任务的先后顺序并没有实际关系,但请务必注意:在清除对象之前不要绘制对象,否则你将看不到任何对象!

2.1 循环

下面我们开始创建动画循环。

const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');const canvasWidth = canvas.width;
const canvasHeight = canvas.height;function animate() {setTimeout(animate, 33);
}
animate();

在以上代码中,其实你只需关注animate函数,它现在非常简单。实际上,animate函数使用setTimeout方法设置了一个定时器,setTimeout方法每隔33毫秒调用一次animate函数,但这常常会创建一个无限循环。在循环外部调用animate函数即可启动该循环。也许你并不希望循环一直运行下去,因为这会消耗不必要的计算机资源,所以最好添加一个结束开关。
canvas元素之后添加以下按钮代码:

<div><button id="startAnimation">Start</button><button id="stopAnimation">Stop</button>
</div>

然后在animate函数上方添加处理按钮的逻辑。

let playAnimation = true;const startButton = document.getElementById('startAnimation');
const stopButton = document.getElementById('stopAnimation');startButton.style.display = 'none';
startButton.onclick = function() {startButton.style.display = 'none';stopButton.style.display = 'inline-block';playAnimation = true;animate();
}stopButton.onclick = function() {stopButton.style.display = 'none';startButton.style.display = 'inline-block';playAnimation = true;playAnimation = false;
}

以上代码的逻辑非常简单:让playAnimation变量保存一个布尔值,用于停止或播放动画循环。然后为每个按钮添加了单击事件,用于隐藏刚刚单击过的按钮并显示另外一个按钮,然后将playAnimation变量设置为正确的值。启动按钮与其他按钮略有不同,因为动画停止以后,需要再次手动启动循环。为此,只需在代码中添加一个额外的animate函数调周用即可。

这些实际上都还不会对动画循环产生影响。要想控制播放,需要在setTimeout方法前面添加一个条件语句。

if (playAnimation) {setTimeout(animate, 33);
}

如果playAnimation变量保存了一个false值,那么动画循环将会停止运行。尝试运行下吧,虽然你不会看到任何动画,但该按钮应该能够提供启动和停止功能。

注意:为什么在动画循环中使用33毫秒作为时间间隔呢?动画在每秒钟需要的帧数通常介于 25 到 30 帧之间。1秒是1000毫秒,因此用1000除以 30 得到 33 毫秒,当然,也可以将它设置为不同的数值来使动画加速或减速。可以根据自己的需要,将动画效果的时间间隔调整为 30 或 40 毫秒。

2.2 更新、清除、绘制

现在已经建立了基本动画循环,接下来可以开始添加前面提到的更新清除绘制过程了。我们建立一个简单的动画,使一个正方形每帧向右移动 1 像素。首先,需要在animate函数外部建立一个变量,用于保存当前正方形的 x 位置:let x = 0;

现在你有了一种记住正方形在每个循环中所处位置的方式,可以一次性添加这三个过程(更新、清除、绘制)。在animate函数内部的setTimeout前面添加以下代码:

x++;
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.fillRect(x, 250, 10, 10);

第一行代码用于更新正方形的 x 位置,每循环一次该值增加1,变量后面的两个加号表示在现有值上增加1。第二行代码是清除过程,可以将画布上的对象有效地擦除干净,为第三行代码绘制正方形做好准备。第三行代码的作用是实现最后一个过程。将变量 x 添加到fillRect方法调用中,说明绘制正方形始终从当前的 x 位置开始,该值始终在增加。

如果一切正常,你应该看到一个黑色的小正方形在画布上移动。单击Stop即按钮,该正方形将停止运动;单击Start按钮,该正方形会重新移动。

3. 记忆要绘制的形状

在创建动画循环时,需要克服的一个主要问题是:准确记忆要绘制的对象的内容及位置。上一节已经简要涉及了该内容,我们采用的方法是:在循环外部设置一个变量来保存需要绘制的对象的位置值,并通过fillRect调用和在循环内部使用位置变量,就可以获取需要绘制的对象的内容及位置信息。但是,如果需要绘制多个动画形状该怎么办呢?甚至在创建循环时你都不知道需要创建多少种动画形状又该怎么办?因此,我们需要采用一种更好的方法。

3.1 错误的方法

你也许试图在动画循环外部使用独立的变量来存储每种形状的位置值。为什么不可以呢?既然这种方法适用于一种形状,那么为什么不适用于多个形状呢?你试图采用的方法是正确的,这样做可以达到目的,但这种方法非常笨拙,需要复制大量代码,并且以后修改这些形状也会非常复杂。
以下代码展示了如何通过修改上一节的代码来使多个形状产生动画效果。

let firstX = 0;
let secondX = 50;
let thirdX = 100;function animate() {firstX++;secondX++;thirdX++;context.clearRect(0, 0, canvasWidth, canvasHeight);context.fillRect(firstX, 50, 10, 10);context.fillRect(secondX, 100, 10, 10);context.fillRect(thirdX, 150, 10, 10);if (playAnimation) {setTimeout(animate, 33);}
}
animate();

这里特意把这段代码单独列出来,而忽路了其他代码(例如,按钮事件),因为目前我们只需关注这部分内容。

这个例子和前面例子的唯一不同之处在于代码中使用了3次位置变量,并3次调用了
fillRect方法。

这种方法在此处也许很管用,但是,如果需要绘制上百个形状该怎么办呢?你会一本正经地告诉我,你会编写上百个位置变量并上百次调用fillRect方法吗?当然不会!因为代码能帮你自动地让对象产生动画效果,这才是使用代码创建动画的真正原因。

3.2 正确的方法

既然使用上面的方法创建多个变量既冗长又复杂,那我们该如何使其简化呢?简而言之,可以考虑使用对象和数组来实现。实际上,需要解决的问题有两个:第一,不管形状的数量有多少,首先考虑如何存储每个形状的位置值;第二,在不复制代码的情况下如何绘制每个形状。

第一个问题的解决方法非常简单,因此我们将直接介绍第二个问题的解决方法。你已经知道了每个形状所需的位置数据,即 x 的值。但是,我们还需要进一步深入研究,考虑同时使用 y 的值。如果知道每个形状具有两个相同类型的位置值,就可以通过创建JavaScript类来创建形状对象,代码如下所示:

const Shape = function (x, y) {this.x = x;this.y = y;
}

简言之,通过使用对象的属性,可以为某些对象定义模板。

但是,仅仅定义对象还不够,还需要通过某种方式来存储对象,这样就不必手动引用上百种形状了。为此,可以在数组中存储形状对象,这样能够把它们按顺序存储在同一个变量中:

const shapes = new Array();
shapes.push(new Shape(50, 50));
shapes.push(new Shape(100, 100));
shapes.push(new Shape(150, 150));

向数组中添加形状对象时,可以使用Array对象的push方法。在本例中,该对象是一个形状对象。

现在就得到了一组形状,每个形状都有不同的 x 和 y 值,这些值存储在一个数组中(这个数组已经被赋给shapes变量)。接下来的任务是如何将这些形状从数组中取出来并更新它们的位置(使它们产生动画效果),然后绘制这些形状。为此,需要在动画循环内部设置一个for循环:

function animate() {context.clearRect(0, 0, canvasWidth, canvasHeight);const shapesLength = shapes.length;for (let i = 0; i < shapesLength; i++) {const tmpShape = shapes[i];tmpshape.x++;context.fillRect(tmpShape.x, tmpShape.y, 10, 10);}if (playAnimation) {setTimeout(animate, 33);}
}

for循环遍历了数组中的每个形状,并将形状赋给了tmpShape变量,因此很容易访问它。现在你已经有了对数组中当前形状的引用,下面只需更新形状的 x 属性,然后使用 x 和 y 属性在当前位置绘制形状就可以了。

以下是完整的代码,它和上一节示例所产生的动画效果是完全相同的:

const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');const canvasWidth = canvas.width;
const canvasHeight = canvas.height;let playAnimation = true;const startButton = document.getElementById('startAnimation');
const stopButton = document.getElementById('stopAnimation');startButton.style.display = 'none';
startButton.onclick = function () {startButton.style.display = 'none';stopButton.style.display = 'inline-block';playAnimation = true;animate();
}stopButton.onclick = function () {stopButton.style.display = 'none';startButton.style.display = 'inline-block';playAnimation = true;playAnimation = false;
}const Shape = function (x, y) {this.x = x;this.y = y;
}const shapes = new Array();shapes.push(new Shape(50, 50));
shapes.push(new Shape(100, 100));
shapes.push(new Shape(150, 150));function animate() {context.clearRect(0, 0, canvasWidth, canvasHeight);const shapesLength = shapes.length;for (let i = 0; i < shapesLength; i++) {const tmpShape = shapes[i];tmpShape.x++;context.fillRect(tmpShape.x, tmpShape.y, 10, 10);}if (playAnimation) {setTimeout(animate, 33);}
}animate();

3.3 随机产生现状

现在已经可以采用一种快速而简单的方法来创建形状了,接下来就是如何产生随机形状。因为正在使用对象,所以解决这个问题非常简单。首先,需要更改Shape类来定义形状的宽度和高度:

const Shape = function (x, y, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;
}

然后,为每个形状随机选取起始位置和大小,并使用以下代码替换shapes.push语句:

for (let i = 0; i = 10; i++) {let x = Math.random() * 250;let y = Math.random() * 250;let width = height = Math.random() * 50;shapes.push(new Shape(x, y, width, height));
}

另外,还需要更改对fillRect方法的调用,以便采用新的宽度和高度:

context.fillRect(tmpShape.x, tmpShape.y, tmpShape.width, tmpShape.height);

就这么简单。使用类和对象的优点在于,无须编辑太多的代码来添加或别除新特性。如果需要通过新示例来验证该特性,可以选择10个不同大小和位置的形状,并让它们在测览器上移动。

canvas 制作动画(上)相关推荐

  1. html5在线制作教程,HTML5 Canvas 制作动画

    HTML5 Canvas 制作动画 在HTML5 canvas中绘制图像动画效果,你需要绘制出每一帧的图像,然后在一个极短的时间内从一帧过渡到下一帧,形成动画效果. 在线示例 要在HTML5画布上绘制 ...

  2. canvas 制作动画(下)

    canvas 制作动画(下) 1. 改变方向 在 canvas 制作动画(上)已经介绍了如何创建动画,但还没有讨论如何控制形状动画的方式.我觉得只有直线动画会让人觉得很枯燥,不知你是否也这样想. 你已 ...

  3. HTML5 canvas 制作动画原理

    使用HTML5画布能够帮助我们快速实现简单的动画效果,基本原理如下: 每隔一定时间绘制图形并且清除图形,用来模拟出一个动画过程,可以使用context.clearRect(0, 0, x, y)方法来 ...

  4. 前端制作动画的几种方式(css3,js)

    制作动态的网页是是前端工程师必备的技能,很好的实现动画能够极大的提高用户体验,增强交互效果,那么动画有多少实现方式,一直对此有选择恐惧症的我就总结一下,以便在开发的时候选择最好的实现方式. 1.css ...

  5. HTML设置页面动画效果有几种,前端制作动画的几种方式(css3,js)

    制作动态的网页是是前端工程师必备的技能,很好的实现动画能够极大的提高用户体验,增强交互效果,那么动画有多少实现方式,一直对此有选择恐惧症的我就总结一下,以便在开发的时候选择最好的实现方式. 1.css ...

  6. Canvas制作的下雨动画

    简介 在codepen上看到一个Canvas做的下雨效果动画,感觉蛮有意思的.就研究了下,这里来分享下,实现技巧.效果可以见下面的链接. 霓虹雨: http://codepen.io/natewile ...

  7. php 3d animation,如何用HTML5的Canvas制作3D动画效果

    HTML5的诞生给web前端界带来了不小轰动,像什么动画旋转.图片滑块.图片轮播等等这些3D特效,也引发了不少朋友想要学习HTML5的好奇心.最近我一直在做canvas动画效果,发现canvas这个东 ...

  8. html制作图片动画效果代码,HTML5 Canvas:制作动画特效

    编辑推荐: 本文来自于jquery之家 ,html5制作canvas动画的基本步骤,控制canvas动画和实例代码. 要在 HTML5 canvas 中绘制图像动画效果,你需要绘制出每一帧的图像,然后 ...

  9. html5canvas效果跳一跳小游戏,HTML5 Canvas:制作动画特效

    编辑推荐: 本文来自于jquery之家 ,html5制作canvas动画的基本步骤,控制canvas动画和实例代码. 要在 HTML5 canvas 中绘制图像动画效果,你需要绘制出每一帧的图像,然后 ...

最新文章

  1. java将实体类转化为json_只有实践才能将知识转化为智慧
  2. graphpad两组t检验_Graphpad 作图教程 | 手把手教你绘制森林图
  3. 卧槽!微信可以改彩色昵称了!又get一个撩妹小技巧 ~~
  4. C++学习笔记(八)——内联函数
  5. mysql卸载安装视频_MYSQL安装与卸载(一)
  6. koa2 仿知乎笔记
  7. windows 程序设计及API
  8. linux如何控制ftp不能get,ftp get/push连接到linux时,都是使用的linux命令
  9. IAR软件中直接查看编译后代码大小
  10. 工业物联网卡未来发展的优势和特点
  11. 基于WebRTC开源框架的实时视频聊天项目,搭建私人实时通信服务
  12. 如何理解希尔伯特空间?
  13. 满满干货!邮储银行java面试
  14. 电子电路基础 (12)——功率放大电路原理分析
  15. UBUNTU-Mplay
  16. 做一个接地气的产品经理
  17. 最低销售量计算机公式,最低、最高、安全库存量的计算公式
  18. 《星际迷航 超越星辰》
  19. CentOS8 图形界面和命令行切换
  20. cgal配置以及一些资料

热门文章

  1. 考研英语(41-50)转自何凯文老师
  2. oppo人脸识别解锁黑屏_OPPO Find X人脸解锁如何设置?这四点事项要注意!
  3. Windows系统VisualSVN-Server下载安装和配置
  4. 大数据Map Reduce 和 MPP数据库 的区别
  5. 35岁男子辛酸自述:整整5个月没工作了,我该如何养家糊口?
  6. Herd Sums(hdu 2715)
  7. 华为鸿蒙系统手机生态,暂无华为以外手机厂商明确表示手机使用鸿蒙系统,生态建设不完善或成主因...
  8. Linux上全源码安装Apache、MySQL、PHP、WSF安装(转载)
  9. 【逗老师的无线电】MMDVM添加4G网卡之后变身4G路由器
  10. 【人工智能算法】受大自然启发的算法之交叉和突变