Howdy
theme-lighttheme-dark

使用Element.animate添加文字与图片动画

December 01, 2021

在网页添加一些简单的切换动画一般使用CSS3的animation或者transition实现,而Element.animate是新的原生Javascript Api,能使用JS快速为Dom添加动画。

前言

为了增加网站的用户体验,在很多场景下一般可以为元素的切换添加一下转场动画。

最简单的方法,一般可以使用CSS的transition或者animation实现。当前也有很多CSS动画库,例如animate.css,它与wow.js搭配使用经常用于很多产品宣传首页。

当然我们也可以使用一些主流的JS动画库操作某些元素的单独动画,例如:VelocityAnime.js等。或者更传统的Jquery也有提供.animate操作动画的方案。

而本文则介绍一下原生较新的Javascript Api: Element.animate()

关于Element.animate

Element.animate()Web Animations API提供的使用Javascript操作元素动画的解决方案。

参数

Element.animate(keyframes, options)方法接收2个参数,第一个为keyframes,第二个为options

keyframes:与CSS3的keyframes的概念是一致的,代表关键帧的集合。它可以接收一个关键帧数组,也可以简写成一个对象。它支持所有CSS动画支持的属性,另简写写法添加offset, float, easing等关键字 。具体写法请参考Keyframe Formats (MDN)或下文的案例使用。

options: 动画的相关配置。其接收delay, duration, easing, iterations等配置参数,可配置动画的延迟执行时间、执行持续时间、缓动曲线、执行次数等,其与CSS动画属性也保持一致,更多参数请参考KeyframeEffect (MDN)

这个方法会返回一个Animation实例,通过这个实例我们可以对动画进行暂停、取消、读取执行状态等。或者通过.finished返回Promise来对执行完动画进行下一步操作。

兼容性

因为该API较新,所以对传统的浏览器支持并不友好,所有IE浏览器都不支持。在Can I use上查询如下:

兼容性

但是只要简单使用if (dom.animate) {}包裹即可向下兼容,对一些不支持的浏览器不执行动画就可以。

使用案例

文字动画特效

简单写了三种文字动画特效:

  • FadeIn:渐变进入
  • FadeUpInOut: 旧文本先淡出新文本再淡入
  • Typewriter: 打字机特效

文字切换特效

淡入淡出

Effect1Effect2是更改opactitytranslate属性实现的文字淡入淡出动画。

<div class="text-wrapper" id="Effect1"></div>
<div class="text-wrapper" id="Effect2"></div>
<script>
// ...省略事件绑定等代码
function animateEffect1(text) {
  const target = document.querySelector("#Effect1");
  target.innerText = text;
  // 使用对象简写写法opactiy from 0, to 1, 执行时间600ms
  target.animate({ opacity: [0, 1] }, 600); 
}
async function animateEffect2(text) {
  const target = document.querySelector("#Effect2");
  // finished返回Promise,可等待文本淡出动画执行完再执行新文本淡入动画
  await target.animate({
    opacity: [1, 0],
    transform: ["translateY(0)", "translateY(-20px)"],
  },300).finished;
  target.innerText = text; // 当旧文本动画执行完再开始替换文本
  target.animate(
    {
      opacity: [0, 1],
      transform: ["translateY(20px)", "translateY(0)"],
    },
    300
  );
}
</script>

打字机

Effect3实现了一个文字打字机特效,该特效需要确保文字是等宽字体,而且文本不能为多行文本。

<div class="text-wrapper" id="Effect3"></div>
<script>
// ...省略事件绑定等代码
async function animateEffect3(text) {
  const target = document.querySelector("#Effect3");
  const beforeWidth = target.offsetWidth; // 计算旧文本宽度
  const textBeforeLength = target.innerText.length; // 计算文本字数
  if (textBeforeLength > 0) {
    // 使用数组参数方式执行宽度减少阶跃动画
    await target.animate(
      [
        { width: `${beforeWidth}px` },
        { width: 0 }
      ],
      {
        duration: textBeforeLength * 100,
        easing: `steps(${textBeforeLength})` // step是阶跃函数,表示动画按多少步执行完
      }
    ).finished;
  }
  target.innerText = text; // 切换新文本
  const afterWidth = target.offsetWidth; // 计算新文本宽度
  const textAfterLength = target.innerText.length; // 计算文本字数
  target.animate(
    [
      { width: 0 },
      { width: `${afterWidth}px` },
    ],
    {
      duration: textAfterLength * 100,
      easing: `steps(${textAfterLength})`
    }
  );
}
</script>

easing属性中使用了steps阶跃函数,它表示动画需要按多少步执行完,每一步状态里面是属性是一致的,每步属性变化是跳跃的,没有过渡。这里使得文本每次减少一个字符的宽度,构造出打字机的效果。

以上文字特效的实现已上传到codepen,请参考此处: https://codepen.io/leon-kfd/pen/vYJbodr

图片切换特效

在使用CSS实现元素动画,一般通过添加类名、移出类名来实现。因为图片无法准确知道需要加载的时间,一般情况是监听图片的load事件回调进行判断,所以基本无法使用纯CSS来实现切换动画。

本次使用原生Element.animate()API实现了一个简单的图片切换特效。

图片切换动画特效

这个特效主要原理:

  • 点击切换时,调用函数切换图片路径,这时候图片会异步加载
  • 在新图片加载过程中,旧图片执行一个高斯模糊叠加的渐变淡出动画,并把这个Animation对象记录下来
  • 为图片元素添加load事件监听,当图片加载完成,把旧图片淡出动画cancel(因为无法确保旧图片淡出动画执行完前新图片已经加载完成,需要Cancel掉旧动画防止重复执行)
  • 执行新图片的高斯模糊淡入动画,动画执行后修改元素CSS最终状态

Demo代码如下:

<button id="btn">Random Img</button>
<p class="img-wrapper">
  <img id="img"/>
</p>

<script>
let leaveAnimation = null; // 用于记录Animation对象
btn.addEventListener("click", () => {
  randomPhoto();
});

async function randomPhoto(first) {
  const target = `https://source.unsplash.com/random/512x512/?nature,${+new Date()}`;
  img.src = target;
  // 切换图片路径后,执行图片淡出动画,此时新图片在后台加载
  if (!first) {
    try {
      leaveAnimation = img.animate([
        { filter: "blur(20px)", tarnsform: "scale(1,1)" },
        { filter: "blur(60px)" },
      ], 400);
      await leaveAnimation.finished;
      img.style.filter = "blur(60px)";
    } catch {
      console.log("Cancel animation");
    }
  }
}

img.addEventListener("load", async () => {
  img.style.opacity = 1; // 用于防止首次加载闪图
  if (leaveAnimation) leaveAnimation.cancel(); // cancel掉淡出动画,防止重复执行
  const changeAnimation = img.animate([
    { filter: "blur(20px)", tarnsform: "scale(1,1)" },
    { filter: "blur(0)", tarnsform: "scale(1)" },
  ],400)
  await changeAnimation.finished;
  img.style.filter = "blur(0)"; // 等待动画执行完后更改最终状态
});

randomPhoto(true); // 页面加载立即执行一遍
</script>

以上图片特效的实现已上传到codepen,请参考此处: https://codepen.io/leon-kfd/pen/ZEXYKLR

Demo中使用了unsplash的随机图片接口, https://source.unsplash.com/random

另因当前Element.animate()仍属于实验特性,并不保证将来api会进行改动或添加新的特性。


to-top