过渡(1):元素/组件过渡和动画

自定义过渡的类名

另外一种状况和Vue过渡执行过程见初始渲染javascript

new Vue({
    el: '#app-2',
    data: {
        taxiCalled: false
    }
})
<style>
.slideInRight {
    transform: translateX(300px);
}
.go {
    transition: all 2s ease-out;
}
</style>
<div id="app-2">
    <button @click="taxiCalled = true">
        Call a cab
    </button>
    <transition enter-class="slideInRight" enter-active-class="go">
        <p v-if="taxiCalled">?</p>
    </transition>
</div>

clipboard.png

这个过程到底发生了什么?css

当点击按钮时,texiCalled被设置成true,而且taxi插入页面。实际上在完成这些动做以前,Vue读取了指定的类enter-class(这里为slideInRight)并把它应用于taxi的外包元素p,而后指定类enter-active-class的状况是同样的。vue

enter-class在第一帧以后就被移除了,类enter-active-class也在动画结束时被移除。java

这种建立动画的方式成为FLIPgit

  • First(F):动画第一帧的状态;这里既是taxi在屏幕的右边开始。
  • Last(L): 动画最后帧的状态;这里taxi在屏幕的最左边做为结束。
  • Invert(I): 使用transformopacity在第一帧和最后帧两个状态之间扭转,这里使用translateX(300px)使taxi在两个状态间产生300px的位移。
  • Play(p): 为咱们设置的属性建立过渡,这里在2s内使taxi从右到左产生了300px的位移。

在钩子中使用Velocity

new Vue({
    el: '#app-3',
    data: {
        taxiCalled: false
    },
    methods: {
        enter(el, done) {
            Velocity(el,
                { opacity: [1, 0], translateX: ["0px", "200px"] },
                { duration: 2000, easing: "ease-out", complete: done })
        },
        leave(el, done) {
            Velocity(el,
                { opacity: [0, 1], 'font-size': ['0.1em', '1em'] },
                { duration: 200, complete: done })
        }
    }
})
<div id="app-3">
    <button @click="taxiCalled = true">
        Call a cab
    </button>
    <button @click="taxiCalled = false">
        Cancel
    </button>
    <transition @enter="enter" @leave="leave" :css="false">
        <p v-if="taxiCalled">?</p>
    </transition>
</div>

clipboard.png

抓取enterleaveappear三种状况的时间点,每种状况上都定义了四种事件,总计12种事件(见API)。在这12种事件绑定钩子函数,这些函数能够配合CSS模式使用,也能够单独使用。这里咱们在钩子函数中使用velocity脚本动画引擎,单独完成动画配置。github

在这些钩子中不必定非要使用 velocity,可使用任何库。

上面代码中:css="false"是告诉Vue,咱们关闭CSS的处理,这里能够节省点CPU时间,将跳过全部与CSS动画相关代码,单纯的使用Javascript动画。面试

咱们这里分别在@enter@leave上绑定了钩子函数,他们将在taxi被插入和离开时执行。函数的第一个参数为外包标签(这里是<p>),第二个参数done必须写,尽管你可能不去用它。这是由于用Javascript代替CSS,Vue没法识别动画何时完成,这里Vue会认为这个离开的动画在@leave事件以前就完成了,因此执行leave没有动画渲染。segmentfault

关于velocity,这种类型的API,咱们称为forcefeeding,咱们只要往它的实例中填写数据,无论他内部如何运行。(具体查看velocityapi

初始渲染

<transition>使用appear特性,在组件第一次被插入时执行相关的转换。
使用它会给人带来一种页面很快的加载大量元素的感受(错觉)。数组

这里还使用了<transition>name特性,可使用自定义的类名('自定义过渡的类名'的类似写法),
name-enter-activename-enter等。
而使用这种自定义类名是一种好习惯。

<div id="app-4">
    <transition appear name="flip">
        <!-- 指定宽高由于是引用的图片,不知道尺寸 -->
        <img src="https://b-ssl.duitang.com/uploads/item/201602/20/20160220213530_Z4reH.thumb.700_0.jpeg" style="width:300px;height:300px">
    </transition>
    <p>在组件第一次插入文档时执行相关的过渡</p>
</div>
<script>
    new Vue({
        el: '#app-4'
    })
</script>
img {
    float: left;
    padding: 5px
}
.flip-enter-active {
    transition: all 5s cubic-bezier(0.55, 0.085, 0.68, 0.53);
}
.flip-enter {
    transform: scaleY(0) translateZ(0);
    opacity: 0;
}

clipboard.png

以上整个过程以下:
Vue发现appear特性,开始查找<transition>标签中的JavaScript钩子或指定的CSS类名。以后若是有一个name被指定,就根据这个name查到过渡的入口:mySpecifiedName-entermySpecifiedName-enter-active等。
若是以上过程失败,就会找默认的v-enterv-enter-active等。

两个元素之间的过渡

new Vue({
    el:'#app-5',
    data:{
        kisses: 0
    }
})
#app-5 button span {
    color: rgb(255, 0, 140);
}
#app-5 p {
    margin: 0;
    position: absolute;
    font-size: 3em;
}
.fade-enter-active {
    transition: opacity 5s
}
.fade-leave-active {
    transition: opacity 5s;
    opacity: 0
}
.fade-enter {
    opacity: 0
}
<div id="app-5">
    <button @click="kisses++">
        <span>?</span>Kiss!</button>
    <transition name="fade">
        <p v-if="kisses < 3">? frog</p>
        <p v-if="kisses >= 3">? princess</p>
    </transition>
</div>

clipboard.png

结果发现切换后没任何过渡效果,缘由是什么呢?
原来是,Vue会启动自身的优化系统,发现两个元素如出一辙,就是内容不一样,所以当切换时,只作了内容的替换,标签<p></p>部分并无被替换,所以没有过渡的效果。
咱们能够为须要过渡的元素增长key属性,让Vue识别青蛙与公主两个不一样的元素。以下:

<transition name="fade">
    <p v-if="kisses < 1" key="frog">? frog</p>
    <p v-if="kisses >= 1" key="princess">? princess</p>
</transition>

clipboard.png

过渡效果正常了。

最佳实践,在元素上使用 key,尤为是当元素间具备不一样的语义时。

多个元素之间的过渡和过渡模式

在有两个以上元素时,你可能这么作:

<transition name="fade">
    <p v-if="kisses < 2" key="frog">? frog</p>
    <p v-else-if="kisses >= 2 && kisses <=5" key="princess">? princess</p>
    <p v-else key="santa">? santa</p>
</transition>

更好的方法是咱们能够根据已有的数据动态的处理多元素过渡。

new Vue({
    el: '#app-6',
    data: {
        kisses: 0,
        kindOfTransformation: 'fade',
        transformationMode: 'in-out'
    },
    computed: {
        transformation() {
            if (this.kisses < 3) {
                return 'frog'
            }
            if (this.kisses >= 3 && this.kisses <= 5) {
                return 'princess'
            }
            if (this.kisses > 5) {
                this.kindOfTransformation = 'zoom'
                this.transformationMode = 'out-in'
                return 'santa'
            }
        },
        emoji() {
            switch (this.transformation) {
                case 'frog':
                    return '?'
                case 'princess':
                    return '?'
                case 'santa':
                    return '?'
            }
        }
    }
})
<div id="app-6">
    <button @click="kisses++"><span>?</span>Kiss!</button>
    <transition :name="kindOfTransformation" :mode="transformationMode">
        <p :key="transformation">{{emoji}} {{transformation}}</p>
    </transition>
</div>

以上过渡的namemode以及元素的key和内容,都将根据实例数据与计算属性进行动态的绑定。
这样作更加的灵活,而且能够为不一样的元素应用不一样的过渡(namemode的不一样)。

这里的mode有三种状况:

  • 不设置 旧元素离开过渡效果和新元素的进入过渡效果同时发生
  • in-out 新元素进入过渡先进行,完毕后旧元素的离开过渡效果再发生 以下面公主先进入,青蛙后消失
  • out-in 旧元素离开过渡先进行,完毕后新元素的进入过渡效果再发生 以下面公主先消失,圣诞老人后进入

clipboard.png

再来看一个过渡模式的例子。

new Vue({
    el: '#app-7',
    data: {
        product: 0,
        products: ['?umbrella', '?computer', '?ball', '?camera']
    }
})
#app-7 {
    margin-left:300px;
}

#app-7 p {
    position: absolute;
    margin: 0;
    font-size: 3em;
}
.slide-enter-active {
    transition: transform .5s
}
.slide-leave-active {
    transition: transform .5s;
    transform: translateX(-300px);
}
.slide-enter {
    transform: translateX(300px)
}
<div id="app-7" style="margin-bottom:100px;">
    <button @click="product++">next</button>
    <transition name="slide" >
        <p :key="products[product % 4]">{{products[product % 4]}}</p>
    </transition>
</div>

clipboard.png

这彷佛没什么问题。那么如今修改下CSS,去掉绝对定位。

#app-7 p {
    /* position: absolute; */
    margin: 0;
    font-size: 3em;
}

再来看看结果。

clipboard.png

彷佛是有点不对劲,为何会这样呢?

clipboard.png

看看过渡执行时的DOM,发现先后两个元素的过渡是同时进行,这是Vue的默认状况,即两个<p></p>同时存在,若是不使用绝对定位,那么上一个就会把下一个的位置挤掉。

这下过渡模式mode就派上用处了,咱们为过渡添加mode属性为out-in,旧先出新后进。

<transition name="slide" mode="out-in">
    <p :key="products[product % 4]">{{products[product % 4]}}</p>
</transition>

clipboard.png

列表过渡

列表过渡的状况比较复杂。一个问题一个问题看吧。

<transition>中有多个并列的元素时,咱们又没有使用v-if|else指令做用其上时,会警告咱们使用<transition-group>标签代替它。
一组列表的过渡效果,由<transition-group>包围,有几点比单元素过渡特殊的。

  • <transition-group>上设置tag属性外包多个元素,如<transition-group tag="p">
  • 在内部以v-for把元素渲染成列表形式
  • 每一个内部的元素须要使用:key="data"标记以与它的同胞们做区分
  • 过渡的状态不止于进入/离开,多了一个移动,使用name-move来定义类名(后面详细解释)。

看个普通示例。

new Vue({
    el: '#app-1',
    data: {
        items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
        nextNum: 10
    },
    methods: {
        ramdomIndex: function () {
            return Math.floor(Math.random() * this.items.length)
        },
        add: function () {
            this.items.splice(this.ramdomIndex(), 0, this.nextNum++)
        },
        remove: function () {
            this.items.splice(this.ramdomIndex(), 1)
        }
    }
})
.num-list-item {
    margin-right: 10px;
}
.num-list-enter,
.num-list-leave-to {
    opacity: 0;
    transform: translateY(30px);
}
.num-list-enter-active,
.num-list-leave-active {
    transition: all 2s;
}
<div id="app-1">
    <button @click="add">添加</button>
    <button @click="remove">移除</button>
    <transition-group name="num-list" tag="p">
        <span v-for="item in items" :key="item" class="num-list-item">
            {{item}}
        </span>
    </transition-group>
</div>

clipboard.png

很明显只有透明度的过渡,而Y轴(30px)的转换过渡没有成功,这是由于<span>为一个inline元素,没有这种转换过渡的功能,所以咱们须要把它切换成inline-block元素。

.num-list-item {
    /*把span切换成inline-block*/
    display:inline-block;
    margin-right: 10px;
}

clipboard.png

仔细观察下还能发现个问题,其余元素由于进入/离开的那个元素,会发生位置的变化,其余元素这种移动变换没有过渡的效果,这不是咱们想要的。

如今就轮到使用name-move为其余元素在此时添加移动过渡效果,很简单的作个修改:

.num-list-move,/* 为其余受到影响的元素添加移动过渡效果*/
.num-list-enter-active,
.num-list-leave-active {
    transition: all 2s;
}

clipboard.png

观察文档结构,当进入/离开发生时,会给受影响的元素添加移动过渡类名num-list-move

clipboard.png

进入过渡时没问题了,其余元素平滑的向右位移,可是离开过渡时其余受影响元素的移动仍是没有过渡效果,这是由于定位问题,离开的元素要有2秒消失,默认的static定位要在2秒后离开元素才会腾出空位让给后面的元素,而此时此刻,移动过渡的时效2秒已通过去了,所以后面元素才会很突兀的补位。
那么,咱们能够在离开状态name-leave-active上使用absolute让元素脱离正常的文档流,那么一发生离开,后面的元素就能够开始正常的移动过渡了。以下修改:

/* 在此离开过渡的状态类名添加absolute定位,以受影响元素正常的使用平滑过渡 */
.num-list-leave-active {
    position: absolute;
}

clipboard.png

总结下,就是当使用行内元素时,使用位置转换的过渡须要把其设置为 inline-block,不然位置转换没有效果。在离开/进入过渡时,受影响的其余元素应该使用移动过渡 name-move为期定义移动过渡。还须要在离开过渡状态类中 name-leave-active设置离开过渡元素的定位为 absolute使其脱离正常的文档流,以不妨碍其余元素的移动过渡。

列表过渡的另外一种写法

观察其上CSS代码,能够发现<span>进入/离开/移动过渡定义的过渡都同样,即:

.num-list-move,
.num-list-enter-active,
.num-list-leave-active {
    transition: all 2s;
}

所以能够去掉这些过渡状态类名,之间写在<span>的样式里就能够了。

.num-list-item {
    display:inline-block;
    margin-right: 10px;
    /* 代替 进入/离开/移动过渡状态类 */
    transition: all 2s;
}
.num-list-enter,
.num-list-leave-to {
    opacity: 0;
    transform: translateY(30px);
}
/* 
.num-list-move,
.num-list-enter-active,
.num-list-leave-active {
    transition: all 2s;
} 
*/
/* 在此离开过渡的状态类名添加absolute定位,以受影响元素正常的使用平滑过渡 */
.num-list-leave-active {
    position: absolute;
}

其余都原封不动,效果和第一种同样。

为移动过渡添加功能

在第一种写法的基础上添加如下功能,再次理解移动过渡。
js中导入lodash库,使用shuffle方法从新排列数组items

methods: {
    ramdomIndex: function () {
        return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
        this.items.splice(this.ramdomIndex(), 0, this.nextNum++)
    },
    remove: function () {
        this.items.splice(this.ramdomIndex(), 1)
    },
    //使用`shuffle`方法从新排列数组`items`。
    shuffle: function () {
        this.items = _.shuffle(this.items)
    }
}

页面添加一个按钮

<div id="app-1">
    <button @click="add">添加</button>
    <button @click="remove">移除</button>
    <button @click="shuffle">重排列</button>
    <transition-group name="num-list" tag="p">
        <span v-for="item in items" :key="item" class="num-list

-item">
            {{item}}
        </span>
    </transition-group>
</div>

clipboard.png

列表过渡的其余示例

一个汽车站:

new Vue({
    el: '#app-2',
    data: {
        buses: [1, 2, 3, 4, 5],
        nextBus: 6
    },
    mounted() {
        setInterval(() => {
            const headOrTail = () => Math.random() >= 0.5
            if (headOrTail()) {
                this.buses.push(this.nextBus)
                this.nextBus += 1
            } else {
                this.buses.splice(0, 1)
            }
        }, 2000)
    }
})
.station-bus {
    display: inline-block;
    margin-left: 10px;
    font-size: 2em;
}
.station-enter {
    opacity: 0;
    transform: translateX(30px);
}
.station-leave-to {
    opacity: 0;
    transform: translateX(-30px);
}
.station-move,
.station-enter-active,
.station-leave-active {
    transition: all 2s;
}
.station-leave-active {
    position: absolute;
}
<div id="app-2">
    <h3>公交车站</h3>
    <transition-group tag="p" name="station">
        <span v-for="bus in buses" :key="bus" class="station-bus">?</span>
    </transition-group>
    {{buses}}
</div>

clipboard.png

在元素插入时的钩子上定义一个timer,每隔两秒一辆车进入或离开,为它们设置进入/离开过渡,和其余受影响车辆的移动过渡。

使用组件包装可重用的过渡

若是想要在咱们的站点的各处重用一种过渡,把它包装进一个组件是个好方法。
要在站点上展现/隐藏一些缩略的文章,咱们能够编写一个过渡组件,而后为不一样的缩略文章添加这个过渡组件使其拥有过渡效果。

Vue.component('puff', {
    functional: true,
    render: function (createElement, context) {
        var data = {
            props: {
                'enter-active-class': 'magictime puffIn',
                'leave-active-class': 'magictime puffOut'
            }
        }
        return createElement('transition', data, context.children)
    }
})
new Vue({
    el: '#app-3',
    data: {
        showRecipe: false,
        showNews: false
    }
})
<link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/magic/1.1.0/magic.min.css">
<div id="app-3">
    <button @click="showRecipe = !showRecipe">
        Recipe
    </button>
    <button @click="showNews= !showNews">
        Breaking News
    </button>
    <puff>
        <article v-if="showRecipe" class="card">
            <h3>
                过渡和动画
            </h3>
            <p>
                自定义过渡的类名 在钩子中使用Velocity 两个元素之间的过渡 ...
            </p>
        </article>
    </puff>
    <puff>
        <article v-if="showNews" class="card">
            <h3>
                今日头条
            </h3>
            <p>
                201七、2018面试分享(js面试题记录)记得点赞分享哦;让更多的人看到~~
            </p>
        </article>
    </puff>
</div>

clipboard.png

这里使用了一个magicCSS动画库和函数式组件(todo)
定义一个全局的函数式组件。在其render选项中定义函数并返回一个可重用的元素<puff>,在内部经过magic将进入/离开的过渡效果添加到<puff>的属性上。在页面须要的地方包裹该<puff>元素便可。

动态过渡

响应是Vue永恒的主体,所以过渡和它的属性均可以是动态的。这样咱们能够控制在特定的位置与时间使用特定的过渡。

多个元素之间的过渡和过渡模式中咱们已经展现了动态过渡,对于不一样的<p>元素,咱们使用了不一样的过渡效果和模式。

相关文章
相关标签/搜索