Vue 不定高展開動效原理詳解
使用場景
在大多數(shù) APP 中,都有問答模塊,類似于下面這種(bilibili 為例):

問答模塊的靜態(tài)頁面開發(fā)并不復(fù)雜,也沒有特殊的交互。唯一有一點(diǎn)難度應(yīng)該是回答部分的展開特效。
- 展開時,需要從上往下將回答部分的 div 慢慢撐開,上面的箭頭也要有旋轉(zhuǎn)的特效。
- 收回時,需要從下往上將回答部分的 div 慢慢縮小,上面的箭頭也要有旋轉(zhuǎn)的特效。
對于一般的展開、隱藏特效,只需要在對應(yīng)元素的 height 上面增加過渡效果即可。但問題是: 不知道對應(yīng)的 div 的高度,其高度是內(nèi)部的元素自動撐開的,此時直接在 height 屬性上面添加過渡效果會失效(后面會說明原因)。
對于箭頭的旋轉(zhuǎn),則只需要在箭頭元素的 transform 上面增加過渡效果,然后讓其旋轉(zhuǎn) 180 度(rotateZ(180deg))即可,這個比較好實(shí)現(xiàn)。
背景
今天做需求時,正好需要做這種特效。也就是上面的第一張圖。先介紹一下列表的數(shù)據(jù)結(jié)構(gòu)和其 DOM 結(jié)構(gòu)。
列表數(shù)據(jù)結(jié)構(gòu)如下:
// Item 表示問答的每一項
interface Item {
Q: string; // 問題
A: string; // 回答
show: boolean; // 是否展示
}
// List 表示問答列表
type List = Item[];項目中并未使用 TypeScript,這里用 interface 是為了方便理解。
DOM 結(jié)構(gòu)(Vue 版本)如下:
<div class="qa panel">
<div class="qa__title">
常見問題
</div>
<div class="list-qa">
<div
v-for="(item, ind) in qaList"
:key="ind"
class="list-qa__item"
>
<div class="list-qa__question">
<span>{{ item.Q }}</span>
<span class="list-qa__question__arrow" />
</div>
<span class="list-qa__answer">
{{ item.A }}
</span>
</div>
</div>
</div>上面的結(jié)構(gòu)簡化了一些交互邏輯和展示邏輯,默認(rèn)問答列表的每一項都會展示。最外層包裹了一層 div,上面是標(biāo)題,下面是問答列表,問答列表的每一項包括問題、箭頭 icon 和答案。
實(shí)現(xiàn)
因為項目使用的框架是 Vue,所以以 Vue 為例,來分析一下如何實(shí)現(xiàn)它,以及其實(shí)現(xiàn)的原理。
回答是否展示,可以用一個變量控制,這里是 item 的 show 屬性。使用 v-show 實(shí)現(xiàn),因為用戶可能會多次點(diǎn)擊箭頭,導(dǎo)致回答頻繁地展示或隱藏。
<div
v-for="(item, ind) in list"
:key="ind"
class="list-item"
>
<!-- 。。。省略不相關(guān)元素。。。 -->
<span
v-show="item.show"
class="list-answer"
>
{{ item.A }}
</span>
</div>transition 組件
在 Vue 中,可以使用 transition 組件來為元素添加動態(tài)效果。transition 組件讓我們可以為使用條件渲染(v-if、v-show)的元素添加進(jìn)入、離開時的過渡效果。
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}這樣在 name 為 fade 的 transition 組件包裹的 p 標(biāo)簽展示和隱藏時,會有一個 0.5s 的淡入淡出效果。
過渡效果原理
在展示時,p 標(biāo)簽的 opacity(透明度)會從 0(.fade-enter 類選擇器中設(shè)定的值)開始增加,經(jīng)過 0.5s 之后,增加至 opacity: 1(元素默認(rèn)的透明度 opacity 為 1)。
在隱藏時,p 標(biāo)簽的 opacity(透明度)會從 1 開始減少,經(jīng)過 0.5s 之后,減少至 opacity: 0(.fade-leave-to 類選擇器中設(shè)定的值)。
這樣就實(shí)現(xiàn)了淡入淡出效果。
同樣的,如果我們想讓一個元素展示時高度從 0 開始增加,經(jīng)過某一個時間,達(dá)到具體的值;隱藏時高度從該具體值開始減少,經(jīng)過某一個時間,達(dá)到 0。這樣就能實(shí)現(xiàn)我們前面需要的效果。
我們可以用 css transition 為某一個元素設(shè)置過渡效果,過渡效果作用在這個元素的某個屬性上、過渡效果的時長等。
.box {
transition: height 1s;
}上面代表為 class 為 box 的元素設(shè)置了過渡效果,作用在它的 height 屬性上面,過渡效果的時長為 1s。當(dāng)該元素的高度從某一個值變化到另一個值時,就會有一個長為 1s 的過渡效果。
過渡效果的本質(zhì)是: 當(dāng)作用的屬性的值變化時,并不會立即從一個值變?yōu)榱硪粋€值,而是在變化的過程中,將中間狀態(tài)呈現(xiàn)出來。
例如:設(shè)置了過渡效果的元素的高度(height)從 0 變化到 100px 時,并不是直接從 0 變化到 100px 的,其變化過程是一個連續(xù)的狀態(tài),從 0 到 1px,從 1px 到 2px······直到 100px。把中間的高度展現(xiàn)出來,就可以讓用戶看到過渡效果。
再例如:設(shè)置了過渡效果的元素的透明度(opacity)從 0 變化到 1 時,并不是直接從 0 變化到 1 的,其變化過程也是一個連續(xù)的狀態(tài),從 0 到 0.1,從 0.1 到 0.2······直到 opacity 為1。這樣用戶就可以看到一個元素從透明狀態(tài)逐漸變得清晰。當(dāng)然,并不一定就是從 0 變化到 0.1,然后從 0.1 變化到 0.2,這個過程是一個連續(xù)的過程,它的值在慢慢增加,增量是多少并不重要。
需要實(shí)現(xiàn)過渡效果,就需要一個起始態(tài)和一個終止態(tài),瀏覽器能夠從起始態(tài)逐步過渡到終止態(tài)。也就是從起始態(tài)到終止態(tài)之間的部分是連續(xù)的,是可以計算的,這樣瀏覽器才能把中間的狀態(tài)給我們呈現(xiàn)出來。
再回到之前的問題:不知道 div 的高度,其高度是內(nèi)部的元素自動撐開的,此時直接在 height 屬性上面添加過渡效果會失效。
為什么會失效?為什么會失效?為什么會失效?
對于一個 div,如果它的高度是由子元素?fù)伍_的,那么它的 css 樣式 height 屬性的值為 auto。從 0 變到 auto,或者從 auto 變到 0,其中間狀態(tài)都是不可計算的,瀏覽器沒發(fā)給我們展示出中間狀態(tài),所以我們看不到過渡效果。
既然從 0 變到 auto,或者從 auto 變到 0,中間狀態(tài)無法計算,那我們可以顯式地告訴瀏覽器一個數(shù)值,應(yīng)該從 0 變到多少,或者從多少變到 0,讓瀏覽器可以計算出中間狀態(tài),這樣不就能看到過渡效果了嗎?
解決
當(dāng)展開時,起始態(tài)為 0,我們通過 getComputedStyle(element).height 得到元素的具體高度 x(終止態(tài))。給元素設(shè)置 transition 屬性,然后將元素的高度從 0 變到 x,這樣就能實(shí)現(xiàn)展開的動效了。
<transition
name="slide"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
>
<span
v-show="item.show"
class="list-answer"
>{{ item.A }}</span>
</transition>beforeLeave(el) {
// 給元素設(shè)置過渡效果
el.style.transition = '0.3s height ease-in-out';
// 高度變化時,讓其內(nèi)容隱藏
el.style.overflow = 'hidden';
},
leave(el) {
el.style.height = 'auto';
// 設(shè)置高度為具體的值
el.style.height = window.getComputedStyle(el).height;
// 強(qiáng)制瀏覽器回流,否則瀏覽器會合并兩次元素的高度更改(回流重繪的知識)
el.offsetHeight;
el.style.height = '0px';
},
afterLeave(el) {
// el.style.height = null;
// 收尾工作,展示完過渡效果之后,設(shè)為原來的值
el.style.transition = '';
el.style.overflow = 'visible';
},這里需要給元素設(shè)置 overflow: hidden,在元素高度小于內(nèi)部內(nèi)容的高度時,才會隱藏內(nèi)容。
同樣地,隱藏時先通過 getComputedStyle(element).height 得到元素的具體高度 x(起始態(tài)),給元素設(shè)置 transition 屬性,然后將元素的高度從 x 變到 0,這樣就能實(shí)現(xiàn)隱藏的動效了。
beforeEnter(el) {
// 給元素設(shè)置過渡效果
el.style.transition = '0.3s height ease-in-out';
// 高度變化時,讓其內(nèi)容隱藏
el.style.overflow = 'hidden';
},
enter(el) {
el.style.height = 'auto';
// 保存元素原來的高度
const endWidth = window.getComputedStyle(el).height;
el.style.height = '0px';
// 強(qiáng)制瀏覽器回流,否則瀏覽器會合并兩次元素的高度更改(回流重繪的知識)
el.offsetHeight;
el.style.height = endWidth;
},
afterEnter(el) {
// el.style.height = null;
// 收尾工作,展示完過渡效果之后,設(shè)為原來的值
el.style.transition = '';
el.style.overflow = 'visible';
},箭頭的旋轉(zhuǎn)動效就比較簡單了。先設(shè)置過渡效果,然后只需要在點(diǎn)擊箭頭的時候,動態(tài)為這個元素添加一個類名,讓其旋轉(zhuǎn)屬性生效(rotateZ(180deg));當(dāng)再一次點(diǎn)擊的時候,去掉這個類名就好了。
<span
class="list-question__arrow"
:class="{'list-question__rotate-arrow': !item.show}"
@click="onClickPromblem(ind)"
/>onClickPromblem(index) {
const qaItem = this.qaList[index];
this.$set(qaItem, 'show', !qaItem.show);
},list-question__arrow {
width: 12px;
height: 12px;
background: url(https://xxx.com/arrowup.png) center no-repeat;
transition: transform .4s;
}
list-question__rotate-arrow {
transform: rotateZ(180deg);
transition: transform .4s;
}到此這篇關(guān)于Vue 不定高展開動效原理詳解的文章就介紹到這了,更多相關(guān)Vue 不定高展開 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vue前端rsa加密遇到的問題message too long for RS
這篇文章主要介紹了解決vue前端rsa加密遇到的問題message too long for RSA,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
vue template中slot-scope/scope的使用方法
今天小編就為大家分享一篇vue template中slot-scope/scope的使用方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue+swiper實(shí)現(xiàn)側(cè)滑菜單效果
這篇文章主要為大家詳細(xì)介紹了vue+swiper實(shí)現(xiàn)側(cè)滑菜單效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
vue3配置代理實(shí)現(xiàn)axios請求本地接口返回PG庫數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了vue3配置代理實(shí)現(xiàn)axios請求本地接口返回PG庫數(shù)據(jù)的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2025-03-03
Vue3+Element+Ts實(shí)現(xiàn)表單的基礎(chǔ)搜索重置等功能
本文主要介紹了Vue3+Element+Ts實(shí)現(xiàn)表單的基礎(chǔ)搜索重置等功能,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

