画布中的缓动动画

Avatar of Darshan Somashekar
Darshan Somashekar

DigitalOcean 为您的旅程每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

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 动画的实时演示。 在这种情况下,我添加了动画,以便游戏中的卡牌在它们在牌堆之间移动时缓出和缓入。

除了创建运动之外,缓动函数还可以应用于任何具有起始状态和结束状态的其他属性,例如不透明度的变化、旋转和缩放。 我希望您能找到许多方法来使用缓动函数,使您的应用程序或游戏看起来更平滑。