Vue 2 进入、离开和列表过渡

前言

Vue 提供了多种方式来实现过渡效果。

  1. 在 CSS 过渡和动画中自动应用 class
  2. 配合 CSS 动画库
  3. 过渡钩子函数中使用 JavaScript 操作 DOM
  4. 配合 JavaScript 动画库

单元素/组件的过渡

将元素或组件放在 <transition> 中可以在下列情形中触发过渡效果:

  1. 使用了 v-if
  2. 使用了 v-show
  3. 使用了动态组件
  4. 组件根节点

如果没有找到 JavaScript 过渡钩子和 CSS 过渡/动画,DOM 操作在下一帧中立即执行。


过渡的类名

Vue提供了 6 个可以自动生成的 CSS 类名,如下图。

可以自动生成的类名是指给 transition 组件的 name 属性指定一个值,假设是 fade,那么该 name 将自动扩展生成 .fade-enter、.fade-enter-active、.fade-enter-to等上述6个类,并应用在 transition 组件里的过渡元素或组件上。

示例:

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <style>
        .fade-enter {
            opacity: 0;
            transform: translateY(-20px);
        }

        .fade-enter-active {
            transition: all 0.3s ease;
        }

        .fade-enter-to {
            opacity: 1;
            transform: translateY(0);
        }

        .fade-leave {
            opacity: 1;
        }

        .fade-leave-active {
            transition: opacity 0.5s ease;
        }

        .fade-leave-to {
            opacity: 0;
        }
    </style>
</head>

<body>
    <div id="app">
        <button @click="showElement=!showElement">显示/隐藏元素</button>
        <transition name="fade">
            <div v-if="showElement">
                你好
            </div>
        </transition>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                showElement: false
            },
        });
    </script>
</body>

</html>

自定义过渡类名

如果想结合使用 animate.css 动画库,可以在 transition 组件使用以下 props 来指定类名:

  • enter-class
  • enter-active-class
  • enter-to-class
  • leave-class
  • leave-active-class
  • leave-to-class

示例:

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
</head>

<body>
    <div id="app">
        <button @click="showElement=!showElement">显示/隐藏元素</button>
        <transition
            enter-active-class="animated fadeInLeft"
            leave-active-class="animated fadeOut">
            <div v-if="showElement">
                你好
            </div>
        </transition>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                showElement: false
            },
        });
    </script>
</body>

</html>

Animate 动画演示


指定要监听的事件类型

通过设置 transition 组件的 type 属性来指定要监听的过渡事件类型(transitionend 和 animationend)。

type 值可以是 transition 或 animation。

示例:

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <style>
        .fade-enter {
            opacity: 0;
            transform: translateY(-20px);
        }
    
        .fade-enter-active {
            transition: all 0.3s ease;
        }
    
        .fade-enter-to {
            opacity: 1;
            transform: translateY(0);
        }
    
        .fade-leave {
            opacity: 1;
        }
    
        .fade-leave-active {
            transition: opacity 0.5s ease;
        }
    
        .fade-leave-to {
            opacity: 0;
        }
    </style>
</head>

<body>
    <div id="app">
        <button @click="toggleElement">Toggle Element</button>
        <transition name="fade"
            type="transition"
            >
            <div v-if="showElement" key="element">Hello, Vue!</div>
        </transition>
        <transition name="bounce"
            enter-active-class="animated bounceIn"
            leave-active-class="animated bounceOut"
            type="animation"
            >
            <div v-if="showElement" key="element">Hello, vue!</div>
        </transition>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                showElement: false
            },
            methods: {
                toggleElement() {
                    this.showElement = !this.showElement;
                },
            },
        });
    </script>
</body>

</html>

指定过渡持续时间

默认情况下,Vue 会等待过渡所在根元素的第一个 transitionendanimationend 事件。

我们可以使用 transition 组件上的 duration prop 定制一个显示的过渡持续时间,以毫秒为单位。

html 复制代码
<transition :duration="1000">...</transition>

<transition :duration="{ enter: 500, leave: 800 }">...</transition>

transition 组件上的 JavaScript 钩子函数

共 8 个:

  • before-enter
  • enter
  • after-enter
  • enter-cancelled
  • before-leave
  • leave
  • after-leave
  • leave-cancelled

注意点:

  • 只用 JavaScript 过渡时,在 leave 和 enter 中必须使用 done 参数来告诉 Vue何时过渡已经完成。
  • 推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!--
        Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    </style>
</head>

<body>
    <div id="app">
        <button @click="toggleElement">切换元素</button>
        <transition name="custom-transition" @before-enter="beforeEnter" @enter="enter" @leave="leave">
            <div v-if="showElement" key="element">Hello, Vue!</div>
        </transition>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                showElement: false
            },
            methods: {
                toggleElement() {
                    this.showElement = !this.showElement;
                },
                beforeEnter(el) {
                    // 在进入过渡开始之前调用
                    el.style.opacity = 0;
                },
                enter(el, done) {
                    // 在进入过渡的主要阶段调用
                    Velocity(el, { opacity: 1, translateY: '0px',fontSize: '2em' }, { duration: 1000, complete: done });
                    Velocity(el, { fontSize: '1em' }, { complete: done })
                },
                leave(el, done) {
                    // 在离开过渡的主要阶段调用
                    Velocity(el, { opacity: 0, translateY: '20px' }, { duration: 1000, complete: done });
                },
            }
        });
    </script>
</body>

</html>

初始渲染的过渡

启用初始渲染的过渡:

html 复制代码
<transition
	appear
	appear-class=""
	appear-active-class=""
	appear-to-class=""
	v-on:before-appear=""  
	v-on:appear=""  
	v-on:after-appear=""  
	v-on:appear-cancelled=""
	>
</transition>

多个元素的过渡

如果想实现多个原生元素的切换过渡,可以使用条件渲染的相关指令。

如果切换的元素标签名相同,比如都是 button ,那么最好给它们设置唯一的key属性,否则可能出现过渡效果不生效或没按设置过渡的情况。

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!--
        Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    </style>
</head>

<body>
    <div id="app">
        <button @click="toggleElement">切换元素</button>
        <transition @before-enter="beforeEnter" @enter="enter" @leave="leave">
            <div v-if="showElement" key="el1">Hello, girls!</div>
            <div v-else key="el2">Hello, boys!</div>
        </transition>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                showElement: false
            },
            methods: {
                toggleElement() {
                    this.showElement = !this.showElement;
                },
                beforeEnter(el) {
                    // 在进入过渡开始之前调用
                    el.style.opacity = 0;
                },
                enter(el, done) {
                    // 在进入过渡的主要阶段调用
                    Velocity(el, { opacity: 1, translateY: '0px',fontSize: '2em' }, { duration: 1000, complete: done });
                    Velocity(el, { fontSize: '1em' }, { complete: done })
                },
                leave(el, done) {
                    // 在离开过渡的主要阶段调用
                    Velocity(el, { opacity: 0, translateY: '20px' }, { duration: 1000, complete: done });
                },
            }
        });
    </script>
</body>

</html>

也可以使用绑定动态 property 的方式来替代使用了多个 v-if 的相同标签名元素

html 复制代码
<transition>
	<div v-if="show==='el1'" key="el1">el1</div>
	<div v-if="show==='el2'" key="el2">el2</div>
</transition>

替换成

html 复制代码
<transition>
	<div :key="el">el</div>
</transition>

过渡模式

如上图中,前一个元素的过渡结束时,另一个元素的过渡开始,这有时候并不是我们想要的效果。

于是,Vue 提供了过渡模式。

html 复制代码
<transition name="fade" mode="out-in">
</transition>

mode 可以为以下值:

  • in-out:新元素先过渡进入,旧元素后过渡离开。
  • out-in:旧元素先过渡离开,新元素再过渡进入。

So cool!


多个组件的过渡

多个元素我们可以使用 key attribute,对于多个组件,我们可以使用更为简单的 is attribute,即动态组件。

html 复制代码
<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>

列表过渡

前面所讲都是针对单个节点或者多个节点中的一个。

而对于有多个节点的列表的渲染,我们使用 transition-group 组件。

transition-group 组件的特点:

  1. transition 不同,它以一个真实的元素存在,默认为 span,可以使用 tag attribute 修改。
  2. 不能使用过渡模式。
  3. 列表元素都需要 key attribute。
  4. CSS 过渡的类会自动应用在列表内部元素。

列表的进入/离开过渡

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!--
        Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    </style>
    <style>
        .list-enter-active,
        .list-leave-active {
            transition: opacity,transform 0.5s;
        }

        .list-enter,
        .list-leave-to {
            opacity: 0;
            transform: translateX(60px);
        }

        .list-item {
            list-style:decimal-leading-zero;
            margin: 5px;
            padding: 5px;
            background-color: #f0f0f0;
            border: 1px solid #ddd;
            border-radius: 5px;
            width: fit-content;
        }
    </style>
</head>

<body>
    <div id="app">
        <button @click="addItem">Add Item</button>
        <button @click="removeItem">Remove Item</button>
        <transition-group name="list" tag="ul">
            <li v-for="item in items" :key="item.id" class="list-item">
                {{item.text}}
            </li>
        </transition-group>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                items: [],
                nextItemId: 0
            },
            methods: {
                addItem() {
                    let randomIndex=Math.floor(Math.random()*this.items.length);
                    this.items.splice(randomIndex,0, {
                        id: this.nextItemId++,
                        text: `Item ${this.nextItemId}`
                    });
                },
                removeItem() {
                    let randomIndex = Math.floor(Math.random()*this.items.length);
                    this.items.splice(randomIndex, 1);
                }
            },
        });
    </script>
</body>

</html>

列表的排序过渡

不仅可以给进入/离开添加过渡,当列表的元素位置发生变化时,也可以产生过渡效果,通过 move-class attribute自定义类名来实现,也可以使用 name 作为前缀。

例如当 name="list" 时:

css 复制代码
.list-move {
  transition: transform 1s;
}

这个可以解决列表过渡不够平滑的问题。

如果想给所有的变动都添加过渡动画,那么可以按以下方法做:

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!--
        Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
    <style>
        .list-enter,
        .list-leave-to {
            opacity: 0;
            transform: translateX(30px);
        }

        .list-leave-active{
            position: absolute;
        }

        .list-item {
            transition: all 1s;
            list-style: none;
            margin: 5px;
            padding: 5px;
            background-color: #f0f0f0;
            border: 1px solid #ddd;
            border-radius: 5px;
            width: fit-content;
        }
    </style>
</head>

<body>
    <div id="app">
        <button @click="addItem">Add Item</button>
        <button @click="removeItem">Remove Item</button>
        <button @click="shuffleItems">Shuffle Items</button>
        <transition-group name="list" tag="ul">
            <li v-for="item in items" :key="item.id" class="list-item">
                {{item.text}}
            </li>
        </transition-group>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                items: [],
                nextItemId: 0
            },
            methods: {
                addItem() {
                    let randomIndex = Math.floor(Math.random() * this.items.length);
                    this.items.splice(randomIndex, 0, {
                        id: this.nextItemId++,
                        text: `Item ${this.nextItemId}`
                    });
                },
                removeItem() {
                    let randomIndex = Math.floor(Math.random() * this.items.length);
                    this.items.splice(randomIndex, 1);
                },
                shuffleItems() {
                    this.items = _.shuffle(this.items);
                }
            },
        });
    </script>
</body>

</html>

这被叫做 FLIP 过渡,使用这种过渡方式的元素不能设置为 display:inline


列表的交错过渡

通过 HTML 的 data 属性与 JavaScript 进行通信来实现交错排序。

html 复制代码
<li  
v-for="(item, index) in computedList"  
v-bind:key="item.msg"  
v-bind:data-index="index"  
>{{ item.msg }}</li>
javascript 复制代码
enter: function (el, done) {
  var delay = el.dataset.index * 150; // 通过 data-index 获取元素的索引,然后计算延迟时间
  setTimeout(function () {
    Velocity(
      el,
      { opacity: 1, height: '1.6em' }, // 使用 Velocity.js 库来应用 CSS 属性的动画效果
      { complete: done } // 动画完成后调用 done 回调函数来通知 Vue.js 过渡完成
    );
  }, delay);
},

每个元素都有不同的延迟,以实现交错的过渡效果。


可复用的过渡

使用 template :

javascript 复制代码
Vue.component('my-special-transition', {
  template: '\
    <transition\
      name="reusable-transition"\
      mode="out-in"\
      v-on:before-enter="beforeEnter"\
      v-on:after-enter="afterEnter"\
    >\
      <slot></slot>\
    </transition>\
  ',
  methods: {
    beforeEnter: function (el) {
      // ...
    },
    afterEnter: function (el) {
      // ...
    }
  }
})

使用 函数式组件 :

javascript 复制代码
Vue.component('reusable-transition', {
  functional: true,
  render: function (createElement, context) {
    var data = {
      props: {
        name: 'reusable-transition',
        mode: 'out-in'
      },
      on: {
        beforeEnter: function (el) {
          // ...
        },
        afterEnter: function (el) {
          // ...
        }
      }
    }
    return createElement('transition', data, context.children)
  }
})

使用 单文件组件:

vue 复制代码
<template>
  <transition name="fade">
    <slot></slot>
  </transition>
</template>

<script>
export default {
  name: 'ReusableTransition',
};
</script>

<style>
/* 在这里定义过渡的 CSS 类名和样式 */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active 在版本 2.1.8 中可用 */ {
  opacity: 0;
}
</style>

总之,可复用过渡就是以 transitiontransition-group 作为根元素。


动态过渡

我们可以根据组件的状态或属性动态地应用不同的过渡效果。

通常涉及使用过渡的不同名称、条件语句和组件的动态属性。

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue 2 过渡 Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <!--
        Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    <style>
        .default-enter,
        .default-leave-to {
            opacity: 0;
            transform: translateX(30px);
        }

        .slide-enter,
        .slide-leave-to {
            transform: translateY(30px);
            opacity: 0;
        }

        .default-leave-active,
        .slide-leave-active{
            position: absolute;
        }

        .list-item {
            transition: all 1s;
        }
    </style>
</head>

<body>
    <div id="app">
        <input v-model="newTodo" @keyup.enter="addTodo" placeholder="添加新任务">
        <button @click="toggleTransition">切换过渡方式</button>
        <transition-group :name="dynamicTransitionName" tag="ul">
            <li v-for="todo in todos" :key="todo.id" class="list-item">
                {{todo.text}}
                <button @click="removeTodo(index)">删除</button>
            </li>
        </transition-group>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                dynamicTransitionName: 'default',
                newTodo: '',
                todos: [],
                nextID: 0
            },
            methods: {
                addTodo() {
                    if (this.newTodo.trim() !== '') {
                        this.todos.push({id:this.nextID++, text: this.newTodo });
                        this.newTodo = '';
                    }
                },
                removeTodo(index) {
                    this.todos.splice(index, 1);
                },
                toggleTransition(){
                    this.dynamicTransitionName=this.dynamicTransitionName==='default'?'slide':'default';
                }
            },
        });
    </script>
</body>

</html>
相关推荐
恋猫de小郭15 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端