经过前面的文章,我们已经能够在canvas画布上画出各种炫酷的图形和画面,但是这些画面都是禁止的,怎么样才能让他们动起来呢?

  • 如何绘制基本图形可以参考:canvas基本图形绘制
  • 如何对基本图形移动旋转缩放可以参考:canvas图形变换
  • 如何设置基本图形颜色和样式可以参考:canvas样式和颜色
  • 如何使用外部图片以及图形组合可以参考:canvas使用图片,图形组合以及裁剪
  • canvas如何保存和加载图像可以参考:canvas图像保存
  • canvas系列教程可以参考:canvas

动画的基本步骤

我们知道,动画是一帧一帧的画面不断反映实现的,人的眼睛看到一幅画或一个物体后,在0.34秒内不会消失。利用这一原理,在一幅画还没有消失前播放下一幅画,就会给人造成一种流畅的视觉变化效果。在canvas中,就是在绘制完当前画面之后,快速的绘制下一个画面。步骤如下:

  • 清空canvas。

    • 除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有画布上的内容。最简单的做法就是用clearRect方法。
  • 保存canvas状态。
    • 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
  • 绘制动画图形(animated shapes)。
    • 这一步才是重绘动画帧。
  • 恢复 canvas 状态。
    • 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

操纵动画

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。

因此,为了实现动画,我们需要一些可以定时执行重绘的方法。window对象提供了下面的方法实现定时动画:

  • setInterval(function, delay)当设定好间隔时间后,function会定期执行
  • setTimeout(function, delay)在设定好的时间之后执行函数
  • requestAnimationFrame(callback)告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。

如果你并不需要与用户互动,你可以使用setInterval()方法,它就可以定期执行指定代码。如果我们需要做一个游戏,我们可以使用键盘或者鼠标事件配合上setTimeout()方法来实现。通过设置事件监听,我们可以捕捉用户的交互,并执行相应的动作。

window.requestAnimationFrame()这个方法提供了更加平缓并更加有效率的方式来执行动画,当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟回调函数执行60次,也有可能会被降低。

在使用window.requestAnimationFrame()方法的过程中,我推荐使用下面的兼容性方法来代替:

window.requestAnimationFrame = (function(){return  window.requestAnimationFrame       ||window.webkitRequestAnimationFrame ||window.mozRequestAnimationFrame    ||window.oRequestAnimationFrame      ||window.msRequestAnimationFrame     ||function (callback) {window.setTimeout(callback, 1000 / 60);};
})();

canvas动画实例-模拟小球自由落体运动

上面介绍了canvas动画的基本概念,接下来我们将会在canvas中实现小球下落的动画。小球的完整代码再本文结尾。点击可跳转到结尾。

绘制小球

首先需要在canvas上绘制一个小球。

var ctx = document.getElementById('canvas').getContext('2d');
if (!ctx) {console.log('您的浏览器不支持canvas');// 可以抛出异常强制结束JS执行throw new Error("Do not support canvas");
}var ball = {x: 100,  // 小球的x坐标y: 100,  // 小球的y坐标radius: 25,  // 小球半径color: 'cyan', // 小球颜色draw: function() {  // 绘制小球的函数ctx.beginPath();ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);ctx.closePath();ctx.fillStyle = this.color;ctx.fill();},clear: function() {  // 清除小球区域的函数ctx.clearRect(this.x - this.radius,this.y - this.radius,this.radius * 2,this.radius * 2);}
}
ball.draw(); // 绘制小球

添加运动描述

绘制了小球之后,要添加动画,还需要为小球添加速率矢量进行移动。另外,速度也是变化的量,对于只有落体运动,还有竖直方向的重力加速度,所以还需要为小球加上加速度。

var ball = {x: 100,  // 小球的x坐标y: 100,  // 小球的y坐标vx: 0,   // 小球水平方向速度vy: 0,   // 小球竖直方向速度ax: 0,   // 小球水平方向加速度ay: 0,   // 小球竖直方向加速度dt: 1,   // 两帧之间的时间为1个单位时间radius: 25,  // 小球半径color: 'cyan', // 小球颜色s: function(v, a, t) {// 匀加速直线运动的位移公式:s=vt+1/2at^2return v * t + (1 / 2.0) * a * t * t;},dx: function() {// 计算水平方向的位移return this.s(this.vx, this.ax, this.dt);},dy: function() {// 计算竖直方向的位置return this.s(this.vy, this.ay, this.dt);},next: function() {// 计算小球下一时刻的位移this.x += this.dx();this.y += this.dy();// 计算小球下一时刻的速度:v_t = v_0 + a*tthis.vx = this.vx + this.ax * this.dt;this.vy = this.vy + this.ay * this.dt;this.boundary(0, canvas.width, canvas.height, 0);},};

假设每一帧之间的时间是单位时间,那么根据当前小球的位置速度和加速度,我们就可以计算下一帧的小球的位置和速度,此时清空上一帧的canvas,再绘制下一帧,即可实现动画效果。

var animate; // 记录动画
ball.draw();// 绘制一帧
function draw() {// 1:清空画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 2:绘制小球ball.draw();// 3:计算小球的下一个状态ball.next();// 4:进入下一帧animate = window.requestAnimationFrame(draw);
}

边界处理

若没有任何的碰撞检测,我们的小球很快就会超出画布。我们需要检查小球的 x 和 y 位置是否已经超出画布的尺寸以及是否需要将速度矢量反转。

boundary: function(top, right, bottom, left) {// 检测小球下一帧是否出界,出界则补正if (this.y > bottom) {  // 下边界越界this.vy = -this.vy;  // 速度反向} else if (this.y < top) {this.vy = -this.vy;} else if (ball.x > right) {this.vx = -this.vx;  // 速度反向} else if (ball.x < left) {this.vx = -this.vx;}}

添加拖尾效果

为了使得小球运动更加逼真,可以添加拖尾效果。使用clearRect函数清除前一帧动画时,若用一个半透明的fillRect函数取代之,就可轻松制作长尾效果。

ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);

移动鼠标到canvas内可让小球动起来!

遗留问题和优化

在实际的生活中,小球碰撞到地面反弹的时候,反弹的高度会越来越低,因为碰撞地面损失了一部分速度。

boundary: function(top, right, bottom, left) {// 检测小球下一帧是否出界,出界则补正if (this.y > bottom) {  // 下边界越界this.vy = -this.vy;  // 速度反向this.vy = 0.9 * this.vy; // 速度损失} else if (this.y < top) {this.vy = -this.vy;} else if (ball.x > right) {this.vx = -this.vx;  // 速度反向} else if (ball.x < left) {this.vx = -this.vx;}
}

上面这种方式会偶尔使得小球无法反弹。

在碰撞地面的时候,小球的反弹之后的速度和位移,准确值需要根据严格的匀加速公式以及损失之后的速度来计算。

边界检查时上述方法是检查圆心和边界的位置,更好的方式是检查圆周和边界的距离。

源码可以以及效果可以参考这儿:本文实例

上述所有方式的源代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ball animate</title><style>body {margin:0;padding:0;height: 100%;/*background: #000;*/overflow: hidden;}canvas {padding: 0;background: #000;border: 1px solid;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>var canvas = document.getElementById('canvas');var SCREEN_WIDTH = window.innerWidth,SCREEN_HEIGHT = window.innerHeight,ACCURACY = 1, ACCURACY_COMPARE = 1e-5;canvas.width = 500;canvas.height = 500;var context = canvas.getContext('2d'),animation, running = false;var Ball = function(){// x 坐标this.x = 100;// y 坐标this.y = 100;// x 方向初始速度this.vx = 10;// y 方向初始速度this.vy = 25;// x 方向位移this.dx = function() {return this.s(this.vx, this.ax, this.dt);};// y 方向位移this.dy = function() {return this.s(this.vy, this.ay, this.dt);};// 计算位移this.s = function(v, a, t){return v * t + (1 / 2.0) * a * t * t;};// x 方向碰撞速度损失this.loss_x = 0.8;// y 方向碰撞速度损失this.loss_y = 0.8;// 水平方向加速度this.ax = 0;// 竖直方向加速度this.ay = 1;// 两帧之间的时间间隔this.dt = 1;// 小球半径this.radius = 20;// 小球质量this.m = 1;// 小球颜色this.color = 'cyan';};// 小球绘制函数Ball.prototype.draw = function() {context.beginPath();context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);context.closePath();context.fillStyle = this.color;context.fill();};// 清除小球Ball.prototype.clear = function() {context.clearRect(this.x - this.radius,this.y - this.radius,this.radius * 2,this.radius * 2);};// 获取小球下一个状态Ball.prototype.next = function() {// 记录当前状态值var current_x = this.x, current_vx = this.vx, t1, self = this,current_y = this.y, current_vy = this.vy;// 下一状态理想坐标this.x += this.dx();this.y += this.dy();// 下一状态理想速度this.vx = this.vx + this.ax * this.dt;this.vy = this.vy + this.ay * this.dt;function boundary(top, right, bottom, left) {// 边界判断,预判下一状态是否碰撞下方墙壁if (self.y > bottom) {// 越界需要重新计算速度和坐标// 计算刚好碰到边界之前的速度: vt = sqrt(2aS + v0^2)self.vy = Math.sqrt(Math.abs(2 * self.ay * (bottom - current_y) + current_vy * current_vy));self.vy = current_vy > 0 ? self.vy : -self.vy;if (isNaN(self.vy)) {self.vy = 0;self.ay = 0;self.y = bottom;return false;}// 计算从当前位置到碰撞位置的时间if (self.ay == 0) {if (self.vy == 0) {t1 = 0;} else {t1 = (bottom - current_y) / self.vy;}} else {t1 = Math.abs((self.vy - current_vy) / self.ay);}// 碰撞后速度衰减并反向self.vy = -self.vy * self.loss_y;// 碰撞后反弹的位移位置self.y = bottom + self.s(self.vy, self.ay, self.dt - t1);// 位移小于阈值,则速度归零if (bottom - self.y < ACCURACY) {self.vy = 0;self.y = bottom;self.ay = 0;}}else if (self.x > right) {// 越界需要重新计算速度和坐标// 计算刚好碰到边界之前的速度: vt = sqrt(2aS + v0^2)self.vx = Math.sqrt(Math.abs(2 * self.ax * (right - current_x) + current_vx * current_vx));self.vx = current_vx > 0 ? self.vx : -self.vx;if (isNaN(self.vx)) {self.vx = 0;self.ax = 0;self.x = right;return false;}// 计算从当前位置到碰撞位置的时间if (self.ax == 0) {if (self.vx == 0) {t1 = 0;} else {t1 = (right - current_x) / self.vx;}} else {t1 = Math.abs((self.vx - current_vx) / self.ax);}// 碰撞后速度衰减并反向self.vx = -self.vx * self.loss_x;// 碰撞后反弹的位移位置self.x = right + self.s(self.vx, self.ax, self.dt - t1);// 位移小于阈值,则速度归零if (right - self.x < ACCURACY) {self.vx = 0;self.ax = 0;self.x = right;}}else if (self.x < left) {// 越界需要重新计算速度和坐标// 计算刚好碰到边界之前的速度: vt = sqrt(2aS + v0^2)self.vx = Math.sqrt(Math.abs(2 * self.ax * (current_x - left) + current_vx * current_vx));self.vx = current_vx > 0 ? self.vx : -self.vx;if (isNaN(self.vx)) {self.vx = 0;self.ax = 0;self.x = left;return false;}// 计算从当前位置到碰撞位置的时间if (self.ax == 0) {if (self.vx == 0) {t1 = 0;} else {t1 = (current_x - left) / self.vx;}} else {t1 = Math.abs((self.vx - current_vx) / self.ax);}// 碰撞后速度衰减并反向self.vx = -self.vx * self.loss_x;// 碰撞后反弹的位移位置self.x = self.s(self.vx, self.ax, self.dt - t1);// 位移小于阈值,则速度归零if (self.x - left < ACCURACY) {self.vx = 0;self.ax = 0;self.x = left;}}else if (self.y < top) {// 越界需要重新计算速度和坐标// 计算刚好碰到边界之前的速度: vt = sqrt(2aS + v0^2)self.vy = Math.sqrt(Math.abs(2 * self.ay * (current_y - top) + current_vy * current_vy));self.vy = current_vy > 0 ? self.vy : -self.vy;if (isNaN(self.vy)) {self.vy = 0;self.ay = 0;self.y = top;return false;}// 计算从当前位置到碰撞位置的时间if (self.ay == 0) {if (self.vy == 0) {t1 = 0;} else {t1 = (top - current_y) / self.vy;}} else {t1 = Math.abs((self.vy - current_vy) / self.ay);}// 碰撞后速度衰减并反向self.vy = -self.vy * self.loss_y;// 碰撞后反弹的位移位置self.y = self.s(self.vy, self.ay, self.dt - t1);// 位移小于阈值,则速度归零if (self.y - top < ACCURACY) {self.vy = 0;self.ay = 0;self.y = top;}}else {}}boundary(0, canvas.width - this.radius, canvas.height - this.radius, 0);};function clear() {context.fillStyle = 'rgba(0,0,0,0.3)';context.fillRect(0,0,canvas.width,canvas.height);}var ball = new Ball(), animate;function draw() {clear();ball.draw();ball.next();}canvas.addEventListener('click',function(e){if (!running) {ball.x = e.clientX;ball.y = e.clientY;animate = setInterval(function() {draw();}, 20);running = true;} else {running = false;clearInterval(animate);}});ball.draw();
</script>
</body>
</html>

转载于:https://www.cnblogs.com/youyoui/p/8525730.html

canvas动画:自由落体运动相关推荐

  1. canvas -小球自由落体运动

    最近学习canvas,就使用canvas写一个小球,再有重力作用下的落体运动 html: <canvas id="canvas" width="600" ...

  2. matlab小球水平抛出,如何用Matlab制作小球自由落体运动的动画

    第一堂课布置了一个Mission Impossible作业,要求学生们用Matlab制作一个动画,模拟小球的自由落体运动. 以下将整个任务的问题解决的过程分享如下: 步骤一,这是一个动画的制作过程,以 ...

  3. html画布实现小球沿直线下落,js+html5实现的自由落体运动效果代码

    本文实例讲述了js+html5实现的自由落体运动效果.分享给大家供大家参考,具体如下: 运行效果截图如下: 具体代码如下: /p> "http://www.w3.org/TR/xhtm ...

  4. Android模拟自由落体运动

    最近想看看android的游戏开发,因此首先绘图方面得练练,突然就想到模拟一下自由落体运动.本例采用serfaceView实现,接下来上代码: 一.首先定义一个自定义控件 public class M ...

  5. pygame里面物体闪烁运动_教师资格【试讲示范】高中物理试讲答辩——《自由落体运动》试讲稿答辩...

    试讲备课纸 教学过程 各位考官: 大家好,我是高中物理组的***号考生,我试讲的题目是<自由落体运动>,下面开始我的试讲. 一.导入新课 同学们,老师手里现在拿着一个小笔记本和一张纸,现在 ...

  6. 手把手教你用Python来模拟绘制自由落体运动过程中的抛物线(附源码)

    前言 前几天有个叫[-berry]的粉丝在问了一道关于自由落体运动过程中产生的抛物线作图的问题,如下图所示. 当某个物体以初速度v水平抛出,其轨迹为一条抛物线,模拟绘制这条抛物线.用高中物理知识,我们 ...

  7. lammps案例:分子自由落体运动模拟

    大家好,我是小马老师. 本文分享一个比较有意思的lammps案例:分子的自由落体运动. lammps提供了fix gravity命令可设置分子或者原子的加速度. 语法规则为: fix ID group ...

  8. matlab 地形模拟程序,MATLAB模拟小球自由落体运动

    大部分朋友学习MATLAB,需要一个学习示例用来参考,有一个比较经典的题目就是如何利用Matlab模拟小球自由落体运动,这可能会是你的某次课后作业,这个程序的编写过程可以分为三个步骤: 第一部分,设置 ...

  9. 模拟自由落体运动的小球

    基于VS2019   EasyX插件   C/C++ 生成一个模拟自由落体运动的小球 #include <iostream> #include <graphics.h> #in ...

最新文章

  1. 俄罗斯独特的职业***文化
  2. UNITY优化资料收集
  3. java 16进制与汉字_java实现汉字转unicode与汉字转16进制实例
  4. 开篇 — 【面向对象设计模式学习】
  5. XidianOJ 1019 自然数的秘密
  6. SUSE10下配置FTP服务
  7. 直通BAT必考题系列:JVM的4种垃圾回收算法、垃圾回收机制与总结
  8. mysql dba命令_mysql DBA:mysqladmin常用命令总结
  9. opencv python3 文本区域识别_使用等高线从图像中提取文本区域 - Opencv,Python
  10. 蓝牙nrf52832的架构和开发(转载)
  11. 智慧城轨信息技术架构及信息安全规范_会员信息 | 中国铁设:在深圳,我们打造智慧地铁的“最强大脑”...
  12. Pyserial库使用心得
  13. 维生素C对免疫力有什么影响?
  14. 简单选择排序 ← vector实现
  15. matlab中strcmp函数的使用
  16. ping指令的格式及参数
  17. 启示录java游戏_龙之幻想启示录(正版)
  18. 修改默认shell为fish shell
  19. SUL(supplementary Uplink)
  20. 论文阅读:2010-基于随机加工时间和模糊交货期的加工车间调度问题

热门文章

  1. StringTie用法详解
  2. (附源码)ssm美通留学管理系统 毕业设计 130854
  3. 生成目录路径树结构方法
  4. 备份工具mysqldump介绍
  5. 脱欧导致IT公司在英国开展业务充满变数
  6. 【教程】扫描识别工具Dynamic Web TWAIN使用教程:条码读取器(上)
  7. C#第四章上机练习2
  8. 学习编程该如何开始呢?
  9. 先进后出的数据结构-栈 一
  10. 西门子 s7-1200和V90伺服3轴PTO