如何用VueUse创建一个动画精灵

435 阅读6分钟

VueUse是一个流行的Vue.js库,由可组合的功能和实用的函数组成,在Vue.js项目的背景下使与本地浏览器功能的交互更加直观。

Vue学校有一个关于VueUse的深入课程,叫做VueUse For Everyone,可以让你亲身体验使用其中的一些功能。不过在这篇文章中,我想重点介绍useIntervaluseRafFn 函数,以创建一个动画精灵。如果你想看视频形式的相同内容,请查看VueUse for Everyone课程中的类似课程

首先,什么是精灵?

一个精灵是一个包括多个图像(帧)的图像,我们可以以一定的间隔循环通过这些图像来创建一个动画,就像这个公共领域的男子行走图像

static sprite image of man walking with all frames shown at once

当隔离到一个单一的帧,并在每一个帧中循环时,结果是这样的。

animated sprite of man walking cycling through one frame at a time

让我们来看看我们如何使用VueUse来创建这个动画,同时能够反应性地调整它的速度,并通过简单的预定义函数调用来暂停/恢复它。

安装和使用VueUse

首先,你要把VueUse安装到一个现有的Vue.js项目中,或者你可以从这个模板Stackblitz项目开始。

npm i @vueuse/core

接下来,你应该从VueUse导入useIntervalFn composable。

// App.vue
<script setup>
import { useIntervalFn } from '@vueuse/core';
</script>

这就是开始使用VueUse composable的简单方法。作为奖励,由于VueUse是完全可树形的,你可以安装整个库,然后只挑你需要的功能。这确保了最终捆绑的尺寸最小。

Sprite标记和样式

我们现在可以先把useIntervalFn ,等我们为精灵设置好HTML和CSS后再回来看它。

// App.vue
<template>
  <!--div to display the sprite in-->
  <div class="sprite"></div>
</template>

<style>
.sprite {
  /* display the image*/
  background: url(https://freesvg.org/img/1525205509.png) no-repeat;

  /* each frame is 75px wide so limit container to display one at a time */
  width: 75px; 

  /* main is roughly 150px tall */
  height: 150px;

  /* the image has some space on top and bottom so this accounts for that */
  background-position: 0px 50%;

}
</style>

现在我们要做的是使背景位置的X轴随时间变化而变化,每隔一段时间将其递增-75px像素,以便在不同的帧中移动。

/*Step 1*/ background-position: 0px 50%;
/*Step 2*/ background-position: -75px 50%;
/*Step 3*/ background-position: -150px 50%; 
/*Step 4*/ background-position: -225px 50%;
/*etc*/

为了做到这一点,我们可以创建一个反应式参考,以跟上哪个位置是活动的,并将其初始化为0。

import {ref} from "vue";
//...
const activePosition = ref(0);

然后我们可以把背景位置移到一个内联样式,这样就更容易提供一个动态的X位置。

<div
    class="sprite"
    :style="`background-position: ${activePosition}px 50%;`"
  ></div>

VueUse - useIntervalFn

现在剩下的就是以一个时间间隔来更新这个活动位置。这就是VueUse的useIntervalFnuseIntervalFn 需要一个回调函数和一个定义回调函数运行频率的数字,单位是毫秒。

useIntervalFn(() => {}, 200);

在回调函数中,我们可以说如果activePosition 大于-525,就把activePosition 递减75,这是每一帧的宽度。

useIntervalFn(() => {
  if (activePosition.value > -525) {
    activePosition.value -= 75;
  }
}, 200);

我在这里选择-525的原因是它是-75的7倍,比我们的动画的帧数少一个,加上从0开始的1帧占了所有8个帧。

else {
    activePosition.value = 0;
}

在这一点上,你应该有一个图像序列,每200毫秒切换一次,看起来像一个行走的人。

sprite of man walking updating with 200 millesecond interval

如果你想让他走得更快,你可以减少间隔时间的数量。

200 -> 100

sprite of man walking updating with 100 millesecond interval

useIntervalFn的好处

在这一点上,你可能会想,useIntervalFn 并没有带来什么好处。你可以用浏览器原生的setInterval 替换对useIntervalFn 的调用,这样做的效果是完全一样的。但是,useIntervalFn 也带来了一些好处。

反应速度

Learn Vue.js 3 With Vue School

首先,如果你想提供一个反应式ref作为间隔速度,你可以。

const speed = ref(100)
useIntervalFn(() => {
  //...
}, speed);

这在Vue环境中是很方便的,因为你经常要处理反应式数据。虽然这种格式很方便,但它实际上也很有效。如果我们想反应性地更新这个人的速度,我们可以把速度绑定到一个范围输入。

<input type="range" step="20" min="20" max="200" v-model="speed" />

sprite of man walking updating with speed slider to change speed

另外,如果你想让输入的感觉更直观一些,当他向右移动时加快速度,向左移动时减慢速度,你可以用CSS反映输入。

input[type='range'] {
  transform: scaleX(-1);
}

如果你倾向于做更多的工作,抛弃v-model ,改用:value@input ,我相信你也可以这样做,但我不想在这样的解决方案上花费时间。

暂停和恢复

除了使速度反应,你还可以通过解构一些方便的函数和数据,从对useIntervalFn 的调用中轻松地暂停和恢复时间间隔。

<script>
const { pause, resume, isActive } = useIntervalFn(() => {
//...
</script>

<template>
<!--...-->
<button @click="isActive ? pause() : resume()">
    {{ isActive ? 'Pause' : 'Resume' }}
</button>
</template>

sprite of man walking with pause and resume controls

当使用普通的'setInterval ,这两种功能都需要多一点。

(如果你想看看到此为止一切是如何工作的,你可以查看这个Stackblitz项目。)

使用RafRn

虽然间隔时间对于简单的动画和没有大量动画的网站来说是可以的,但requestAnimationFrame实际上是给精灵做动画的推荐方法。我不会去讨论所有的原因,因为说实话,我不是动画专家,但如果你想知道更多,请看这个堆栈溢出

那么我们能在VueUse中使用requestAnimationFrame 吗?的确,我们可以用一个叫useRafFn 的函数。

import { useRafFn } from '@vueuse/core';

现在我们需要把useIntervalFn 换成useRafFn ,同时我们也要去掉间隔速度。

const { pause, resume, isActive } = useRafFn(() => {
  if (activePosition.value > -525) {
    activePosition.value -= 75;
  } else {
    activePosition.value = 0;
  }
});

这样做的原因是,requestAnimationFrame 并不是按时间间隔运行的,而是基于你的显示器的帧速率,通常是每秒60帧。

如果你跟在后面,你会注意到现在那个人就快飞起来了,他跑得很快,速度滑块不再起作用。事实上,他跑得太快了,我无法以足够高的帧率正确地捕捉到一个gif。

sprite of man walking with requestAnimationFrame

那么,我们如何在不能设置间隔的情况下控制这个人的速度呢?

再说一次,我不是动画专家,但以下方法对我来说很有效。

//...
// keep up with how many passed frames
const framesComplete = ref(0);
// change speed default to better match new methodology
const speed = ref(10);
const { pause, resume, isActive } = useRafFn(() => {
  // increment framesComplete each animation frame
  framesComplete.value++;
  // return early if frames complete is not a multiple of 10 (or speed.value)
  // so that the postition is not altered until every 10th animation frame
  if (framesComplete.value % speed.value) return;
  //...
});
</script>

这也意味着,我们也需要更新输入范围的输入。

<input type="range" step="1" min="1" max="20" v-model="speed" />

这样一来,我们的步行者就全部设置好了,可以使用更友好的动画useRafFn ,速度控制和以前一样。

暂停/恢复按钮呢?它还能与useRafFn 一起工作吗?的确,它的作用是一样的!

sprite of man walking with requestAnimationFrame with speed and pause/resume controls

如果你想看完整的工作实例,你可以在这个Stackblitz项目中玩一玩。

总结

VueUse是一个强大的工具库,你可以在你的Vue.js项目中快速安装它来做各种各样的事情,比如反应式控制精灵。如果你有兴趣了解更多关于VueUse的超强可组合性,请查看我们的课程。VueUse for Everyone。你现在可以免费预览入门课程

Learn Vue.js 3 With Vue School