1.CSS过渡 #

<template>
  <div id="app">
    <button v-on:click="show = !show">
      Toggle
    </button>
    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      show: true
    }
  }
}
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity .5s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.fade-leave,
.fade-enter-to{
  opacity: 1;
}
</style>

这段代码创建了一个简单的界面,包含一个按钮和一段文本。这段文本可以根据按钮的点击来进行显示和隐藏,并且在切换时带有淡入淡出的过渡效果。

<template>部分:

<div id="app">:这是Vue应用的根元素,Vue实例将挂载到这个元素上。

<button v-on:click="show = !show">Toggle</button>:这是一个按钮。当点击这个按钮时,会触发v-on:click绑定的事件处理器,使得show的值取反。如果show原本为true,则会变为false,反之亦然。

<transition name="fade">:Vue的<transition>组件用于在元素或组件的插入、更新、移除时应用过渡效果。这里定义的过渡名称为fade,Vue会在过渡的不同阶段自动添加/删除与之对应的CSS类。

<p v-if="show">hello</p>:这是一段文本,它的显示和隐藏受到v-if="show"的控制。只有当showtrue时,这个段落才会被插入到DOM中。

<script>部分:

这是一个名为'App'的Vue组件。它的data函数返回一个对象,这个对象包含一个名为show的数据项,初始值为true。这意味着在初始化时,<p>元素会被插入到DOM中。

<style>部分:

这部分定义了与过渡效果相关的CSS样式。Vue会在过渡的不同阶段添加/删除对应的CSS类。

.fade-enter-active, .fade-leave-active:定义了过渡效果进行中的CSS样式。在这个例子中,元素在过渡时将改变其透明度。

.fade-enter, .fade-leave-to:定义了过渡效果开始前和结束后的CSS样式。在这个例子中,元素在过渡开始前和结束后的透明度为0,即完全透明。

.fade-leave, .fade-enter-to:定义了过渡效果开始和结束时的CSS样式。在这个例子中,元素在过渡开始和结束时的透明度为1,即完全不透明。

总的来说,这个应用会在点击按钮时切换段落的显示状态,并在段落显示和隐藏时应用淡入淡出的过渡效果。

2. 过渡的类名 #

在进入/离开的过渡中,会有 6 个 class 切换。

v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

v-leave-to:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则 v- 是这些类名的默认前缀。如果你使用了 ,那么 v-enter 会替换为 my-transition-enter。

v-enter-active 和 v-leave-active 可以控制进入/离开过渡的不同的缓和曲线,

3. CSS 动画 #

CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。

<template>
  <div id="app">
    <button v-on:click="show = !show">
      Toggle
    </button>
    <transition name="fade">
      <p v-if="show" style="background-color: lightgreen;">hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      show: true
    }
  }
}
</script>

<style>
.fade-enter-active {
  animation: fade 1s;
}
.fade-leave-active {
  animation: fade 1s reverse;
}
@keyframes fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

</style>

4.自定义过渡的类名 #

我们可以通过以下 attribute 来自定义过渡类名:

他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。

<template>
  <div id="app">
    <button v-on:click="show = !show">
      Toggle
    </button>
    <transition 
      name="animate__animated" 
      enter-active-class="animate__animated animate__backInDown" 
      leave-active-class="animate__animated animate__backOutDown"
    >
      <p v-if="show" style="background-color: lightgreen;">hello</p>
    </transition>
  </div>
</template>

<script>
import 'animate.css';
export default {
  name: 'App',
  data() {
    return {
      show: true
    }
  }
}
</script>

<style>
.fade-enter-active {
  animation: fade 1s;
}
.fade-leave-active {
  animation: fade 1s reverse;
}
</style>

5.appear #

可以通过 appear attribute 设置节点在初始渲染的过渡

<transition appear>
  <!-- ... -->
</transition>

6.列表的进入/离开过渡 #

<template>
  <div id="app">
    <button v-on:click="add">增加</button>
    <button v-on:click="remove">删除</button>
    <ul>
      <transition-group name="list" tag="ul">
        <li v-for="item in items" :key="item" class="item">
          {{ item }}
        </li>
      </transition-group>
    </ul>
  </div>
</template>

<script>
import 'animate.css';
export default {
  name: 'App',
  data: () => ({
    items: [1,2,3,4,5,6],
    nextNum: 10
  }),
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
}
</script>

<style>
ul{
  width:100px;
  list-style: none;
  list-style-type: none;
  padding: 0;
}
.item{
  background-color: lightgreen;
  transition: all 3s;
}

.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateX(-100%);
}
.list-leave, .list-enter-to {
  opacity: 1;
  transform: translateX(0);
}
</style>

7.参考 #

7.1. CSS过渡 #

CSS 过渡(CSS Transitions)是一个使得元素从一种样式逐渐改变为另一种样式的方法。你可以为任何可动画的 CSS 属性指定过渡效果。

以下是 CSS 过渡的主要属性:

  1. transition-property: 该属性定义应用过渡效果的 CSS 属性的名称。可以用逗号分隔的方式指定多个属性。

  2. transition-duration: 该属性定义过渡效果花费的时间。可以为每个属性单独设置或者设置一样的值应用到所有属性。

  3. transition-timing-function: 该属性描述过渡效果的速度曲线,常见的值有 lineareaseease-inease-outease-in-out

  4. transition-delay: 该属性指定开始出现过渡效果的时间。同样可以为每个属性单独设置或者设置一样的值应用到所有属性。

下面是一个简单的 CSS 过渡示例,当用户鼠标悬停在元素上时,元素的背景颜色将在 2 秒内从红色平滑过渡到蓝色:

.my-element {
  width: 100px;
  height: 100px;
  background-color: red;

  /* Apply a transition to the background-color property */
  transition-property: background-color;
  transition-duration: 2s;
  transition-timing-function: linear;
  transition-delay: 0s;
}

.my-element:hover {
  background-color: blue;
}

还有一种简便写法,可以将所有过渡属性在一个声明中定义:

.my-element {
  /* other styles... */

  /* property name | duration | timing function | delay */
  transition: background-color 2s linear 0s;
}

这样当 CSS 属性值发生变化时,过渡效果就会被触发。这个变化可以是由于伪类(如 :hover)或者 JavaScript 的 DOM 操作引起的。

需要注意的是,并非所有 CSS 属性都可以过渡。例如,displaycontentcounter-reset 等属性不能过渡。你可以在这里查看所有可动画的 CSS 属性。

7.2. transitionend #

transitionend 是一个在 CSS 过渡完成后触发的事件。当 CSS 过渡完成,或者如果过渡没有完成,而是在执行的过程中被取消,就会触发 transitionend 事件。这个事件可以帮助你在 CSS 过渡结束后执行一些操作,例如开始另一个动画或修改页面的其他部分。

这个事件的 Event 对象包括一些有用的信息,包括 propertyName(完成过渡的属性名称),elapsedTime(完成过渡花费的时间,不包括任何关联的 transition-delay 时间),以及 pseudoElement(表示过渡发生在哪个伪元素上,如果没有伪元素,则为一个空字符串)。

下面是一个简单的示例,它在 transitionend 事件触发时打印一条消息:

let element = document.querySelector('.my-element');

element.addEventListener('transitionend', function(event) {
  console.log(`Finished transitioning ${event.propertyName}`);
});

在这个示例中,当 .my-element 元素上的过渡完成时,transitionend 事件会被触发,并打印出完成过渡的属性名称。

注意,如果你在一个元素上应用了多个过渡(例如同时改变 widthheight),transitionend 事件将为每个属性触发一次。也就是说,如果你同时改变了 widthheight,那么 transitionend 事件将触发两次。这是因为每个属性的过渡都被视为一个独立的过程。

另外,你可能需要在多个浏览器上监听 transitionend 事件,因为这个事件在一些旧的或者非标准的浏览器中可能需要使用特定的事件名,例如 webkitTransitionEnd

7.3. CSS动画 #

@keyframes 是 CSS 动画的一部分,允许你在动画序列中定义中间步骤。这可以让你创建复杂的,有细致控制的动画。

@keyframes 规则中,你可以定义一个由多个关键帧(keyframes)组成的动画序列,每个关键帧都描述了动画的一部分。这些关键帧由百分比来表示,0% 表示动画的开始,100% 表示动画的结束,中间的百分比表示动画中间的某个时刻。

以下是一个简单的 @keyframes 示例,定义了一个名为 slide 的动画,它使元素在动画开始时向右移动,然后在动画结束时回到原位:

@keyframes slide {
  0% {
    transform: translateX(0);
  }

  50% {
    transform: translateX(100px);
  }

  100% {
    transform: translateX(0);
  }
}

定义好 @keyframes 后,你可以在任何元素上使用 animation 属性来应用这个动画:

.my-element {
  animation-name: slide;
  animation-duration: 2s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

在这个例子中,.my-element 会无限次地运行 slide 动画,每次运行持续 2 秒,动画的速度曲线为线性。

CSS 的 animation 属性包括了一系列子属性,你可以用它们来控制动画的行为。例如,animation-duration 控制动画的持续时间,animation-timing-function 控制动画的速度曲线,animation-iteration-count 控制动画的重复次数。

还有一种简便写法,可以将所有动画属性在一个声明中定义:

.my-element {
  /* name | duration | timing function | delay | iteration count | direction */
  animation: slide 2s linear 0s infinite normal;
}

需要注意的是,和 CSS 过渡不同,CSS 动画不需要一个触发条件,它会在页面加载后立即开始。如果你需要在特定的时间开始动画,你可以使用 JavaScript 来动态添加或删除动画样式。

7.4. animationend #

animationend 是一个在 CSS 动画完成后触发的事件。当 CSS 动画完成,或者在动画执行的过程中被取消(例如,当 animation-iteration-count 被满足或者 animation 属性被移除),都会触发 animationend 事件。这个事件可以帮助你在 CSS 动画结束后执行一些操作,例如开始另一个动画,或修改页面的其他部分。

这个事件的 Event 对象包括一些有用的信息,包括 animationName(完成动画的名称),elapsedTime(动画周期完成所花费的时间,不包括任何 animation-delay 时间)和 pseudoElement(表示动画发生在哪个伪元素上,如果没有伪元素,则为一个空字符串)。

下面是一个简单的示例,它在 animationend 事件触发时打印一条消息:

let element = document.querySelector('.my-element');

element.addEventListener('animationend', function(event) {
  console.log(`Finished animating ${event.animationName}`);
});

在这个示例中,当 .my-element 元素上的动画完成时,animationend 事件会被触发,并打印出完成的动画名称。

需要注意的是,如果你在一个元素上应用了多个动画,或者如果你设置了 animation-iteration-count 为无限(infinite),animationend 事件将会在每个动画周期结束后触发。

另外,你可能需要在多个浏览器上监听 animationend 事件,因为这个事件在一些旧的或者非标准的浏览器中可能需要使用特定的事件名,例如 webkitAnimationEnd

7.5. FLIP动画 #

FLIP 是一个创建动画的技术,由 Google 的 Paul Lewis 提出。这个词是 First, Last, Invert, Play (首先,最后,反转,播放) 的缩写,可以用来创建平滑的,高性能的动画。

以下是 FLIP 技术的基本步骤:

这个方法的好处是,它仅使用 compositor thread,避免了引起重绘的昂贵操作,所以动画看起来非常平滑。

下面是一个简单的使用 JavaScript 和 CSS 实现的 FLIP 动画示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .my-element {
            width: 100px;
            height: 100px;
            background-color: green;
        }
        .final-state{
            width: 200px;
            height: 200px;
        }
    </style>
</head>
<body>
    <div class="my-element"></div>
    <script>
        let element = document.querySelector('.my-element');
        // First: get the initial position
        let first = element.getBoundingClientRect();
        // Apply the final state
        element.classList.add('final-state');
        // Last: get the final position
        let last = element.getBoundingClientRect();
        // Invert: calculate the difference between the final and initial state
        let deltaX = first.left - last.left;
        let deltaY = first.top - last.top;
        let deltaW = first.width / last.width;
        let deltaH = first.height / last.height;
        // Apply a transform to the element to invert the changes
        element.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
        // Force a synchronous layout with getComputedStyle
        window.getComputedStyle(element).transform;
        // Play the animation (remove the transforms)
        element.style.transition = 'transform 1s';
        element.style.transform = '';
    </script>
</body>

</html>

在上述代码中,元素的位置和大小首先被获取,然后应用了一个结束状态的样式(.final-state)。再次获取元素的位置和大小后,计算出了开始和结束状态之间的差异,并应用了一个 CSS transform 来反转这个改变。然后通过 getComputedStyle 强制进行同步布局,最后再通过 CSS transition 移除 transform,使元素以动画的形式过渡到最终状态。

请注意,你需要在 CSS 中定义 .final-state 样式和初始状态。还要注意,在复杂的 UI 中使用 FLIP 可能会有些复杂,因为你需要手动计算和应用多个元素之间的所有布局改变。

7.6.transition-group #

TransitionGroup 是 Vue.js 提供的一个抽象组件,用于在 Vue 组件中管理列表的过渡效果。它是基于 Vue 的过渡系统构建的,可以方便地对列表中的元素进行添加、删除和移动时的过渡动画处理。

使用 TransitionGroup 组件时,需要在组件的根元素上添加 v-elsev-bind:key 指令来标识每个子元素的唯一性。这个唯一标识用于帮助 Vue 区分每个子元素,并在添加、删除或移动元素时进行动画过渡。

下面是一个使用 TransitionGroup 的示例:

<transition-group name="list" tag="ul">
  <li v-for="item in items" :key="item.id" class="list-item">
    {{ item.text }}
  </li>
</transition-group>

在上面的示例中,transition-group 标签包裹了一个 li 列表,并指定了过渡的名称为 "list",同时使用 tag 属性指定了最终渲染的标签类型为 ul

在 Vue 实例中,需要提供一个数组 items 来作为列表数据源,并为每个列表项设置一个唯一的 id 作为 key。这样,在对 items 数组进行增删改操作时,TransitionGroup 就能够自动检测到变化,并为每个元素应用相应的过渡效果。

需要注意的是,为了使过渡生效,需要在 CSS 样式中定义相应的过渡效果。可以使用 Vue 提供的过渡类名来自定义过渡效果,例如在上述示例中,可以在样式中定义以下类名:

.list-item-enter-active,
.list-item-leave-active {
  transition: opacity 0.5s;
}

.list-item-enter,
.list-item-leave-to {
  opacity: 0;
}

以上样式定义了一个淡入淡出的过渡效果,当元素被添加或删除时,其透明度会在0.5秒的时间内进行过渡动画。

总结一下,TransitionGroup 是 Vue.js 提供的一个用于管理列表过渡效果的抽象组件。它通过在每个子元素上设置唯一的 key,并配合过渡效果的 CSS 样式,可以实现在列表中添加、删除和移动元素时的动画过渡效果。