HTML 中的 <canvas>
元素和 JavaScript 中的 Canvas API 结合在一起,构成了 Web 上主要的栅格图形和动画可能性之一。 一个常见的画布用例是为网站(特别是游戏)以编程方式生成图像。 这正是我在 为玩纸牌游戏而构建的网站 中所做的。 包括所有移动在内的卡牌,全部都在画布中完成。
在本文中,让我们专门看看画布中的动画,以及使它们看起来更平滑的技术。 我们将专门关注缓动过渡 - 例如“缓入”和“缓出” - 它们不像 CSS 那样在画布中免费提供。
让我们从一个静态画布开始。 我已经绘制了一个从 DOM 中获取的单张扑克牌到画布上。
让我们从一个基本的动画开始:在画布上移动这张扑克牌。 即使对于非常基本的东西来说,也需要从画布中从头开始,因此我们需要开始构建可以使用的函数。
首先,我们将创建函数来帮助计算 X 和 Y 坐标
function getX(params) {
let distance = params.xTo - params.xFrom;
let steps = params.frames;
let progress = params.frame;
return distance / steps * progress;
}
function getY(params) {
let distance = params.yTo - params.yFrom;
let steps = params.frames;
let progress = params.frame;
return distance / steps * progress;
}
这将帮助我们在图像被动画化时更新位置值。 然后我们将不断重新渲染画布,直到动画完成。 我们通过在我们的 addImage()
方法中添加以下代码来实现这一点。
if (params.frame < params.frames) {
params.frame = params.frame + 1;
window.requestAnimationFrame(drawCanvas);
window.requestAnimationFrame(addImage.bind(null, params))
}
现在我们有动画了! 我们每次都稳定地递增 1 个单位,我们称之为线性动画。
您可以看到该形状如何以线性方式从点 A 移动到点 B,在点之间保持相同的恒定速度。 它具有功能性,但缺乏真实感。 开始和结束是刺耳的。
我们想要的是让物体加速(缓入)和减速(缓出),这样它就能模拟现实世界物体在摩擦力和重力起作用时会做的事情。
JavaScript 缓动函数
我们将使用“三次”缓入和缓出过渡来实现这一点。 我们修改了 Robert Penner 的 Flash 缓动函数 中的其中一个方程,使其适合我们这里要做的工作。
function getEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) {
return (distance/2)*(Math.pow(currentProgress, 3)) + start;
}
currentProgress -= 2;
return distance/2*(Math.pow(currentProgress, 3)+ 2) + start;
}
将其插入我们的代码中,这是一个三次缓动,我们得到了一个更平滑的结果。 注意卡牌如何加速向空间中心移动,然后在到达终点时减速。
使用 JavaScript 进行高级缓动
我们可以使用二次缓动或正弦缓动来获得更慢的加速。
function getQuadraticEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress <= 1) {
return (distance/2)*currentProgress*currentProgress + start;
}
currentProgress--;
return -1*(distance/2) * (currentProgress*(currentProgress-2) - 1) + start;
}
function sineEaseInOut(currentProgress, start, distance, steps) {
return -distance/2 * (Math.cos(Math.PI*currentProgress/steps) - 1) + start;
};
要获得更快的加速,请使用五次缓动或指数缓动
function getQuinticEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) {
return (distance/2)*(Math.pow(currentProgress, 5)) + start;
}
currentProgress -= 2;
return distance/2*(Math.pow(currentProgress, 5) + 2) + start;
}
function expEaseInOut(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) return distance/2 * Math.pow( 2, 10 * (currentProgress - 1) ) + start;
currentProgress--;
return distance/2 * ( -Math.pow( 2, -10 * currentProgress) + 2 ) + start;
};
使用 GSAP 进行更复杂的动画
自己编写缓动函数可能很有趣,但是如果您想要更多的功能和灵活性怎么办? 您可以继续编写自定义代码,也可以考虑一个功能更强大的库。 让我们转向 GreenSock 动画平台 (GSAP) 来实现这一点。
使用 GSAP,动画的实现变得容易很多。 举个例子,卡牌在结束时会反弹。 请注意,GSAP 库包含在演示中。
关键函数是 moveCard
function moveCard() {
gsap.to(position, {
duration: 2,
ease: "bounce.out",
x: position.xMax,
y: position.yMax,
onUpdate: function() {
draw();
},
onComplete: function() {
position.x = position.origX;
position.y = position.origY;
}
});
}
该 gsap.to
方法 是所有魔力发生的地方。 在两秒的持续时间内,position
对象会更新,并且每次更新时,onUpdate
会被调用,触发画布被重绘。
而且我们不仅仅是在谈论反弹。 有很多 不同的缓动选项 可供选择。
将所有内容整合在一起
仍然不确定在涉及缓动时应该在画布中使用哪种动画样式和方法? 这里有一个 Pen 展示了不同的缓动动画,包括 GSAP 中提供的动画。
查看我的 纸牌游戏,以查看非 GSAP 动画的实时演示。 在这种情况下,我添加了动画,以便游戏中的卡牌在它们在牌堆之间移动时缓出和缓入。
除了创建运动之外,缓动函数还可以应用于任何具有起始状态和结束状态的其他属性,例如不透明度的变化、旋转和缩放。 我希望您能找到许多方法来使用缓动函数,使您的应用程序或游戏看起来更平滑。
我在哪里可以找到纸牌游戏的图像
可能值得一提的是 lerp、slerp 以及它们的同类。 虽然它们在概念上更高级,但它们很容易实现,并且比大多数缓动更自然。 这也是摆脱基于时间动画并开始跳出框框思考的好方法 :)