聊一聊前端动画的种类,以及动画的触发方式有哪些?

引言

动画在前端开发中扮演着重要的角色。它不仅可以提升用户体验,还可以使界面更加生动和有趣。在这篇文章中,我们将深入探讨前端动画的各种实现方式,包括 CSS 动画、JavaScript 动画、SVG 动画等。我们还将讨论一些触发动画的方式和动画在用户体验中的最佳实践。

前端动画分类

  • CSS 动画

    • CSS Transition
      CSS 过渡,属于补间动画,即设置关键帧的初始状态,然后在另一个关键帧改变这个状态,比如大小、颜色、透明度等,浏览器将自动根据二者之间帧的值创建的动画。
    • CSS Animation
      CSS 动画,可以理解是 CSS Transition 的加强版,它既可以实现 补间动画 的动画效果,也可以使其以 逐帧动画 的方式进行绘制。
  • SVG 动画

    • SVG 动画用于矢量图形,提供了高质量的动画效果,常用于图标和图形动画。可以使用 SMIL 在SVG中定义动画。同样的也可以使用css或者js来控制svg动画。
  • Canvas 动画

    • 通过结合使用 requestAnimationFrame、路径和变换等技术对画布的元素进行擦除和重新绘制,可以实现复杂的动画效果。另外Canvas还可以用于绘制复杂的背景或静态内容,从而减少每帧的绘制工作量。
    • 可以参考我的一篇关于canvas制作动画的文章:用Canvas绘制一个高可配置的圆形进度条
  • JS 动画

    • setTimeout / setInterval / requestAnimationFrame
      setTimeoutsetInterval 这两个 API 设定的时间会因为浏览器当前工作负载而有所偏差,而且无法与浏览器的绘制帧保持同步。所以才有了 与浏览器的绘制帧同步 的原生 API requestAnimationFrame,以取代 setTimeoutsetInterval 实现动画。

    • Web Animations API
      浏览器动画 API,通过 JavaScript 操作。这些 API 被设计成 CSS TransitionCSS Animation 的接口,很容易通过 JS 的方式实现 CSS 动画,它是对动画化的支持最有效的方式之一。

css 动画

css过渡动画 transition

注意

由于浏览器是根据样式差异化的两帧自动计算并过渡,所以 transition 只支持可识别中间值的属性 (如大小、颜色、位置、透明度等),而如 display 属性则不支持。

语法定义

  • transition-property: 指定哪个或哪些 CSS 属性用于过渡。只有指定的属性才会在过渡中发生动画,其他属性仍如通常那样瞬间变化。

  • transition-duration: 指定过渡的时长。你可以为所有属性指定一个值,或者指定多个值,或者为每个属性指定不同的时长。

  • transition-timing-function: 指定一个缓动函数,定义属性值怎么变化。常见的缓动函数是一个三次贝塞尔曲线 ( cubic-bezier(<x1>, <y1>, <x2>, <y2>) )。当然也可以选择关键字

    • linearcubic-bezier(0.0, 0.0, 1.0, 1.0)
    • easecubic-bezier(0.25, 0.1, 0.25, 1.0)
    • ease-incubic-bezier(0.42, 0.0, 1.0, 1.0)
    • ease-outcubic-bezier(0.0, 0.0, 0.58, 1.0)
    • ease-in-outcubic-bezier(0.42, 0.0, 0.58, 1.0)
  • transition-delay: 指定延迟,即属性开始变化时与过渡开始发生时之间的时长。

代码示例

  /* 单条 简写形式 */
  transition: 
    <property> <duration> <timing-function> <delay>;
  
  
  /* 多条 简写形式 */
  transition: 
    <property> <duration> <timing-function> <delay>,
    <property> <duration> <timing-function> <delay>,
    ...;


  /* 单条 子属性形式 */
  transition-property: <property-name>;
  transition-duration: <duration-time>;
  transition-timing-function: <timing-function>;
  transition-delay: <duration-time>;
  
  
  /* 多条 子属性形式 */
  transition-property: <property-name> [, <property-name>, ...];
  transition-duration: <duration-time> [, <duration-time>, ...];
  transition-timing-function: [, <cubic-bezier>, ...];
  transition-delay: [, <duration-time>, ...];
  
  
  // 如果任意属性值列表的长度比其他属性值列表要短,则其中的值会重复使用以便匹配
  
  // 如果某个属性的值列表长于 `transition-property` 的属性,则将被截短
  

css过渡动画 触发方式

1. 伪类触发(:hover、:focus、:active等)
.button {
  background-color: blue;
  transition: background-color 0.3s ease;
}

.button:hover {
  background-color: red;
}

2. 类名切换(通过JS动态切换类名来触发过渡效果)
<button id="toggleButton">Toggle</button>
<div id="box" class="box"></div>

<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: blue;
    transition: background-color 0.3s ease;
  }

  .box.active {
    background-color: red;
  }
</style>

<script>
  document.getElementById('toggleButton').addEventListener('click', function() {
    document.getElementById('box').classList.toggle('active');
  });
</script>

3. 属性变化
<button id="toggleButton">Toggle</button>
<div id="box" class="box"></div>

<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: blue;
    transition: background-color 0.3s ease;
  }
</style>

<script>
  document.getElementById('toggleButton').addEventListener('click', function() {
    const box = document.getElementById('box');
    box.style.backgroundColor = box.style.backgroundColor === 'red' ? 'blue' : 'red';
  });
</script>

4. 伪元素触发(通过伪元素如::before::after的状态变化来触发过渡效果。)
<div class="box"></div>

<style>
  .box {
    width: 100px;
    height: 100px;
    position: relative;
  }

  .box::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: blue;
    transition: background-color 0.3s ease;
  }

  .box:hover::before {
    background-color: red;
  }
</style>

css动画 animation

注意

CSS Animation 具备了对 关键帧和循环次数 的自定义能力。CSS Animation 在实现像 CSS Transition 补间动画 效果时,还可以在起始帧和结束帧之间自定义中间帧,使得动画更加平滑过渡的同时,对动画有了更好的控制和自定义能力。

语法定义

先创建一个带名称的 @keyframes 规则,以便后续使用 animation-name 属性将动画同其关键帧声明进行匹配。每个规则包含多个关键帧,也就是一段样式块语句,每个关键帧有一个百分比值作为名称,代表在动画进行中,在哪个阶段触发这个帧所包含的样式。

  • animation-name:指定一个或多个 @keyframes 的名称,描述了要应用于元素的动画。多个 @keyframes 以逗号分隔。

  • animation-duration:设置动画完成一个动画周期所需的时间,需要指定单位,如 1s500ms

  • animation-delay:指定执行动画之前的等待时间。动画可以稍后开始、立即从开头开始、立即在动画中途播放 (如 -1s) 。其中 -1s 意思是动画立即从 1s 处开始。

  • animation-iteration-count:设置动画序列在停止前应播放的次数,有效值 0、正整数、正小数、无限循环 infinite

  • animation-direction:设置动画是正向播放 normal、反向播放 reverse、正向交替播放 alternate、反向交替播放 alternate-reverse

  • animation-play-state:设置动画是运行还是暂停,有效值 runningpaused

  • animation-fill-mode:设置 CSS 动画在执行之前和之后如何将样式应用于其目标,有效值如下:

    • none:当动画未执行时,动画将不会将任何样式应用于目标,而是已经赋予给该元素的 CSS 规则来显示该元素。这是默认值
    • forwards:目标将保留由执行期间遇到的最后一个关键帧计算值。
    • backwards:动画将在应用于目标时立即应用第一个关键帧中定义的值。
      animation-timing-function:设置动画在每个周期的持续时间内如何进行,主要是如下两种函数:
  • cubic-bezier 三次贝塞尔曲线 ( cubic-bezier(<x1>, <y1>, <x2>, <y2>) ),以实现 补间动画 效果。

  • steps 是一个分段的阶跃函数,,以实现 逐帧动画。n 相当于单次动画的帧数,每帧动画的时间是均等的 (steps(n, <jumpterm>)),其中 jumpterm (默认值 end) 含义如下:

    1. jump-start:在起始位置阶跃,n=2 ⇒ 50% 100%; (100 / 2)
    2. jump-end:在结束位置阶跃, n=4 ⇒ 0% 25% 50% 75%; (100 / 4)
    3. jump-none:起止位置均无跳跃,n=5 ⇒ 0% 25% 50% 75% 100%; (100 / 4)
    4. jump-both:起止位置均有跳跃 n=3 ⇒ 25% 50% 75%; (100 / 4)
    5. start:等同 jump-start
    6. end:等同 jump-end
    7. step-start:等同 steps(1, jump-start)
    8. step-end:等同 steps(1, jump-end)
  /* animation 声明样式顺序 */ 
  /* animation-duration */
  /* animation-easing-function */
  /* animation-delay */ 
  /* animation-iteration-count */
  /* animation-direction */
  /* animation-fill-mode */
  /* animation-play-state */
  /* animation-name */
  animation: 3s ease-in 1s 2 reverse both paused slidein; 

  
  /* animation - duration | easing-function | delay | name */
  animation: 3s linear 1s slidein;
  
  
  /* more animations - duration | easing-function | delay | name */
  animation: 3s linear slidein, 3s ease-out 5s slideout;

 
  /* animation-name */
  animation-name: none;
  animation-name: animate1;
  animation-name: animate1, animate2;
  
  
  /* animation-timing-function */
  animation-timing-function: ease;
  animation-timing-function: step-start;
  animation-timing-function: cubic-bezier(0.1, 0.7, 1, 0.1);
  animation-timing-function: ease, step-start, cubic-bezier(0.1, 0.7, 1, 0.1);
  

css animation 动画触发方式

和css transition 触发动画方式相似

此外还可以增加一个图层,专门用于制作动画效果。

例如:鼠标在点击按钮时,会有涟漪动画。


// 涟漪动画定义
@keyframes ripple {
  0% {
    transform: scale(0);
    opacity: 1;
  }

  to {
    transform: scale(4);
    opacity: 0;
  }
}

// 图层动画 css
.ripple {
  position: absolute;
  border-radius: 50%;
  background: rgba(8, 7, 7, 0.2);
  pointer-events: none;

  animation: ripple 0.6s linear;
}

// 制作动画  这样每次点击按钮 就会生成动画,动画结束便销毁动画元素
const makeAnimate = (e: React.MouseEvent) => {
  const dom = e.currentTarget;
  const rect = dom.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const size = 100;

  const ripple = document.createElement('span');
  ripple.classList.add('ripple');
  ripple.style.width = `${size}px`;
  ripple.style.height = `${size}px`;
  ripple.style.left = `${x - size / 2}px`;
  ripple.style.top = `${y - size / 2}px`;
  dom.appendChild(ripple);

  ripple.addEventListener('animationend', () => {
    ripple.remove();
  });
};


svg 动画

常用的 SMIL 动画元素

  • <animate>:用于动画化单个属性。
  • <animateTransform>:用于动画化变换属性,如旋转、缩放、平移等。
  • <animateMotion>:用于沿着路径动画化元素。(路径动画)
  • <set>:用于在指定时间点设置属性值。
<svg width="100" height="100">
  <circle cx="50" cy="50" r="40" fill="red">
    <animate attributeName="cx" from="50" to="150" dur="2s" repeatCount="indefinite" />
  </circle>
</svg>

svg 描边动画

SVG动画的路径实现主要依赖属性:stroke(描边)和 fill(填充)。

  • stroke:定义svg的轮廓线。常用css属性有: stroke-dasharray(描边的样式),stroke-dashoffset(起始位置),stroke-color(描边的颜色),stroke-opacity(描边的透明度),stroke-linecap(描边端点形状)等。
  • fill:定义svg内部颜色或图案 ,常用css属性有fill-opacity(定义填充的透明度), fill-rule(定义填充规则)等。

stroke-dasharray (定义虚线的长度和间隔)

提供一个奇数或偶数数列,其中数与数之间用逗号或空格隔开,用来指定短划线和缺口的长度,并重复。 如果是偶数数列,则一个表示短线长度,一个表示缺口长度。 如果是奇数数列,将奇数数列复制一个变成偶数数列,然后按照短线,缺口的顺序绘制。

(偶数数列) stroke-dasharray="5, 5" x1="10" y1="10" x2="190" y2="10"表示从坐标(10,10)到(200,10)这条水平线上,短划线和缺口都为5个px

在这里插入图片描述

(奇数数列) stroke-dasharray="20 10 5" x1="10" y1="10" x2="190" y2="10"表示从坐标(10,10)到(200,10)这条水平线上,短划线和缺口按照20 10 5 20 10 5的顺序排列。

在这里插入图片描述

stroke-dashoffset (定义虚线的起始位置)

stroke-dashoffset 属性用于指定路径开始的距离(正值向左偏移,负值向右偏移)

在这里插入图片描述

描边动画示例:svg描边动画

js 动画

setTimeout / setInterval API

设定定时器,通过周期性的触发重复执行绘制动画的函数,来实现 “逐帧动画” 的效果。

  • 优势

    1. 具有很好的浏览器兼容性
  • 劣势

    1. 只能接近设备屏幕刷新率,无法做到和浏览器同步,所以可能会存在卡顿、丢帧、抖动的现象
    2. 由于浏览器单线程机制,存在队列中回调函数被阻塞的可能,所以无法保证每一次调用的时间间隔都相同,某次回调可能会被跳过,导致跳帧。

requestAnimationFrame API

为了弥补 setTimeout / setInterval 在动画方面的不足,浏览器提供了为动画而生的 API,它可以让 DOM 动画、Canvas 动画、 SVG 动画等有一个统一的刷新机制,随着浏览器的屏幕刷新,统一绘制动画帧。

  let id = null
  
  // 动画函数
  const draw = () => {
    /* 动画绘制... */
  }
  
  const start = () => {
    draw()
    cancelAnimationFrame(id)
    id = requestAnimationFrame(start)
  }
  const stop = () => { cancelAnimationFrame(id) }
  
  • 优势

    1. 由系统来决定回调函数的执行时机, 它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次, 这样就不会引起丢帧现象, 也不会导致动画出现卡顿的问题。
    2. 在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU的开销。
  • 不足

    1. 同 setTimeout/setInterval 一样,它是以逐帧动画的方式进行绘制,无法做到像 CSS 动画,让游览器自动根据两帧之间的差异创建插值,以实现补间动画的过渡效果。

Web Animations API

  1. requestAnimationFramesetTimeout/setInterval 都是以逐帧绘制的方式实现动画, 而 Animations API 不仅可以 “逐帧动画”,还可以实现 “补间动画” 的效果。
  2. CSS 动画有一定的局限性,需要事先预设动画样式,而且无法与 JS 进行交互。相比之下,Animations API 可以随时定义并使用动画,自然是更加灵活方便。

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API

语法示例:

  const element = document.getElementById("container");

  const animation = element.animate(
    [
      { transform: "translateY(0%)" },
      { transform: "translateY(100%)" },
    ],
    { duration: 3000, fill: "forwards" }
  );
  

代码示例:Web Animations API 动画

关于Filp动画

浏览器计算位置很快,绘制可能很慢。利用浏览器强大的计算能力,获取动画的起止状态,接着单独开启一个线程做动画。这样触发布局更新的操作,只会发生在一帧时间内,剩下的动画跑在单独的线程上,会更流畅。

介绍下FLIP 。

  1. F 代表 First,也就是动画的开始状态。
  2. L 代表 Last,代表动画结束状态。
  3. I 代表 Invert,也就是状态反转,使用 transform 等属性,创建单独的图层,并将元素状态反转回去。
  4. P 代表 Play,播放动画。

示例代码:

其中,在初始帧中,应用逆变换(translatescale),将元素从其最终状态逆变换到初始状态。

最后一帧 transform: "none" 的作用是将元素的变换属性重置为其最终状态。具体来说,transform: "none" 表示不应用任何变换,这意味着元素将恢复到由 CSS 设置的最终位置和大小。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>FLIP Animation Example</title>
  <style>
    #box {
      width: 100px;
      height: 100px;
      background-color: #4caf50;
      position: absolute;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <button id="animateButton" style="margin-top: 300px;">Animate</button>

  <script>
    const box = document.getElementById('box');
    const button = document.getElementById('animateButton');

    button.addEventListener('click', () => {
      // First: 记录初始状态
      const first = box.getBoundingClientRect();

      // 修改元素的位置
      box.style.top = `${300}px`;
      box.style.left = `${300}px`;

      // Last: 记录最终状态
      const last = box.getBoundingClientRect();

      // Invert: 计算初始状态和最终状态之间的变换
      const deltaX = first.left - last.left;
      const deltaY = first.top - last.top;
      const deltaW = first.width / last.width;
      const deltaH = first.height / last.height;


      // 应用 FLIP 动画
      box.animate(
        [
          {
            transformOrigin: "top left",
            transform: `
            translate(${deltaX}px, ${deltaY}px)
            scale(${deltaW}, ${deltaH})`,
          },
          {
            transformOrigin: "top left",
            transform: "none",
          },
        ],
        {
          duration: 300,
          easing: "ease-in-out",
          fill: "both",
        }
      );
    });
  </script>
</body>
</html>

相关推荐

  1. Sentinel背后原理

    2024-07-22 23:24:03       41 阅读
  2. Spring Bean 生命周期

    2024-07-22 23:24:03       40 阅读
  3. Spring Bean 生命周期

    2024-07-22 23:24:03       50 阅读
  4. 我是怎么学习单片机

    2024-07-22 23:24:03       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-22 23:24:03       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-22 23:24:03       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-22 23:24:03       45 阅读
  4. Python语言-面向对象

    2024-07-22 23:24:03       55 阅读

热门阅读

  1. SpringBoot中如何使用RabbitMq

    2024-07-22 23:24:03       11 阅读
  2. 【Python】Pandas简要教程

    2024-07-22 23:24:03       16 阅读
  3. 0基础认识C语言(函数)

    2024-07-22 23:24:03       20 阅读
  4. Gradle构建加速:自定义缓存策略全解析

    2024-07-22 23:24:03       14 阅读
  5. 2024.7.22

    2024-07-22 23:24:03       12 阅读
  6. Webpack 5 Tree Shaking与Module Federation

    2024-07-22 23:24:03       15 阅读
  7. MUX-VLAN基本概述

    2024-07-22 23:24:03       17 阅读
  8. 探索特征降维的奥秘:sklearn中的分层方法

    2024-07-22 23:24:03       12 阅读
  9. 学习数据处理的三要点

    2024-07-22 23:24:03       14 阅读
  10. Mojo模型与A/B测试:数据驱动决策的科学

    2024-07-22 23:24:03       17 阅读
  11. 降维与选择:用Scikit-Learn精炼数据特征的艺术

    2024-07-22 23:24:03       14 阅读