canvas 制作动画(下)

1. 改变方向

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

你已经知道了如何让一个形状向右移动(把 x 的值增加),但是如果需要改变运动的速度又该如何实现呢,或者如何改变动画的方向呢?非常简单:只需要增加(或减少)x 和 y 值就可以了。如果你使用与 canvas 制作动画(上) 中示例相同的代码,按以下方式修改tmpShape.x++语句,就可以非常方便地使形状沿着向右的对角线方向运动:

tmpShape.x += 2;
tmpShape.y++;

与前面代码的不同之处在于,上面的代码将 x 值每次增加2,而不是增加1,并将y 值每次增加 1。这样产生的效果是,在每次动画循环中,每个形状向右移动 2 像素,并向下移动1像素,即为向右的对角线方向移动。

或者还可以实现一些非常有趣的效果。例如,在每个动画循环中将 x 和 y 值设为随机值。这样产生的动画效果就具有不可预测性和无序性,形状将表现为不规则的运动形式。但这种方法可以让对象的运动更加生动自然:

tmpShape.x += Math.random() * 4 - 2;
tmpShape.y += Math.random() * 4 - 2;

以上代码的作用是产生一个介于 0 到 4 之间的随机数(Math.random产生一个 0 到 1 之间的数,然后将该数乘以 4),然后减去 2 得到一个介于 -2 到 2 之间的随机数。通过这种方法,形状可以向右运动(x 值为正数)、向左运动(x 值为负数)、向上运动(y 值为正数)和向下运动(y 值为负数)。

如果你在浏览器中尝试该方法,形状将会前后随机运动,并出现摆动现象。

2. 圆周运动

形状不一定始终沿着直线运动。如果你需要的动画效果是沿着圆周运动,例如,沿着圆形轨道运行,该如何实现呢?好消息是,这是完全可以实现的,并且不需要使用太多代码。坏消息是,这里需要使用三角函数的相关知识,可能需要你稍微动一下脑筋。

概念非常简单:将一个形状放在圆周的边缘处(它的周长上),以圆周的任意位置作为起点。但为了简单起见,可以将形状放在周长上角度为0弧度的位置,该位置位于右手边。在每次动画循环中,只需要增加位于圆周上的形状的角度,就可以使形状沿着圆周运动。这非常简单,接下来我们具体讨论如何实现。

2.1 三角函数

需要解决的问题是:如何计算位于圆周上的形状的(x, y)坐标值。其实很简单。当然,只有用正确的方式来考虑需要解决的问题,才会觉得它容易。

在解决问题之前,首先需要知道圆的实际大小。可以选择任意大小的圆周,毕竟,这里只是示例,所以实际大小并不重要。重要的是可以通过半径(从圆心到圆周的长度)来描述圆的大小。如果画出运动轨道所在圆周的半径,那么你会发现形状移动的角度遵循一种模式。如果你认真地看,或者稍微思考一下,也许就会发现这种模式。如果幸运的话,你会发现三角形的边存在一些规律。

圆周中包含了一个三角形。但它有何用处呢?这个三角形能够提供一些准确的信息,帮助你计算形状沿圆周移动到新位置处的(x, y)坐标值。更具体地说,现在得到了一个三角形和两个角度(沿圆周转动的角度和三角形的 90 度直角),接下来可以构造一些基本三角形来计算你需要的值。这也体现了数学的重要作用。但是,在真正解决问题之前,我还要简要解释一下三角函数的原理。

三角函数的基本要点是:如果已知一个三角形的一个角是 90 度,并且已知另外一个角,那么就可以计算三角形的边长之间的比值。然后,可以通过该比值来计算边的长度,边的长度单位是任意的,本示例中边的单位是像素。因此,你需要知道三角形的哪条边是需要计算的长度,因为它们分别对应着不同的三角函数规则。这三条边分别是斜边(最长的边)、邻边(与除直角以外的已知角相邻的边)和对边(与已知角相对的边)。

要计算边之间的比值,需要用3种三角函数:正弦函数(sin)、余弦函数(cos)或正切函数(tan)。正弦函数是对边与斜边的比值,余弦函数是邻边与斜边的比值,正切函数是对边与邻边的比值。通过把三角形中的已知角代人正确的函数,可以计算出所需的比值来。

在此,我们需要知道三角形的邻边和对边的长度,它们分别代表 x 和 y 的位置。要计算这些边的长度,首先需要在对应的三角函数中通过已知角计算比值。在JavaScriptr中,可以使用Math对象来计算这些比值:

const angle = 45;
const adjRatio = Math.cos(angle * (Math.PI / 180)); // 余弦-邻边-斜边
const oppRatio = Math.sin(angle * (Math.PI / 180)); // 正弦-对边-外边

你会注意到,Math对象的cossin方法中执行了一些简单的计算过程。这种计算是为了将角从角度转换为弧度,因为 JavaScript 使用的单位是弧度。如果你在开始就使用弧度制,就不需要做任何转换了。

得到这些比值仅仅完成了一半的工作量。另外一半工作才是最终我们需要得到的答案,将这些比值与斜边(因为它是半径,所以长度已知)的长度相比较。最终的答案可以由半径乘以该比值得到,即:

const radius = 50;
const x = radius * adjRatio;
const y = radius * oppRatio;

2.2 运用

既然你能够计算位于圆周上某个角度的形状对应的(x, y)坐标值,那么把这些结果综合应用于当前的示例就非常简单了。第一步是更新Shape类,并向其中添加几个新属性:

const Shape = function (x, y, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;this.radius = Math.random() * 30;this.angle = 0;
}

这两个属性用于设置起始角度和计算圆周的随机半径(介于0~30之间)。倒数第二步是使用以下代码替换动画循环中的现有代码,从而更新形状:

const x = tmpShape.x + (tmpShape.radius * Math.cos(tmpShape.angle * (Math.PI / 180)));
const y = tmpshape.y + (tmpShape.radius * Math.sin(tmpShape.angle * (Math.PI / 180)));tmpShape.angle += 5;
if (tmpShape.angle > 360) {tmpShape.angle = 0;
}

前两行代码没有什么新内容,它们分别用于计算位于圆周上当前角度的形状所对应的 x 和 y 值,其中圆周是通过半径来定义的。这里的 x 和 y 值能够提供坐标值(假设圆周中心的坐标为(0, 0)),因此,当将 x 和 y 值添加到形状中对应的点(x, y)时,就可以把形状移动到正确的位置。注意,形状对象中定义的点(x, y)现在引用的是圆周的中心——形状围绕它旋转的点,而不是形状的起点。最后几行代码用于在每个动画循环中增加角的度数,如果角度超过 360 度(一个完整的圆),则将角度重新设置为 0 度。

最后,将新的 x 和 y 变量添加到fillRect方法中:

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

如果一切运行正常,就可以选择不同的形状,让它们沿着不同的圆周运动。

以下是完整的代码供你参考。

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, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;this.radius = Math.random() * 30;this.angle = 0;
}const shapes = new Array();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));
}function animate() {context.clearRect(0, 0, canvasWidth, canvasHeight);const shapesLength = shapes.length;for (let i = 0; i < shapesLength; i++) {const tmpShape = shapes[i];const x = tmpShape.x + (tmpShape.radius * Math.cos(tmpShape.angle * (Math.PI / 180)));const y = tmpShape.y + (tmpShape.radius * Math.sin(tmpShape.angle * (Math.PI / 180)));tmpShape.angle += 5;if (tmpShape.angle > 360) {tmpShape.angle = 0;}context.fillRect(x, y, tmpShape.width, tmpShape.height);}if (playAnimation) {setTimeout(animate, 33);}
}animate();

3. 反弹

如果你希望形状能够感知周围的环境,或者在边界处反弹回来怎么办呢?这种行为可以避免机械性的动画,使动画更加自然和随机。

在学习如何实现这种行为之前,先用编写如下代码:

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, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;
}const shapes = new Array();for (let i = 0; i < 10; i++) {let x = Math.random() * 250;let y = Math.random() * 250;let width = height = Math.random() * 30;shapes.push(new Shape(x, y, width, height));
}function animate() {context.clearRect(0, 0, canvasWidth, canvasHeight);const shapesLength = shapes.length;for (let i = 0; i < shapesLength; i++) {const tmpShape = shapes[i];context.fillRect(tmpShape.x, tmpShape.y, tmpShape.width, tmpShape.height);}if (playAnimation) {setTimeout(animate, 33);}
}animate();

这些代码建立了一个完整的动画循环,该循环将遍历10个随机生成的形状。实际上,代码并没有在视觉上移动任何形状,因为我们没有修改动画循环中形状的属性(如,增加的值将形状向右移动)。

使形状感知画布边界的过程其实非常简单。假设一个形状在每个循环中向右移动 1像素。一旦该形状移动到画布的右边界处(假设是500像素),它将会继续移动,并且 x 值仍在增加,但我们就无法在画布上看到它了。其实你希望该形状发生的行为是:形状在画布的右边界处反弹回来,就好像边界处有一堵墙一样。为此,你需要检查形状是否超过了画布的右边界,如果已经到达边界处,则反向改变形状运动的方向,这样它就会反弹回来。

计算一个形状是否超过画布的右边界其实就是检查形状的 x 位置是否超过了画布的宽度。如果形状的 x 位置大于画布的宽度,那么形状必然会超出右边界。同样,检查形状是否超过画布的左边界也可以采用这种方法。其中,形状的左边界的位置对应的值为0。检查形状的 x 位置是否小于0,就可以确定形状是否位于画布的左边界之外。当然,也可以使用同样的方法检查形状是否位于画布的上边界和下边界。具体做法是,检查 y 值是否小于上边界 0,并检查 y 是否大于画布的高度(下边界)。

综合运用这些方法,可以创建一组简单的逻辑:让形状在画布的边界处弹回。第一步是向Shape类中添加一些新属性,它们将用于定义形状是否碰到边界及反弹的路径方向:

  this.reverseX = false;this.reverseY = false;

默认情况下,这些属性的值为false,在本示例中,这表明形状将一直向右下方运动。下一步是添加逻辑关系来检查形状是否超出了画布边界。在动画循环的fillRect调用下面插人以下代码:

if (tmpShape.x < 0) {tmpShape.reverseX = false;
} else if (tmpShape.x + tmpshape.width >> canvasWidth) {tmpShape.reverseX = true;
}if (tmpshape.y < 0) {tmpShape.reverseY = false;
} else if (tmpShape.y + tmpShape.height > canvasHeight) {tmpShape.reverseY = true;
}

当形状即将到达边界之外时,这些检查将反向改变形状的运动路线。但是,设置布尔值并不能实际改变形状的具体运动方向,因此,需要另外进行一些检查。此时,需要将它们放在fillRect调用的上面:

if (!tmpShape.reverseX) {tmpShape.x += 2;
} else {tmpShape.x -= 2;
}if (!tmpshape.reverseY) {tmpShape.y += 2;} else {tmpShape.y -= 2;
}

如果形状在 x 轴上没有反转,那么这些检查将会使形状向右移动(通过增加 x 位置)。如果形状在 x 轴上反转,那么这些检查将会使形状向左移动(通过减少 x 位置)。同样,在 y 轴上也可以执行相同的检查。
由于有了这些相对简单的逻辑检查,你可以使一组形状移动到画布的边界时反弹回来。甚至可以更改 Shape 类表示反转方向的属性的默认值,从而改变形状的运动方式。

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

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

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

  2. canvas 制作动画(上)

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

  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. html制作图片动画效果代码,HTML5 Canvas:制作动画特效

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

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

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

  9. html5的canvas动画效果,HTML5 Canvas:制作动画特效

    要在HTML5 canvas中绘制图像动画效果,你需要绘制出每一帧的图像,然后在一个极短的时间内从一帧过渡到下一帧,形成动画效果.这其实是一种视觉欺骗,原理就像播放电影一样,胶片上每一格是一帧图片,然 ...

最新文章

  1. 6/29 原型编码阶段:(2) GridView的数据库操作
  2. uart与usart区别
  3. 微信小程序无法获取UnionId的情况及处理
  4. 计时器StopWatch示例
  5. python mysql异地备份_python脚本备份mysql数据库
  6. oracle安装 redo log file,Oracle Dump Redo Log File 说明
  7. Linux命令替换字符串
  8. [导入]将asp.net usercontrol(用户控件页)转变为普通控件
  9. 半小时训练亿级规模知识图谱,亚马逊这个 AI 框架要火!
  10. VK Cup 2018 Round 1: A. Primal Sport
  11. 学习c3p0连接池的原理与使用总结
  12. Chapter第六章
  13. C++ primer 5th 习题之10.13
  14. 一文教会你导出微信聊天记录
  15. 信号处理--傅里叶变换的性质及常用信号的傅里叶变换
  16. Android5.1-s5p6818平台adb push 、adb install/uninstall的疑问
  17. PostgreSQL+PostGIS下载和离线安装
  18. 何为挂载(mount)?
  19. 如何用ps裁剪规定像素的图片
  20. EventBus的介绍和使用

热门文章

  1. 微信公众平台深度开发JAVA版第一季 16.响应被动消息4
  2. noxctf2018_grocery_listactf_2019_message
  3. Java面试知识点总结1
  4. 千锋很火的SpringBoot实战开发教程视频
  5. 畅捷通软件重装系统应注意的问题和步骤
  6. 从高宏伟处借来的HOOK API代码
  7. 二进制小游戏 猜生肖
  8. 使用 Passenger +Apache扩展 Puppet,代替其Webrick的web框架
  9. Elasticsearch-ik同义词,近义词,联想词
  10. matlab计算成绩等级