vue3.0(十三)内置组件Transition和TransitionGroup


简介

制作基于状态变化的过渡和动画:

  • <Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。
  • <TransitionGroup> 会在一个 v-for 列表中的元素或组件被插入,移动,或移除时应用动画。

一、<Transition> 组件

<Transition> 是一个内置组件,它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。

1.基本应用

进入或离开触发条件:

  • 由 v-if 所触发的切换
  • 由 v-show 所触发的切换
  • 由特殊元素<component>切换的动态组件
  • 改变特殊的 key 属性
    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition>
          <p v-if="show">hello</p>
        </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style scoped>
    /*
    * CSS 过渡 class
      1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
      2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,
        在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
      3. v-enter-to:进入动画的结束状态。
        在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
      4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
      5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。
        在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。
        这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
      6. v-leave-to:离开动画的结束状态。
        在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。
     */
    .v-enter-active,
    .v-leave-active {
      transition: opacity 0.5s ease;
    }
    
    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
    </style>
    
    注:<Transition> 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。

2.动画过程中发生的事情

  • Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除。
  • 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
  • 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。

3.基于 CSS 的过渡效果

  1. 为过渡效果命名
    传一个 name prop 来声明一个过渡效果名:
    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <!-- name设置的过渡名 -->
        <Transition name="show">
          <p v-if="show">hello</p>
        </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style scoped>
    .show-enter-active,
    .show-leave-active {
      transition: opacity 1s ease;
    }
    
    .show-enter-from,
    .show-leave-to {
      opacity: 0;
    }
    </style>
    
  2. CSS 的 transition
    <Transition> 一般都会搭配原生 CSS 过渡一起使用。这个 transition CSS 属性是一个简写形式,使我们可以一次定义一个过渡的各个方面,包括需要执行动画的属性、持续时间和速度曲线。
    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition name="show">
          <p v-if="show">hello</p>
        </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    })
    </script>
    <style scoped>
     .show-enter-active {
      transition: all 0.3s ease-out;
    }
    
    .show-leave-active {
      transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
    }
    
    .show-enter-from,
    .show-leave-to {
      transform: translateX(20px);
      opacity: 0;
    }
    </style>
    

Transition动画视频


3. CSS 的 animation

  • 原生 CSS 动画和 CSS transition 的应用方式基本上是相同的,只有一点不同,那就是 *-enter-from 不是在元素插入后立即移除,而是在一个 animationend 事件触发时被移除。
  • 对于大多数的 CSS 动画,我们可以简单地在 *-enter-active 和 *-leave-active class 下声明它们。
    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition name="show">
          <p v-if="show">CSS transition</p>
        </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style scoped>
     .show-enter-active {
      animation: bounce-in 0.5s;
    }
    
    .show-leave-active {
      animation: bounce-in 0.5s reverse;
    }
    
    @keyframes bounce-in {
      0% {
        transform: scale(0);
      }
      50% {
        transform: scale(1.25);
      }
      100% {
        transform: scale(1);
      }
    }
    </style>
    

CSS 的 animation


4. 自定义过渡 class
可以向 <Transition> 传递以下的 props 来指定自定义的过渡 class:

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class
    	<template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition
          name="custom-classes"
          enter-active-class="animate__animated animate__tada"
          leave-active-class="animate__animated animate__bounceOutRight"
        >
        <p v-if="show">custom-classes</p>
      </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style>
    @import "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css";
    </style>
    

Transition自定义过渡 class

  1. 同时使用 transition 和 animation
    Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 transitionend 或 animationend,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。
    传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation 或 transition
    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition
          name="show"
          type="transition"
        >
        <p v-if="show">type-animation</p>
      </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref, reactive } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style lang="scss" scoped>
    //  元素开始进入的状态 | 元素离开结束的状态
    .show-enter-from,
    .show-leave-to {
    opacity: 0;
    }
    // 元素进入结束的状态 | 元素开始离开的状态
    .show-enter-to,
    .show-leave-from {
    opacity: 1;
    }
    // 元素进入 | 结束时,过渡的效果
    .show-enter-active,
    .show-leave-active {
      // 过渡动画的使用
    transition: opacity 2s linear 0s;
    }
    .show-enter-active {
    animation: show-scale 2s linear 0s;
    }
    // 离开的时候设置成相反哒
    .show-leave-active {
    animation: show-scale 2s linear 0s reverse;
    }
      @keyframes show-scale {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.3);
        }
        100% {
          transform: scale(1);
        }
    }
    </style>
    

同时使用 transition 和 animation

  1. 深层级过渡与显式过渡时长
    尽管过渡 class 仅能应用在<Transition> 的直接子元素上,我们还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果:
    <Transition> 组件会通过监听过渡根元素上的第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成。通过向 <Transition> 组件传入 duration prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间:

    <template>
      <div>
        <button @click="show = !show">Toggle</button>
        <Transition :duration="550" name="show">
          <div class="outer" v-if="show">
            <div class="inner">
              Hello inner
            </div>
          </div>
       </Transition>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, ref } from 'vue'
    export default defineComponent({
      setup () {
        const show = ref<boolean>(false)
        return {
          show
        }
      }
    
    })
    </script>
    <style lang="scss" scoped>
    .outer {
      width: 200px;
      margin: auto;
    }
    
    .outer, .inner {
      background: #eee;
      padding: 30px;
      min-height: 100px;
    }
    .inner { 
      background: #ccc;
    }
    
    .show-enter-active, .show-leave-active {
      transition: all 0.3s ease-in-out;
    }
    .show-leave-active {
      transition-delay: 0.25s;
    }
    .show-enter-from,
    .show-leave-to {
      transform: translateY(30px);
      opacity: 0;
    }
    // 可以在深层元素上添加一个过渡延迟,从而创建一个带渐进延迟的动画序列:
    .show-enter-active .inner,
    .show-leave-active .inner { 
      transition: all 0.3s ease-in-out;
    }
    
    .show-enter-active .inner {
      transition-delay: 0.25s;
    }
    
    .show-enter-from .inner,
    .show-leave-to .inner {
      transform: translateX(30px);
      opacity: 0.001;
    }
    </style>
    

Transition 深层级过渡与显式过渡时长

  1. 性能考量
    transform 和 opacity 之类的。用这些属性制作动画非常高效,因为:
  • 他们在动画过程中不会影响到 DOM 结构,因此不会每一帧都触发昂贵的 CSS 布局重新计算。
  • 大多数的现代浏览器都可以在执行 transform 动画时利用 GPU 进行硬件加速。
    相比之下,像 height 或者 margin 这样的属性会触发 CSS 布局变动,因此执行它们的动画效果更昂贵,需要谨慎使用。可以在 CSS-Triggers 这类的网站查询哪些属性会在执行动画时触发 CSS 布局变动。

4.JavaScript 钩子

可以通过监听<Transition> 组件事件的方式在过渡过程中挂上钩子函数:

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
  :css="false
>
  <!-- ... -->
</Transition>
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}

// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 当进入过渡完成时调用。
function onAfterEnter(el) {}

// 当进入过渡在完成之前被取消时调用
function onEnterCancelled(el) {}

// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}

// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}

// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}

这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css=“false” prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果:
在有了 :css=“false” 后,就可以全权负责控制什么时候过渡结束了。这种情况下对于 @enter 和 @leave 钩子来说,回调函数 done 就是必须的。否则,钩子将被同步调用,过渡将立即完成。

5.可复用过渡效果

要创建一个可被复用的过渡,我们需要为 <Transition> 组件创建一个包装组件,并向内传入插槽内容:

<!-- 封装的组件 -->
<script>
// JavaScript 钩子逻辑...
</script>

<template>
  <!-- 包装内置的 Transition 组件 -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- 向内传递插槽内容 -->
  </Transition>
</template>

<style>
/*
  必要的 CSS...
  注意:避免在这里使用 <style scoped>
  因为那不会应用到插槽内容上
*/
</style>
// 组件中的使用
<MyTransition>
  <div v-if="show">Hello</div>
</MyTransition>

6.出现时过渡

在某个节点初次渲染时应用一个过渡效果,你可以添加 appear prop:

<Transition appear>
  ...
</Transition>

7.元素间过渡

除了通过 v-if / v-show 切换一个元素,我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可


<template>
  <div>
    <div class="btn-container">
      <Transition name="slide-up">
        <button v-if="docState === 'saved'"
                @click="docState = 'edited'">Edit</button>
        <button v-else-if="docState === 'edited'"
                @click="docState = 'editing'">Save</button>
        <button v-else-if="docState === 'editing'"
                @click="docState = 'saved'">Cancel</button>
      </Transition>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'

const docState = ref('saved')
</script>

<style>
.btn-container {
  display: inline-block;
  position: relative;
  height: 1em;
}

button {
  position: absolute;
}

.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.25s ease-out;
}

.slide-up-enter-from {
  opacity: 0;
  transform: translateY(30px);
}

.slide-up-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

8.过渡模式

进入和离开的元素都是在同时开始动画的,因此不得不将它们设为 position: absolute 以避免二者同时存在时出现的布局问题。
然而,很多情况下这可能并不符合需求。可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 <Transition> 传入一个 mode prop 来实现这个行为:

<Transition mode="out-in">
  ...
</Transition>

9.组件间过渡

<Transition> 也可以作用于动态组件之间的切换:

<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>

10.动态过渡

<Transition> 的 props (比如 name) 也可以是动态的!这让我们可以根据状态变化动态地应用不同类型的过渡:

<Transition :name="transitionName">
  <!-- ... -->
</Transition>

特性的用处是可以提前定义好多组 CSS 过渡或动画的 class,然后在它们之间动态切换。

11.使用 Key Attribute 过渡

有时为了触发过渡,你需要强制重新渲染 DOM 元素。

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);

setInterval(() => count.value++, 1000);
</script>

如果不使用 key attribute,则只有文本节点会被更新,因此不会发生过渡。但是,有了 key 属性,Vue 就知道在 count 改变时创建一个新的 span 元素,因此 Transition 组件有两个不同的元素在它们之间进行过渡。

二、<TransitionGroup>组件

<TransitionGroup> 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
当在 DOM 内模板中使用时,组件名需要写为 <transition-group>

1.​​进入 / 离开动画

<TransitionGroup> 对一个 v-for 列表添加进入 / 离开动画的示例:

<template>
  <div>
    <TransitionGroup name="list" tag="ul">
      <li v-for="item in list" :key="item">
        {{ item }}
      </li>
    </TransitionGroup>
    <p>
      <button @click="handleAdd">在任意位置添加一项</button>
    </p>
    <p>
      <button @click="handleDelete">移除任意位置上一项</button>
    </p>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
  setup () {
    const list = ref([])
    
    function handleAdd () {
      const randomNumber = Math.floor(Math.random() * list.value.length)
      list.value.splice(randomNumber, 0, list.value.length)
    }
    function handleDelete () {
      const randomNumber = Math.floor(Math.random() * list.value.length) + 1
      list.value.splice(randomNumber, 1)
    }
    return {
      list,
      handleAdd,
      handleDelete
    }
  }

})
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.25s ease-out;
}

.list-enter-from {
  opacity: 0;
  transform: translateY(30px);
}

.list-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

<Transition><TransitionGroup>的区别

<TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:

  • 默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
  • 过渡模式在这里不可用,因为不再是在互斥的元素之间进行切换。
  • 列表中的每个元素都必须有一个独一无二的 key attribute。
  • CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值