過渡(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>元素,咱們使用了不一樣的過渡效果和模式。

相關文章
相關標籤/搜索