Vue过渡 and 动画

深入LearningVue.js 过渡 and 动画system, for application添加流畅 视觉效果

1. 过渡 and 动画overview

Vue.jsproviding了一套完整 过渡 and 动画system, 允许我们 in 元素进入 or 离开DOM时application过渡效果, 以及 in 元素status变化时application动画效果.

1.1 过渡 and 动画 区别

  • 过渡: 元素 from 一种status平滑过渡 to 另一种status, 通常涉及 to CSSproperty 变化, such asopacity, transformetc..
  • 动画: 元素执行一系列预定义 动画效果, 通常usingCSS动画 or JavaScript动画implementation.

1.2 Vue过渡 application场景

  • 元素 显示/隐藏 (v-if/v-show)
  • component 挂载/卸载
  • routing切换
  • list 添加/delete/sort

2. 单元素/component 过渡

Vueproviding了<transition>component来implementation单元素 or 单component 过渡效果.

2.1 basicusing

<transition name="fade">
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>
<button @click="show = !show">切换显示/隐藏</button>

当元素进入 or 离开DOM时, Vue会自动添加/移除以 under class名:

  • v-enter: 进入过渡 开始status
  • v-enter-active: 进入过渡 激活status
  • v-enter-to: 进入过渡 结束status
  • v-leave: 离开过渡 开始status
  • v-leave-active: 离开过渡 激活status
  • v-leave-to: 离开过渡 结束status

such as果using了nameproperty, such asname="fade", 则class名会变 for :

  • fade-enter
  • fade-enter-active
  • fade-enter-to
  • fade-leave
  • fade-leave-active
  • fade-leave-to

2.2 过渡class名 持续时间

Vue会automatically detect过渡效果 持续时间. such as果需要手动指定, 可以usingdurationproperty:

<transition name="fade" :duration="1000">
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>

<!-- 分别指定进入 and 离开 持续时间 -->
<transition name="fade" :duration="{ enter: 500, leave: 800 }">
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>

2.3 自定义过渡class名

可以using自定义 过渡class名, 这些class名priority high 于默认class名:

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

这使得我们可以方便地using第三方动画library, such asAnimate.css:

<!-- 引入Animate.css -->
<link href="https://cdn.jsdelivr.net/npm/animate.css@4.1.1/animate.min.css" rel="stylesheet">

<!-- usingAnimate.css 动画效果 -->
<transition 
    enter-active-class="animate__animated animate__bounceIn"
    leave-active-class="animate__animated animate__bounceOut"
>
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>

2.4 JavaScripthookfunction

可以usingJavaScripthookfunction来implementation更 complex 过渡效果:

<transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:after-enter="afterEnter"
    v-on:enter-cancelled="enterCancelled"
    v-on:before-leave="beforeLeave"
    v-on:leave="leave"
    v-on:after-leave="afterLeave"
    v-on:leave-cancelled="leaveCancelled"
>
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>
methods: {
    beforeEnter: function(el) {
        // 进入过渡 before  callback
        el.style.opacity = 0;
        el.style.transform = 'translateX(100px)';
    },
    enter: function(el, done) {
        // 进入过渡 callback, done is 必须调用 
        el.offsetWidth; // 触发重排
        el.style.opacity = 1;
        el.style.transform = 'translateX(0)';
        done(); // 必须调用donefunction, 否则after-enter不会触发
    },
    afterEnter: function(el) {
        // 进入过渡completion after  callback
        console.log('Enter transition completed');
    },
    enterCancelled: function(el) {
        // 进入过渡被取消时 callback
        console.log('Enter transition cancelled');
    },
    beforeLeave: function(el) {
        // 离开过渡 before  callback
        el.style.opacity = 1;
        el.style.transform = 'translateX(0)';
    },
    leave: function(el, done) {
        // 离开过渡 callback, done is 必须调用 
        el.offsetWidth; // 触发重排
        el.style.opacity = 0;
        el.style.transform = 'translateX(-100px)';
        done(); // 必须调用donefunction, 否则after-leave不会触发
    },
    afterLeave: function(el) {
        // 离开过渡completion after  callback
        console.log('Leave transition completed');
    },
    leaveCancelled: function(el) {
        // 离开过渡被取消时 callback (仅用于v-show) 
        console.log('Leave transition cancelled');
    }
}

提示

usingJavaScripthookfunction时, 需要注意 in enter and leavehookin调用donefunction, 否则Vue无法知道过渡when结束.

3. list过渡

Vueproviding了<transition-group>component来implementationlist 过渡效果, includinglist项 添加, delete and sort.

3.1 basicusing

<transition-group name="list" tag="ul">
    <li 
        v-for="item in items" 
        :key="item.id" 
        class="list-item"
    >
        {{ item.name }}
        <button @click="removeItem(item.id)">delete</button>
    </li>
</transition-group>
<button @click="addItem">添加项</button>
data: function() {
    return {
        items: [
            { id: 1, name: '项 1' },
            { id: 2, name: '项 2' },
            { id: 3, name: '项 3' }
        ],
        nextId: 4
    };
},
methods: {
    addItem: function() {
        this.items.push({ id: this.nextId++, name: '项 ' + this.nextId });
    },
    removeItem: function(id) {
        this.items = this.items.filter(item => item.id !== id);
    }
}

3.2 listsort过渡

<transition-group>component还supportlist项 sort过渡, throughv-moveclass名implementation:

<!-- listsort过渡 -->
<transition-group name="list" tag="ul">
    <li 
        v-for="item in sortedItems" 
        :key="item.id" 
        class="list-item"
    >
        {{ item.name }}
        <input v-model.number="item.order" type="number">
    </li>
</transition-group>
<!-- CSS样式 -->
.list-move, .list-enter-active, .list-leave-active {
    transition: all 0.5s ease;
}

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

/* 确保delete时 过渡效果 */
.list-leave-active {
    position: absolute;
}

warning

usinglist过渡时, 必须 for 每个list项providing唯一 key值, 否则过渡效果可能无法正常工作.

3.3 list交错过渡

可以usingJavaScripthookfunctionimplementationlist项 交错过渡效果:

<transition-group
    name="stagger"
    tag="ul"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
>
    <li 
        v-for="(item, index) in items" 
        :key="item.id" 
        :data-index="index"
        class="list-item"
    >
        {{ item.name }}
    </li>
</transition-group>
methods: {
    beforeEnter: function(el) {
        el.style.opacity = 0;
        el.style.height = 0;
    },
    enter: function(el, done) {
        const delay = el.dataset.index * 150;
        setTimeout(() => {
            el.style.opacity = 1;
            el.style.height = el.scrollHeight + 'px';
            done();
        }, delay);
    },
    leave: function(el, done) {
        const delay = el.dataset.index * 150;
        setTimeout(() => {
            el.style.opacity = 0;
            el.style.height = 0;
            done();
        }, delay);
    }
}

4. status过渡

除了进入/离开DOM 过渡, Vue还support元素status变化 过渡, such as颜色, 尺寸etc. 变化.

4.1 动态过渡

可以using动态 nameproperty来切换不同 过渡效果:

<transition :name="transitionName">
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>
<button @click="show = !show">切换显示/隐藏</button>
<select v-model="transitionName">
    <option value="fade">淡入淡出</option>
    <option value="slide">滑动</option>
    <option value="scale">缩放</option>
    <option value="bounce">弹跳</option>
</select>

4.2 条件过渡

可以根据条件来决定 is 否application过渡效果:

<transition :name="show ? 'fade' : 'slide'">
    <div v-if="show" class="demo-box">Hello Vue!</div>
</transition>

4.3 动态component过渡

可以using<transition>componentpackage裹<component>component, implementation动态component 过渡效果:

<transition name="component-fade" mode="out-in">
    <component :is="currentComponent"></component>
</transition>
<button @click="toggleComponent">切换component</button>
data: function() {
    return {
        currentComponent: 'ComponentA'
    };
},
methods: {
    toggleComponent: function() {
        this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    }
},
components: {
    'ComponentA': {
        template: '<div class="demo-box">component A</div>'
    },
    'ComponentB': {
        template: '<div class="demo-box" style="background-color: #34A853;">component B</div>'
    }
}

4.4 过渡模式

当using<transition>componentpackage裹两个 or many 个元素时, 可以usingmodeproperty来控制过渡 顺序:

  • in-out: new 元素先进入, old 元素再离开
  • out-in: old 元素先离开, new 元素再进入
<transition name="fade" mode="out-in">
    <component :is="currentComponent" key="currentComponent"></component>
</transition>

5. 过渡 performanceoptimization

5.1 usingtransformproperty

in 过渡效果in, 优先usingtransformproperty (such astranslate, scale, rotateetc.) , 而不 is top, leftetc.property, 因 for transformproperty可以触发GPU加速, improving动画performance.

5.2 usingwill-changeproperty

for 于频繁发生 动画, 可以usingwill-changeproperty来告诉浏览器提 before 准备, improving动画performance:

.demo-box {
    will-change: transform, opacity;
}

5.3 避免过度using动画

虽然动画可以增强user体验, 但过度using动画会导致页面performance under 降, 影响user体验. 因此, 应该只 in 必要 地方using动画, 并保持动画效果简洁流畅.

5.4 usingCSS动画而非JavaScript动画

in 可能 circumstances under , 优先usingCSS动画, 而不 is JavaScript动画, 因 for CSS动画可以更 good 地利用浏览器 硬件加速, performance更 good .

6. best practices

6.1 选择合适 过渡效果

  • 淡入淡出效果适合 big many 数场景, 简洁 big 方
  • 滑动效果适合侧edge栏, 菜单etc.
  • 缩放效果适合弹窗, 模态框etc.
  • 弹跳效果适合强调某个元素

6.2 保持过渡效果一致

in 整个applicationin, 应该保持过渡效果 consistency, 避免using过 many 不同 过渡效果, 导致视觉混乱.

6.3 合理设置过渡持续时间

过渡持续时间应该根据具体circumstancesfor调整, 一般来说:

  • 淡入淡出效果: 300-500ms
  • 滑动效果: 300-600ms
  • 缩放效果: 300-500ms
  • complex 动画: 500-1000ms

6.4 考虑无障碍访问

in using动画时, 应该考虑 to 无障碍访问, for userproviding关闭动画 选项, 因 for 某些user可能 for 动画敏感.

练习 1: basic过渡效果

  1. creation一个按钮, 点击时切换元素 显示/隐藏.
  2. for 元素添加淡入淡出过渡效果.
  3. for 元素添加滑动过渡效果.
  4. for 元素添加缩放过渡效果.
  5. using动态nameproperty切换不同 过渡效果.

练习 2: list过渡

  1. creation一个list, support添加, delete and sortfunctions.
  2. for list项 添加 and delete添加过渡效果.
  3. for list项 sort添加过渡效果.
  4. implementationlist项 交错过渡效果.

练习 3: component过渡

  1. creation两个component, ComponentA and ComponentB.
  2. implementationcomponent之间 切换functions.
  3. for component切换添加过渡效果.
  4. 尝试using不同 过渡模式 (in-out and out-in) .
  5. using第三方动画library (such asAnimate.css) for component切换添加更 complex 动画效果.