画布系列 (10):动画入门
今天开始就要讲一些进阶的东西了,是不是很兴奋呢?
requestAnimationFrame
所谓动画其实就是快读绘制图片,由于人的眼睛更不上屏幕绘制的速率,所以看到的就好像连着的一样,也就形成了动画,动画片就是这个原理,canvas中的动画也是这个原理。提到动画就不得不说一个函数了,那就是requestAnimationFrame
。这是一个定时执行的函数,类似于setTimeout
,只是间隔时间不再有我们自己手动去设定,而是由计算机自己去计算,这样比我们直接设定的误差更小(通常我们是定1000/60,约等于16.7毫秒,因为CPU的频率一般是60Hz,也就是1秒最多可以刷新60次界面)。但是往往浏览器对requestAnimationFrame
的支持不够友好,那这就需要polyfill,通常一种简单的polyfill可以这么写:
if (!Date.now)
Date.now = function() { return new Date().getTime(); };
window.requestAnimationFrame = (function(){
var lastTime = 0;
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
var now = Date.now();
var nextTime = Math.max(lastTime + 16, now);
return window.setTimeout(function() {
callback(lastTime = nextTime);
},nextTime - now);
};
})();
// 与之对应的清空定时器的polyfill
window.cancelAnimationFrame = (function(){
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.clearTimeout
})();
我们可以看到,他的做法是如果没有requestAnimationFrame
那么使用setTimeout
来做回退处理。通过上面我们可以看到callback
有一个参数,就是时间,通常对于游戏等精度要求比较高的情况下我们使用这个时间和速度来计算当前帧的位置,这样可以有效避免,硬件配置所带来的优势。举个例子,比如我配置高可能比配置低的多画了几帧,那么同样的速度我就比别人走的快,而基于这个时间来计算的话就不会有问题了,如果配置低的少绘制几帧,那么时间间隔会变大相同的速度,距离也会边远。这就相当于直接跳过了中间几帧。当然对于这个时间的值不同浏览器实现的方式可能不一样,就比如谷歌的是从0毫秒开始逐渐递增的,有的浏览器是当前的毫秒数逐渐递增的,对于绘制图像的时候我们更多的是关注时间差,所以影响不是很大,就比如谷歌的第一帧传的时间可能是0,第二帧可能传的是17,而某些浏览器可能第一帧传的是1561859029000,第二帧传的是1561859029017,我们计算的时候往往是根据两者的差17来计算下一帧的位置。当然,对于一些简单与时间无关的动画特效,也可以不用关注这个时间,直接根据每次绘制时增加的速度去计算就好了,我们这里为了简单起见就不去动这个事件了。同时为了减少代码的长度我们就不使用polyfill了,如果是一个上线的项目最好使用上。
匀速直线运动
匀速直线运动是最简单的动画,由于我们现在需要不断地檫除然后重新绘制,所以我们需要重新给出我们此时的JavaScript代码,如下,也可以在这里查看:
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var sW = 1;
var lW = 4;
var spacing = 10;
// 绘制坐标系
function drawCoordinate(){
context.beginPath();
for (var x = 0; x < canvas.width; x+=spacing) {
context.moveTo(x, 0);
if(x % (spacing * 5) === 0){
context.lineTo(x, lW);
} else {
context.lineTo(x, sW);
}
}
for (let y = 0; y < canvas.height; y+=spacing) {
context.moveTo(0, y);
if(y % (spacing * 5) === 0){
context.lineTo(lW, y);
} else {
context.lineTo(sW, y);
}
}
context.strokeStyle='black';
context.stroke();
}
// 其他代码
// 中心坐标(centerX,centerY)
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
// 小球圆心的坐标
var ballX = centerX;
var ballY = centerY;
// 小球的半径
var ballRadius = 20;
// 更新小球
function updateBall(){
ballX += 1;
// 如果超出去 那么回到初始位置
if (ballX > 300 + ballRadius) {
ballX = -ballRadius;
}
}
// 绘制小球
function drawBall(){
context.beginPath();
context.arc(ballX, ballY, ballRadius, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.closePath();
context.fillStyle='orange';
context.fill();
}
// 此时没有轨迹的绘制 所以就是一个空函数
function drawLocus(){}
function animate(){
// 清屏
context.clearRect(0, 0, canvas.width, canvas.height);
// 绘制坐标系
drawCoordinate();
//绘制轨迹 有可能会用到 当前是空
drawLocus();
// 更新小球位置
updateBall();
// 绘制球
drawBall();
// 递归调用
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
看到animate
还是了吗,此函数是canvas动画的“套路”,一定要熟悉它,几乎所有动画相关的代码都有该函数的身影。此时的效果如下:
匀速圆周运动
匀速圆周运动和匀速直线运动代码差不多,唯一不同的地方就是drawBall
和updateBall
这两个方法,当然涉及到一点小小的数学计算,这里直接给出变动的部分,变动的代码大多数也是相似的,你只要把注意力放在updateBall
中就好了:
// ...
// 圆周运动半径
var radius = 50;
// 小球的角度
var angle = 0;
// 更新小球
function updateBall(){
ballX = centerX + Math.cos(angle) * radius;
ballY = centerY + Math.sin(angle) * radius;
// 需要注意的是Math.cos和Math.sin中的参数是弧度而不是角度
// 也就是说2*Math.PI是一周约等于6.28 这里每次加0.08弧度
angle += 0.08;
}
// 绘制轨迹
function drawLocus(){
context.beginPath();
context.arc(centerX, centerY, radius, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.closePath();
context.strokeStyle='red';
context.stroke();
}
// ...
出来的效果如下:
椭圆运动
圆周运动和椭圆运动很相似,直接给代码:
// ...
// 椭圆运动半径
var radiusX = 100;
var radiusY = 50;
// 小球的角度
var angle = 0;
// 更新小球
function updateBall(){
ballX = centerX + Math.cos(angle) * radiusX;
ballY = centerY + Math.sin(angle) * radiusY;
angle += 0.08;
}
// 轨迹
function drawLocus(){
// 绘制椭圆
context.save();
context.beginPath();
// 将坐标系平移到圆心位置
context.translate(centerX, centerY );
// 把圆缩放后使之形成椭圆
context.scale(1, radiusY / radiusX);
// 此时(0,0)是平移后的位置 也就是原坐标系的(centerX, centerY)
context.arc(0, 0, radiusX, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.strokeStyle='red';
context.stroke();
context.restore();
}
// ...
出来的效果如下:
左右来回运动
通过观察我们可以发现,椭圆运动和圆周运动的区别就是椭圆运动的某一个轴的半径和另一个轴的半径是不同的,那么如果某一个周的半径是0会发生什么情况呢。这就是左右来回的运动。我们修改一下代码,并且把绘制轨迹的函数drawCoordinate
去掉吧:
// 小球的角度
var angle = 0;
var radiusX = 100;
// 更新小球
function updateBall(){
ballX = centerX + Math.sin(angle) * radiusX;
angle += 0.08;
}
出来的效果如下:
正弦运动
上面是y不变,x利用三角函数计算的值,现在我们x每次增加一点,然后y轴使用三角函数,那就是正弦运动了。
// 小球的角度
var angle = 0;
var radiusX = 100;
var radiusY = 50;
var ballRadius = 20;
// 更新小球
function updateBall(){
ballX += 2;
ballY = centerY + Math.sin(angle) * radiusY;
angle += 0.08;
// 超出去以后左边显示
if (ballX > 300 + ballRadius) {
ballX = -ballRadius;
}
}
出来的效果如下:
带角度的匀速运动
更多的时候我们会遇到带有一定角度的匀速运动,比如速度是每次更新2个像素,那么实际上x和y都是他的一个分量,现在看一下代码:
// 小球圆心的坐标
var ballX = 0;
var ballY = 0;
// 小球的半径
var ballRadius = 20;
// 这里写角度看起来比较直观
var angle = 45;
var spend = 2;
// 更新小球
function updateBall(){
// 绘制的时候需要把角度转换为弧度
var vx = Math.cos(angle * Math.PI / 180) * spend;
var vy = Math.sin(angle * Math.PI / 180) * spend;
ballX += vx;
ballY += vy;
}
出来的效果如下:
我们可以看到小球在二维坐标系中的运动和速度的分解与合成有很大的关系。良好的数学和物理知识将帮助我们在这条路上走的更远。
推荐阅读
-
入门《Three.js实战指南:基于WebGL与HTML5,在网页上绘制3D图形与动画(第三版)》系列一 - 用Three.js打造首个3D互动场景
-
HTML5 代码系列:画布动画 - 碰碰球
-
Python 零基础快速入门系列 10]类设计理念:自然法则的体现
-
画布系列 (10):动画入门
-
aps是什么意思_不同的富士APS-C画幅微单区别在哪里,档次是怎么划分的?-X-A系列原本指的是富士的入门级微单,最大的特点是没有使用富士X-Trans™CMOS 传感器,目前在售的有两款,分别是XA5和XA7。 富士(FUJIFILM)X-A5/XA5 15-45套机 富士(FUJIFILM)X-A7/XA7 15-45套机 目前这两款相机都处于历史最低价附近,XA5套机2699元,XA7套机3999元。XA5就是一个标准的入门级相机,定位就是时尚小巧自拍,在2699这个价位不要对它的性能有太多的奢求。 XA7价格来到了3999元,这就很有意思了,富士把入门型的相机价格推到了4000元,并且提供了自拍翻转屏和4K30P视频录制,这样一款相机就很有性价比了。 XE3是老款的中端相机,价格和入门级的XA7是一样的,都是3999元,这两款相机如何做选择呢?XE3有着更多的按键意味着更好的操控,但屏幕不是自拍翻转屏所以这点不如XA7好用。 要注意的是XE3用的是富士独有的X-Trans™CMOS III传感器,XA7是普通的2400万像素传感器,你可以理解为X-Trans才是富士的精髓。 富士(FUJIFILM)X-E3 15-45套机 当然,买新不买旧,XA7的新功能和自拍翻转屏可能会更适合你。 XT200是富士专门针对vlog市场推出的相机,其实之前的XA7也可以拍摄vlog,但XT200是富士官方宣传中的第一款vlog相机。数码防抖+3.5mm 麦克风口+自拍翻转屏+无裁切4K30P,这些都是XT200的优势,但这款相机也是普通的2400万像素传感器,没有用富士独有的X-Trans,可能是从价格角度考虑做了阉割吧。 富士(FUJIFILM)X-T200/XT200 微单相机 Vlog相机 富士XT30是我认为富士性价比最高的微单照相机,注意我说的是照相机。理由很简单,因为从拍照角度来看XT30和XTXT3几乎没有明显差距,主要是操控差了一些、视频性能大幅削弱,但好歹也是个有着双波轮+曝光补偿波轮+快门速度波轮的相机,操控方面不会太差的。视频方面也有着超采4K 30P的规格,支持F-log输出。 可以这么说,如果你只拍照,那么XT30是富士微单中性价比最高的,视频方面XT30也不差,只不过没有专业的10bit和4K60P而已。 富士(FUJIFILM)X-T30/XT30 15-45套机 XT3和XT4得放在一起说,这两款相机其实都挺好,420 10bit 4K60P的专业视频模式基本代表了APS-C画幅的上限水平。XT4还提升了电池续航增加了五轴防抖,配上富士独特的胶片滤镜,不管是拍照还是拍视频都非常优秀。 不要觉得这两款相机贵,同价位里能做到4K60P的微单也就是M43画幅的GGHGH5S,最便宜的G9机身也要7000多,这APS-C画幅的XT3机身接近8000也算合理价格范围内。除此之外的4K60P机身只有13998的松下S5和15999的佳能R6了。 富士(FUJIFILM)X-T3/XT3 1855套机 富士(FUJIFILM)X-T4/XT4 微单相机 套机(18-55mm) B站更新4K视频投稿后有很多人想拍摄4K升格,在很长一段时间里富士XT3和XT4是最优选,毕竟兼顾视频和拍照,对焦也还算能用。 X-Pro3和X-Pro2这两款微单可以算是旁轴相机,是富士官方意义上的旗舰级相机。从用料做工操控按键角度来说的确是旗舰级别,但视频性能方面只有4K30P,价格却比XT3还贵,可能这就是旁轴情怀带来的溢价吧。 富士(FUJIFILM)X-Pro3 微单相机 机身 黑色 我在之前的文章里提过很多次,有一些相机属于如果你想买你压根不会看测评,如果你犹豫那么这款相机不适合你,为什么这么说,因为有一些比较小众的相机可能在性能上并不好,但独特的外形、操控、体积、传承赋予了它独特的定位。譬如富士X-Pro系列微单就是旁轴的电子化,理光GR传承大师的扫街理念,尼康DF的外形源自胶片时代的相机,这些相机就不是针对大多数消费者的,定位就是小众。所以我说喜欢就买,不要考虑什么性能规格。 X100系列相机是一款不可换镜头的等效35mm旁轴数码相机,从外形看就是经典的复古造型。这两款相机和X-Pro3一样,如果你喜欢那就买,别犹豫, 你在市场上找不到同类型的其他数码相机,徕卡Q是28mm,索尼RX1R系列是35mm但外形不够复古,X100系列就是独特的你没有其他选择。 那么X100F和X100V该如何选择呢?X100F的镜头很一般甚至算不上好,如果我没记错的话和初代的X100是同款镜头,X100V的镜头是全新制作的很棒,X100V的机身性能也和XTX-Pro3差不多。 富士(FUJIFILM)X100F 数码相机 旁轴 2430万像素 富士(FUJIFILM)X100V 数码相机 旁轴 2610万像素 还是那句话,这两款相机也是那种如果你喜欢那就毫不犹豫下单的类型,而且这两款相机也没有竞品。 以前不推荐富士的原因是原厂镜头太贵,现在唯卓仕给富士出了四款可以自动对焦的大光圈镜头,覆盖35到130mm的焦段,可以基本满足人像摄影爱好者的需求。拍风景的话国产很多镜头厂商都有富士卡口的手动镜头可以选择,从这个角度来说富士微单就非常值得入手了。 和友商竞品相比:
-
入门MySQL基础查询系列(10) - 详解REGEXP_LIKE函数:探索正则表达式的基础应用
-
linux入门系列10--firewalld防火墙管理