小码哥的IT人生

首页 > JS > vue

Vue 3.0 过渡与动画概述

vue 2022-05-07 17:05:44小码哥的IT人生shichen

Vue 提供了一些抽象概念,可以帮助处理过渡和动画,特别是在响应某些变化时。这些抽象的概念包括:

  1. 在 CSS 和 JS 中,使用内置的 <transition> 组件来钩住组件中进入和离开 DOM。
  2. 过渡模式,以便你在过渡期间编排顺序。
  3. 在处理多个元素位置更新时,使用 <transition-group> 组件,通过 FLIP 技术来提高性能。
  4. 使用 watchers 来处理应用中不同状态的过渡。

我们将在本指南接下来的三个部分中介绍所有这些以及更多内容。然而,除了提供这些有用的 API 之外,值得一提的是,我们前面介绍的 class 和 style 声明也可以应用于动画和过渡,用于更简单的用例。

在下一节中,我们将回顾一些 web 动画和过渡的基础知识,并提供一些资源链接以进行进一步的研究。如果你已经熟悉 web 动画,并且了解这些原理如何与 Vue 的某些指令配合使用,可以跳过这一节。对于希望在开始学习之前进一步了解网络动画基础知识的其他人,请继续阅读。

#基于 class 的动画和过渡

尽管 <transition> 组件对于组件的进入和离开非常有用,但你也可以通过添加一个条件 class 来激活动画,而无需挂载组件。

<div id="demo">
  Push this button to do something you shouldn't be doing:<br />
  <div :class="{ shake: noActivated }">
    <button @click="noActivated = true">Click me</button>
    <span v-if="noActivated">Oh no!</span>
  </div>
</div>
.shake {
  animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}
@keyframes shake {
  10%,
  90% {
    transform: translate3d(-1px, 0, 0);
  }
  20%,
  80% {
    transform: translate3d(2px, 0, 0);
  }
  30%,
  50%,
  70% {
    transform: translate3d(-4px, 0, 0);
  }
  40%,
  60% {
    transform: translate3d(4px, 0, 0);
  }
}
const Demo = {
  data() {
    return {
      noActivated: false
    }
  }
}
Vue.createApp(Demo).mount('#demo')

试一试

完整实例:

<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3.0 过渡&动画概述</title>
<script src="https://unpkg.com/vue@3.0.0/dist/vue.global.js"></script>
<style>
body {
  margin: 30px;
}
button {
  background: #d93419;
  border-radius: 4px;
  display: inline-block;
  border: none;
  padding: 0.75rem 1rem;
  margin: 20px 10px 0 0;
  text-decoration: none;
  color: #ffffff;
  font-family: sans-serif;
  font-size: 1rem;
  cursor: pointer;
  text-align: center;
  -webkit-appearance: none;
  -moz-appearance: none;
}
button:focus {
  outline: 1px dashed #fff;
  outline-offset: -3px;
}
.shake {
  animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}
@keyframes shake {
  10%, 90% {
    transform: translate3d(-1px, 0, 0);
  }
  20%, 80% {
    transform: translate3d(2px, 0, 0);
  }
  30%, 50%, 70% {
    transform: translate3d(-4px, 0, 0);
  }
  40%, 60% {
    transform: translate3d(4px, 0, 0);
  }
}
</style>
</head>
<body>
<div id="demo">
  Push this button to do something you shouldn't be doing:<br>
  <div :class="{ shake: noActivated }">
    <button @click="noActivated = true">Click me</button>
    <span v-if="noActivated">Oh no!</span>
  </div>
</div>
<script>
const Demo = {
  data() {
    return {
      noActivated: false
    }
  }
}
Vue.createApp(Demo).mount('#demo')
</script>
</body>
</html>

可以使用本站在线JavaScript测试工具测试上述代码运行效果:http://www.phpcodeweb.com/runjs.html

#过渡与 Style 绑定

一些过渡效果可以通过插值的方式来实现,例如在发生交互时将样式绑定到元素上。以这个例子为例:

<div id="demo">
  <div
    @mousemove="xCoordinate"
    :style="{ backgroundColor: `hsl(${x}, 80%, 50%)` }"
    class="movearea"
  >
    <h3>Move your mouse across the screen...</h3>
    <p>x: {{x}}</p>
  </div>
</div>
.movearea {
  transition: 0.2s background-color ease;
}
const Demo = {
  data() {
    return {
      x: 0
    }
  },
  methods: {
    xCoordinate(e) {
      this.x = e.clientX
    }
  }
}
Vue.createApp(Demo).mount('#demo')

试一试

完整实例:

<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3.0 过渡&动画概述</title>
<script src="https://unpkg.com/vue@3.0.0/dist/vue.global.js"></script>
<style>
#demo {
  width: 100vw;
  height: 100vh;
}
.movearea {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  padding: 6vmin;
  transition: 0.2s background-color ease;
}
</style>
</head>
<body>
<div id="demo">
  <div @mousemove="xCoordinate"
       :style="{ backgroundColor: `hsl(${x}, 80%, 50%)` }"
       class="movearea">
    <h3>Move your mouse across the screen...</h3>
    <p>x: {{x}}</p>
  </div>
</div>
<script>
const Demo = {
  data() {
    return {
      x: 0
    }
  },
  methods: {
    xCoordinate(e) {
      this.x = e.clientX;
    }
  }
}
Vue.createApp(Demo).mount('#demo')
</script>
</body>
</html>

可以使用本站在线JavaScript测试工具测试上述代码运行效果:http://www.phpcodeweb.com/runjs.html

在这个例子中,我们是通过使用插值来创建动画,将触发条件添加到鼠标的移动过程上。同时将 CSS 过渡属性应用在元素上,让元素知道在更新时要使用什么过渡效果。

#性能

你可能注意到上面显示的动画使用了 transforms 之类的东西,并应用了诸如 perspective 之类的奇怪的 property——为什么它们是这样实现的,而不是仅仅使用 margintop 等?

我们可以通过对性能的了解,在 web 上创建极其流畅的动画。我们希望尽可能对元素动画进行硬件加速,并使用不触发重绘的 property。我们来介绍一下如何实现这个目标。

#Transform 和 Opacity

我们可以通过工具,例如 CSS Triggers 来查看哪些属性会在动画时触发重绘。在工具中,查看 transform 的相关内容,你将看到:

非常好的是,更改 transform 不会触发任何几何形状变化或绘制。这意味着该操作可能是由合成器线程在 GPU 的帮助下执行。

opacity 属性的行为也类似。因此,他们是在 web 上做元素移动的理想选择。

#硬件加速

诸如 perspectivebackface-visibilitytransform:translateZ(x) 等 property 将让浏览器知道你需要硬件加速。

如果要对一个元素进行硬件加速,可以应用以下任何一个 property (并不是需要全部,任意一个就可以):

perspective: 1000px;
backface-visibility: hidden;
transform: translateZ(0);

许多像 GreenSock 这样的 JS 库都会默认你需要硬件加速,并在默认情况下应用,所以你不需要手动设置它们。

#Timing

对于简单 UI 过渡,即从一个状态到另一个没有中间状态的状态,通常使用 0.1s 到 0.4s 之间的计时,大多数人发现 0.25s 是一个最佳选择。你能用这个定时做任何事情吗?并不是。如果你有一些元素需要移动更大的距离,或者有更多的步骤或状态变化,0.25s 并不会有很好的效果,你将不得不有更多的目的性,而且定时也需要更加独特。但这并不意味着你不能在应用中重复使用效果好的默认值。

你也可能会发现,起始动画比结束动画的时间稍长一些,看起来会更好一些。用户通常是在动画开始时被引导的,而在动画结束时没有那么多耐心,因为他们想继续他们的动作。

#Easing

Easing 是在动画中表达深度的一个重要方式。动画新手最常犯的一个错误是在起始动画节点使用 ease-in,在结束动画节点使用 ease-out。实际上你需要的是反过来的。

如果我们将这些状态应用于过渡,它应该像这样:

.button {
  background: #1b8f5a;
  /* 应用于初始状态,因此此转换将应用于返回状态 */
  transition: background 0.25s ease-in;
}
.button:hover {
  background: #3eaf7c;
  /* 应用于悬停状态,因此在触发悬停时将应用此过渡 */
  transition: background 0.35s ease-out;
}

试一试

完整实例:

<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3.0 过渡&动画概述</title>
<script src="https://unpkg.com/vue@3.0.0/dist/vue.global.js"></script>
<style>
body {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 40px;
}
.button {
  background: #1b8f5a;
  transition: background 0.25s ease-in;
  border-radius: 4px;
  display: inline-block;
  border: none;
  padding: 0.75rem 1rem;
  margin: 0;
  text-decoration: none;
  color: #ffffff;
  font-family: sans-serif;
  font-size: 1rem;
  cursor: pointer;
  text-align: center;
  -webkit-appearance: none;
  -moz-appearance: none;
}
button:hover,
button:focus {
  transition: background 0.3s ease-out;
  background: #3eaf7c;
}
button:focus {
  outline: 1px solid #fff;
  outline-offset: -4px;
}
</style>
</head>
<body>
<div id="app">
  <button class="button">
    {{ message }}
  </button>
</div>
<script>
const ButtonApp = {
  data() {
    return {
      message: 'Hover Me!'
    }
  }
}
Vue.createApp(ButtonApp).mount('#app')
</script>
</body>
</html>

可以使用本站在线JavaScript测试工具测试上述代码运行效果:http://www.phpcodeweb.com/runjs.html

Easing 也可以表达动画元素的质量。以下面的 Pen 为例,你认为哪个球是硬的,哪个球是软的?

试一试

完整实例:

<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3.0 过渡&动画概述</title>
<script src="https://unpkg.com/vue@3.0.0/dist/vue.global.js"></script>
<style>
@import "compass/css3";
html {
  background: #222;
}
.container {
  width: 500px;
  margin: 10px auto;
  border: 1px solid #333;
  height: 280px;
  background: #111;
  overflow: hidden;
}
.unit {
  float:left;
  width: 249px;
  height: 280px;
  background-color: #222426; // Old browsers
  @include filter-gradient(#222426, #111111, horizontal); // IE6-9 fallback on horizontal gradient
  @include background-image(radial-gradient(center, ellipse cover, #222426 0%,#111111 100%));
  &:first-child {
    border-right: 1px solid #333;
  }
}
.overlay {
  position: absolute;
  background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/grain.png");
  top: 10px;
  width: 500px;
  height: 280px;
  left: 50%;
  margin-left: -250px;
  z-index: 1000;
  opacity: 0.11;
  animation: filmgrain 0.4s steps(3) infinite;
}
.ball {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #959595; // Old browsers
@include filter-gradient(#959595, #494949, horizontal); // IE6-9 fallback on horizontal gradient
@include background-image(radial-gradient(center, ellipse cover, #959595 0%,#4e4e4e 79%,#494949 100%));
  transform: translateZ(0);
  margin: 30px auto;
  position: relative;
  z-index: 300;
}
.ball-shadow {
  position: absolute;
  width: 50px;
  height: 5px;
  border-radius: 50%;
  background: #000;
  top: 280px;
  margin-left: 100px;
  z-index: 2;
  opacity: 0;
  box-shadow: 0 0 10px 5px rgba(0,0,0,0.2);
  animation: boom 1.6s 0.7s ease-in-out infinite;
}
@keyframes boom {
  50% { opacity: 0.8; }
}
@keyframes filmgrain {
  100% { background-position: 200% 0%; }
}
</style>
</head>
<body>
<div class='container'>
  <div class='unit'>
    <div class='ball ball1'></div>
    <div class='ball-shadow'></div>
  </div>
  <div class='unit'>
    <div class='ball ball2'></div>
    <div class='ball-shadow'></div>
  </div>
</div>
<div class='overlay'></div>
<script>
const ball1 = document.querySelector('.ball1')
const ball2 = document.querySelector('.ball2')
gsap.from(ball1, {
  duration: 0.8,
  y: 220,
  repeat: -1,
  yoyo: true,
  ease: Power4.easeOut
});
gsap.from(ball2, {
  duration: 0.8,
  y: 225,
  repeat: -1,
  yoyo: true,
  ease: Circ.easeOut
});
gsap.fromTo(ball2, {
  duration: 0.8,
  scaleY: 1
}, {
  scaleY: 1.1,
  repeat: -1,
  yoyo: true,
  ease: Circ.easeOut
});
</script>
</body>
</html>

可以使用本站在线JavaScript测试工具测试上述代码运行效果:http://www.phpcodeweb.com/runjs.html

你可以通过调整你的 Easing 来获得很多独特的效果,使你的动画非常时尚。CSS 允许你通过调整 cubic bezier 属性来修改 Easing,Lea Verou 开发的这个 playground 对探索这个问题非常有帮助。

虽然使用 cubic-bezier ease 提供的两个控制柄可以为简单的动画获得很好的效果,但是 JavaScript 允许多个控制柄,以此支持更多的变化。

以弹跳为例。在 CSS 中,我们必须声明向上和向下的每个关键帧。在 JavaScript 中,我们可以通过在 greensock API(GSAP) 中声明 bounce 来描述 ease 中所有这些移动 (其他 JS 库有其他类型的 easing 默认值)。

这里是 CSS 中用来实现 bounce 的代码 (来自 animate.css 的例子):

@keyframes bounceInDown {
  from,
  60%,
  75%,
  90%,
  to {
    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  }
  0% {
    opacity: 0;
    transform: translate3d(0, -3000px, 0) scaleY(3);
  }
  60% {
    opacity: 1;
    transform: translate3d(0, 25px, 0) scaleY(0.9);
  }
  75% {
    transform: translate3d(0, -10px, 0) scaleY(0.95);
  }
  90% {
    transform: translate3d(0, 5px, 0) scaleY(0.985);
  }
  to {
    transform: translate3d(0, 0, 0);
  }
}
.bounceInDown {
  animation-name: bounceInDown;
}

下面是 JS 中使用 GreenSock 实现相同的 bounce:

gsap.from(element, { duration: 1, ease: 'bounce.out', y: -500 })

我们将在之后章节的部分示例中使用 GreenSock。他们有一个很棒的 ease visualizer,帮助你建立精心制作的画架。

#进一步阅读

  1. 界面动画设计:通过 Val Head 动画改善用户体验
  2. Animation at Work 作者:Rachel Nabors
 

版权所有 © 小码哥的IT人生
Copyright © phpcodeweb All Rights Reserved
ICP备案号:苏ICP备17019232号-2  

苏公网安备 32030202000762号

© 2021-2024