Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 进阶 ------- 过渡效果 #9

Open
Corbusier opened this issue Jun 7, 2017 · 0 comments
Open

Vue 进阶 ------- 过渡效果 #9

Corbusier opened this issue Jun 7, 2017 · 0 comments
Labels

Comments

@Corbusier
Copy link
Owner

Corbusier commented Jun 7, 2017

概述

Vue 在插入、更新或移除DOM时,提供多种不同方式的应用过渡效果。
包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

单元素/组建的过渡

Vue提供了transition的封装组件,在下列情形中,可以给任何元素或组件添加移入/移出过渡

  • 条件渲染(v-if)
  • 条件展示(v-show)
  • 动态组件
  • 组件根节点
    .fade-enter-active,.fade-leave-active{
        transition:opacity .5s;
    }
    .fade-enter,.fade-leave-active{
        opacity:0;
    }
    <div id="demo">
        <button v-on:click="show = !show">
            Toggle
        </button>
        <transition name="fade">
            <p v-if="show">hello</p>
        </transition>
    </div>
    
    new Vue({
        el:"#demo",
        data:{
            show:true
        }
    })

以上的示例可以达到淡入淡出的效果,利用组件插入或移出包含在组件transition中的元素时,Vue会做出以下的处理:

  1. 检测目标元素是否应用了CSS过渡或动画,如果有,在恰当的时机添加/移除CSS类名;
  2. 如果过渡组件提供了JS钩子函数,这些钩子函数将在恰当的时机被调用;
  3. 如果没有找到JS钩子并且也没有检测到CSS过渡/动画,DOM 操作(插入/删除)在下一帧中立即执行。(注意:此指浏览器逐帧动画机制)

过渡的CSS类名

四个(CSS)类名在enter/leave的过渡中切换

  1. v-enter:定义为进入过渡的开始状态。元素被插入时生效,在下一个帧移除。
  2. v-enter-active:定义进入过渡的结束状态。在元素被插入时生效,在transition/animation完成之后移除。
  3. v-leave:定义为离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。
  4. v-leave-active:定义为定义离开过渡的结束状态。在离开过渡被触发时生效,在 transition/animation完成之后移除。

v-enter-active 和 v-leave-active 可以控制 进入/离开 过渡的不同阶段。

CSS过渡

常用的过渡是CSS过渡:

    .slide-fade-enter-active {
        transition: all .3s ease;
    }
    .slide-fade-leave-active {
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }
    .slide-fade-enter, .slide-fade-leave-active {
        transform: translateX(10px);
        opacity: 0;
    }
    <div id="example-1">
        <button @click="show = !show">
            Toggle render
        </button>
        <transition name="slide-fade">
            <p v-if="show">hello</p>
        </transition>
    </div>
    
    new Vue({
        el: '#example-1',
        data: {
            show: true
        }
    })

CSS动画

与过渡transition的效果相同,区别在于动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发时移除。

    .bounce-enter-active{
        animation: bounce-in .5s;
    }
    .bounce-leave-active{
        animation: bounce-out .5s;
    }
    @keyframes bounce-in{
        0% {
            transform: scale(0);
        }
        50% {
            transform: scale(1.5);
        }
        100% {
            transform: scale(1);
        }
    }
    @keyframes bounce-out{
        0% {
            transform: scale(1);
        }
        50% {
            transform: scale(1.5);
        }
        100% {
            transform: scale(0);
        }
    }

自定义过渡类名

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

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

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

    <link href="https://unpkg.com/animate.css@3.5.1/animate.min.css" rel="stylesheet" type="text/css">
    <div id="example-3">
        <button @click="show = !show">
            Toggle render
        </button>
        <transition
            name="custom-classes-transition"
            enter-active-class="animated tada"
            leave-active-class="animated bounceOutRight"
        >
            <p v-if="show">Hello</p>
        </transition>
    </div>
    
    new Vue({
        el:"#example-3",
        data:{
            show:true
        }
    })

同时使用Transition 和 Animations

Vue为了检测过渡的完成,必须设置相应的事件监听器。它可以是transitionendanimationend,这完全取决于给元素应用的CSS规则。如果只使用其中一种,Vue能自动识别类型并设置监听。

有些情况下需要给同一个元素同时设置两种过渡效果,比如animation触发并完成后,transition效果还没有结束,这时需要使用type特性并设置animationtransition来声明需要Vue监听的类型。

JavaScript钩子

一个元素出现的过渡包括四个节点:before-enter,enter,after-enter,enter-cancelled,分别代表开始出现前,出现中,出现后,离开。可以在属性中声明JavaScript钩子,当处于各个节点时,触发这些钩子函数。

    <div id="app">
        <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'
        >
        </transition>
    </div>
    
    new Vue({
        el: '#app',
        data: {
            view: 'v-a'
        },
        methods: {
            // 过渡进入 设置过渡进入之前的组件状态
            beforeEnter: function(el){

            },
            // 设置过渡进入完成时的组件状态
            enter: function(el,done){
                // 
                done()
            },
            // 设置过渡进入完成之后的组件状态
            afterEnter: function(el){
                // ....
            },
            enterCancelled: function(el){
                // ...
            },
            // 过渡离开 设置过渡离开之前的组件状态
            beforeLeave: function(el){
                // ...
            },
            // 设置过渡离开完成时的组件状态
            leave: function(el, done){
                // ...
                done()
            },
            // 设置过渡离开完成之后的组件状态
            afterLeave: function(el){
                // ......
            },
            leaveCancelled: function(el){
                // ....
            }
        }
    })

这些钩子函数可以结合CSStransitions/animations也可以单独使用。

当只使用 JavaScript 过渡时,在 enterleave中,回调函数done是必须的,否则它们会同步执行,过度会立即完成。
推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

关于使用 Velocity.js 的简单例子:

    <div id="example-4">
        <button @click="show = !show">
            Toggle
        </button>
        <transition
            v-on:before-enter="beforeEnter"
            v-on:enter="enter"
            v-on:leave="leave"
            v-bind:css="false"
        >               
            <p v-if="show">
                Demo
            </p>
        </transition>
    </div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    new Vue({
        el:"#example-4",
        data:{
            show:false
        },
        methods:{
            beforeEnter:function(el){
                el.style.opacity = 0;
                el.style.transformOrigin = "left";
            },
            enter:function(el,done){
                Velocity(el,
                    { opacity: 1, 
                      fontSize: '1.4em'}, 
                    { duration: 300 }
                )
                Velocity(el,
                    { fontSize: '1em' },
                    { complete: done }
                )
            },
            leave:function(el,done){
                Velocity(el, 
                    { translateX: '15px', 
                      rotateZ: '50deg' }, 
                    { duration: 600 }
                )
                Velocity(el, 
                    { rotateZ: '100deg' }, 
                    { loop: 2 }
                )
                Velocity(el,
                    { rotateZ: '45deg',
                      translateY: '30px',
                      translateX: '30px',
                      opacity: 0
                }, 
                    { complete: done }
                )
            }
        }
    })

初始渲染的过渡

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

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

这里默认和进入和离开过渡一样,同样也可以自定义 CSS 类名。

    <transition
        appear
        appear-class="custom-appear-class"
        appear-active-class="custom-appear-active-class"
    >
        /*<!-- ... -->*/
    </transition>

自定义 JavaScript 钩子:

    <transition
        appear
        v-on:before-appear="customBeforeAppearHook"
        v-on:appear="customAppearHook"
        v-on:after-appear="customAfterAppearHook"
    >
        /*<!-- ... -->*/
    </transition>

多个元素的过渡

关于多个组件的过渡,对于原生标签可以使用v-if/v-else,最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:

    <transition>
        <table v-if="items.length > 0">
        /*<!-- ... -->*/
        </table>
        <p v-else>Sorry, no items found.</p>
    </transition>

当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 组件中的多个元素设置 key 是一个更好的实践。

示例:

    <transition>
        <button v-bind:key="isEditing">
            {{ isEditing ? 'Save' : 'Edit' }}
        </button>
    </transition>

使用多个v-if的多个元素的过渡可以重写为绑定了动态属性的单个元素过渡。例如:

    <transition>
        <button v-if="docState === 'saved'" key="saved">
            Edit
        </button>
        <button v-if="docState === 'edited'" key="edited">
            Save
        </button>
        <button v-if="docState === 'editing'" key="editing">
            Cancel
        </button>
    </transition>

可以重写为:

    <transition>
        <button v-bind:key="docState">
            {{ buttonMessage }}
        </button>
    </transition>
    
    computed: {
        buttonMessage: function (){
            switch(docState){
                case 'saved': return 'Edit'
                case 'edited': return 'Save'
                case 'editing': return 'Cancel'
            }
        }
    }

过渡模式

!!原文档中该部分并没有贴出代码样例佐以说明。详细代码请点击如下的地址,并有点击预览效果。

在 "on"按钮和 "off" 按钮的过渡中,两个按钮都被重绘了,一个离开过渡的时候另一个开始进入过渡。
这是<transition>的默认行为 - 进入和离开同时发生。

代码地址

在线演示

同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式

  • in-out: 新元素先进行过渡,完成之后当前元素过渡离开。
  • out-in: 当前元素先进行过渡,完成之后新元素过渡进入。

用 out-in 重写之前的开关按钮过渡:

    <transition name="fade" mode="out-in">
        /*<!-- ... the buttons ... -->*/
    </transition>

将之前滑动淡出的例子结合,即in-out模式,可以得到新的效果。

多个组件的过渡

多个组件的过渡比较简单,不需要使用key特性。只需要使用动态组件

该例与原文档中的不同,校验勘误之后再发!

    .component-fade-enter-active, .component-fade-leave-active {
        transition: opacity .3s ease;
    }
    .component-fade-enter, .component-fade-leave-active {
        opacity: 0;
    }    
    <div id="transition-components-demo">
        <transition name="component-fade" mode="out-in">
            <component v-bind:is="view"></component>
        </transition>
    </div>
    
    new Vue({
        el: '#transition-components-demo',
        data: {
            view: 'v-a'
        },
        components: {
            'v-a': {
                template: '<div>Component A</div>'
            },
            'v-b': {
                template: '<div>Component B</div>'
            }
        }
    })

列表过渡

关于过渡提到了:

  1. 单个节点
  2. 同一时间渲染多个节点中的一个

如果需要同时渲染整个列表,比如v-for?在这种场景中,需要使用<transition-group>组件。关于组件的几个特点:

  • 不同于 <transition>,会以真实元素呈现:默认为一个<span>
  • 内部元素总是需要提供唯一的key属性值

列表的进入和离开过渡

进入和离开的过渡使用之前一样的CSS类名,举例如下:

    .list-item {
        display: inline-block;
        margin-right: 10px;
    }
    .list-enter-active, .list-leave-active {
        transition: all 1s;
    }
    .list-enter, .list-leave-active {
        opacity: 0;
        transform: translateY(30px);
    }
    <div id="list-demo" class="demo">
        <button v-on:click="add">Add</button>
        <button v-on:click="remove">Remove</button>
        <transition-group name="list" tag="p">
            <span v-for="item in items" v-bind:key="item" class="list-item">
                {{ item }}
            </span>
        </transition-group>
    </div>
    
    new Vue({
        el: '#list-demo',
        data: {
            items: [1,2,3,4,5,6,7,8,9],
            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)
            },
        }
    })

在该例中,当移除或添加元素时,周围的元素会立即移动到新布局的位置,而不是平滑的过渡。

列表位移过渡

<transition-group>组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name 属性来自定义前缀,也可以通过 move-class 属性手动设置。

v-move 对于设置过渡的切换时间和过度曲线很有用,在视觉上过渡会显得更加自然流畅,而不是生硬的切换。举例说明:

    .flip-list-move {
        transition: transform 1s;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
    <div id="flip-list-demo" class="demo">
        <button v-on:click="shuffle">Shuffle</button>
        <transition-group name="flip-list" tag="ul">
            <li v-for="item in items" v-bind:key="item">
                {{ item }}
            </li>
        </transition-group>
    </div>
    
    new Vue({
        el: '#flip-list-demo',
        data: {
            items: [1,2,3,4,5,6,7,8,9]
        },
        methods: {
            shuffle: function () {
                this.items = _.shuffle(this.items)
            }
        }
    })

Vue 使用了一个叫 FLIP 简单的动画队列
使用 transforms 将元素从之前的位置平滑过渡新的位置。通过这个动画队列,修改之前的例子:

完整代码

在线演示

使用v-move动画队列的元素不能设置为display: inline。作为替代方案,可以设置为 display: inline-block 或者放置于 flex 中。

使用该动画不仅可以实现单列过渡,多维网格的过渡一样可以实现,下面列出的是数独的简单版本,网格能自然平滑的过渡:

完整代码

在线演示

列表的渐进过渡

通过 data 属性与 JavaScript 通信,就可以实现列表的渐进过渡:

完整代码

在线演示

可复用的过渡

过渡可以通过Vue的组建系统实现复用。要创建一个可复用的过渡组件,需要做的就是将<transition>或者<transition-group>作为根组件,然后将任何子组件放置在其中。

使用template的简单例子:

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

函数组件更适合完成这个任务:

    Vue.component('my-special-transition', {
        functional: true,
        render: function (createElement, context) {
            var data = {
                props: {
                    name: 'very-special-transition',
                    mode: 'out-in'
                },
                on: {
                    beforeEnter: function (el) {
                    // ...
                    },
                    afterEnter: function (el) {
                    // ...
                    }
                }
            }
            return createElement('transition', data, context.children)
        }
    })

动态过渡

在Vue中即使是过渡也是数据驱动的。动态过渡最基本的例子是通过name特性来绑定动态值。

    <transition v-bind:name="transitionName">
        //<!-- ... -->
    </transition>

当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画 在不同过渡间切换会非常有用。

所有的过渡特性都是动态绑定的。通过事件的钩子函数方法,可以获取到相应的上下文数据。因此,可以根据组件的状态通过JavaScript过渡设置不同的过渡效果。

完整代码

在线演示

总结

!!!过渡效果这部分的内容较多,且属于实际运用的内容,项目需要时再根据需求运用。
附上轮播图代码如下:

完整代码

在线演示

@Corbusier Corbusier added the Vue label Jun 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant