css+js制作不定高度展開收起動(dòng)畫詳解
最近在做需求的時(shí)候,遇見了元素高度展開收起的動(dòng)畫需求,一開始是想到了使用 transition: all .3s; 來做動(dòng)畫效果,在固定高度的情況下,transition 動(dòng)畫很好使,滿足了需求,但是如果要考慮之后可能還會(huì)有更改的情況下,如果每次都是用固定高度來做動(dòng)畫,會(huì)顯得很繁瑣,也很呆,就想到了使用 height: auto; 來做高度動(dòng)畫,但是,眾所周知,高度設(shè)置成 auto 時(shí)是不會(huì)觸發(fā) transition 動(dòng)畫的
.container {
height: 0;
background-color: #ccc;
overflow: hidden;
transition: all .3s;
}
.container:hover {
height: 1000px;
}效果如圖,不能滿足動(dòng)畫的要求
在一番查找實(shí)驗(yàn)之后,目前發(fā)現(xiàn)了如下幾種方法:
1. max-height 最大高度
transition 動(dòng)畫可以響應(yīng) max-height
.container {
max-height: 0;
background-color: #ccc;
overflow: hidden;
transition: all .3s;
}
.container:hover {
max-height: 1000px;
}但是使用 max-height 做動(dòng)畫有一個(gè)問題,如果設(shè)置的最大高度越大,但是實(shí)際高度確與最大高度相差甚遠(yuǎn),那么整體的動(dòng)畫速度就會(huì)非常快,動(dòng)畫的時(shí)間只會(huì)是 實(shí)際高度 / 最大高度 * 動(dòng)畫時(shí)間,因?yàn)檎归_動(dòng)畫原本預(yù)期高度是設(shè)置的最大高度,所以整體時(shí)間是以最大高度完全展開所用時(shí)間來進(jìn)行的,但是當(dāng)?shù)竭_(dá)實(shí)際高度的時(shí)候動(dòng)畫就停止了,所以最終動(dòng)畫時(shí)間會(huì)與期望時(shí)間相差甚遠(yuǎn)。
max-height 方法做動(dòng)畫也是一個(gè)好方法,如果能夠確定大致高度的話,使用此方法是最簡(jiǎn)單也是最快的方法,但是如果不能確定大致高度或整體高度經(jīng)常變化的話,可以考慮其他方法。
2. grid 動(dòng)畫
grid 網(wǎng)格布局,是一種較新的布局,號(hào)稱是最強(qiáng)大的布局方案。grid 布局不是本文的介紹重點(diǎn),并且較為復(fù)雜,如果感興趣的話,可以參考相關(guān)文章,如:
grid 布局中可以使用 fr 單位,fr 單位是支持過度動(dòng)畫的(0fr=>1fr),將 grid 布局下的子元素,初始設(shè)置為0fr,在 :hover 狀態(tài)下設(shè)置為 1fr,就能夠?qū)崿F(xiàn)不定高度動(dòng)畫效果,但是如果子元素有內(nèi)容,在設(shè)置 0fr 的時(shí)候,會(huì)被其內(nèi)容撐開,所以要給子元素添加 min-height: 0;
.container {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: all .3s;
}
.container:hover {
grid-template-rows: 1fr;
}
.container .child {
min-height: 0;
}如果想要實(shí)現(xiàn)帶有基礎(chǔ)高度的展開收起動(dòng)畫,我們可以設(shè)置 min-height: 100px;
.container .child {
min-height: 100px;
}雖然此時(shí)實(shí)現(xiàn)了帶有基礎(chǔ)高度的動(dòng)畫效果,但是可以看到,如果我把 transition: all 3s; 的動(dòng)畫時(shí)間設(shè)置的較大,就可以看出來,雖然有基礎(chǔ)高度,但是整個(gè)動(dòng)畫的效果還是要實(shí)現(xiàn) 0fr 到 1fr 的動(dòng)畫效果,基礎(chǔ)高度部分不會(huì)有動(dòng)畫效果,這也算是一個(gè)小的缺點(diǎn),如果動(dòng)畫時(shí)間較短并且基礎(chǔ)高度也不大的話,可以這樣使用,并不會(huì)有太大的影響效果。
但是 grid 布局有可能有兼容性的問題,grid-template-rows 動(dòng)畫的支持可能有兼容性問題

3. js 控制動(dòng)畫
寫這篇文章的原因是因?yàn)樵诳错?xiàng)目代碼的時(shí)候看見了 $(.xx).slideDown() 方法實(shí)現(xiàn)了元素的下滑動(dòng)畫,覺得很不錯(cuò),想學(xué)習(xí)一下怎么實(shí)現(xiàn)的,實(shí)現(xiàn)效果如下:
但是在看元素的時(shí)候卻只能看見下面的樣子,發(fā)現(xiàn)不是 css 實(shí)現(xiàn)的,是使用 js 不斷改變?cè)氐母叨葋韺?shí)現(xiàn)的:
我又去看了一下 ant-design 的 Menu 組件,通過觀察元素,發(fā)現(xiàn)其也是不斷改變高度來實(shí)現(xiàn)的(Ps: 我并沒有去看源碼,如果有誤,多謝指正)。
實(shí)現(xiàn)
首先要思考整個(gè)實(shí)現(xiàn)的思路
展開的時(shí)候,元素從無到有,我們應(yīng)該首先獲取整個(gè)元素的實(shí)際高度使用 offsetHeight 來獲取,獲取到整體高度后就要計(jì)算每一次增加或者減少的高度,通過定時(shí)器不斷增加或減少元素的高度,直到到了最大高度或 0 后停止
展開
const element = document.getElementById('container');
let expandTimer = null;
let offsetHeight = 0;
// 獲取元素總高度
element.style.display = 'block';
let height = 0;
// 先將 display 設(shè)置為 block,獲取到的 offsetHeight 才是正確的高度,之后才能設(shè)置元素高度
offsetHeight = element.offsetHeight;
const stepHeight = offsetHeight / 30;
element.style.height = height + 'px';
expandTimer = setInterval(() => {
height += stepHeight;
if (height >= offsetHeight) {
clearInterval(expandTimer);
element.style = null;
return;
}
element.style.height = height + 'px';
}, 10);收起
let collapseTimer = null;
offsetHeight = element.offsetHeight;
let height = offsetHeight;
const stepHeight = offsetHeight / 30;
element.style.height = height + 'px';
collapseTimer = setInterval(() => {
height -= stepHeight;
if (height <= 0) {
clearInterval(collapseTimer);
element.style = null;
return;
}
element.style.height = height + 'px';
}, 10);現(xiàn)在能夠正確展開收起,但是我們?cè)谡归_收起的時(shí)候也會(huì)有相反的操作,比如鼠標(biāo)進(jìn)入元素展開離開收起,在展開的過程中鼠標(biāo)離開了,我們應(yīng)該立刻就將元素收起,而不是等動(dòng)畫結(jié)束后在進(jìn)行下一個(gè)動(dòng)畫,所以要將展開收起操作合并操作才可以
const element = document.getElementById('container');
let expandTimer = null;
let collapseTimer = null;
// 我認(rèn)為在一次展開后,直到收起完成之前,這個(gè)元素的實(shí)際高度都不應(yīng)該發(fā)生變化,但是可以在下一次展開時(shí)發(fā)生變化,所以在展開時(shí)會(huì)進(jìn)行賦值,在收起完成時(shí)會(huì)將此值清空
let offsetHeight = 0;
let stepHeight = 0;
const handleClick = () => {
// 如果當(dāng)前 expandTimer 值存在,就標(biāo)識(shí)當(dāng)前是正在展開或已經(jīng)展開,接下來要進(jìn)行的是收起操作
if (expandTimer) {
clearInterval(expandTimer);
expandTimer = null;
// 收起時(shí)的初始高度是元素的當(dāng)前實(shí)際高度,即使是元素在展開動(dòng)畫過程中,也要從當(dāng)前元素高度進(jìn)行收起動(dòng)畫
let height = element.offsetHeight;
collapseTimer = setInterval(() => {
height -= stepHeight;
if (height <= 0) {
// 高度小于等于 0 代表動(dòng)畫完成,將數(shù)據(jù)進(jìn)行重置
clearInterval(collapseTimer);
offsetHeight = 0;
// 要將元素的高度置為 null,不然會(huì)影響下一次展開時(shí)獲取正確的高度
element.style.height = null;
// display 設(shè)為 null,要將元素隱藏
element.style.display = 'none';
return;
}
element.style.height = height + 'px';
}, 10);
} else {
clearInterval(collapseTimer);
collapseTimer = null;
// 獲取元素總高度
element.style.display = 'block';
let height = 0;
如果當(dāng)前沒有 offsetHeight 就要重新獲取
if (!offsetHeight) {
offsetHeight = element.offsetHeight;
// 每一次給元素添加或減少的高度,除以 30 是自己設(shè)定的,跟下面定時(shí)器的每次間隔時(shí)間一起控制整個(gè)高度動(dòng)畫的時(shí)長(zhǎng),也可以給函數(shù)添加第二個(gè)時(shí)間參數(shù),可以自由控制動(dòng)畫時(shí)間
stepHeight = offsetHeight / 30;
} else {
// 如果有 offsetHeight 就代表正在進(jìn)行收起動(dòng)畫,應(yīng)該從收起動(dòng)畫的當(dāng)前高度進(jìn)行展開動(dòng)畫
height = element.offsetHeight;
}
element.style.height = height + 'px';
expandTimer = setInterval(() => {
height += stepHeight;
if (height >= offsetHeight) {
// 當(dāng)前高度如果已經(jīng)到了元素的實(shí)際高度,就要清除定時(shí)器
clearInterval(expandTimer);
// 將 expandTimer 設(shè)為 1 是因?yàn)楫?dāng)前是以 expandTimer 判斷是否正在或已經(jīng)進(jìn)行了展開動(dòng)畫,所以要在完成是設(shè)為 1,在收起動(dòng)畫的開始時(shí)會(huì)將值設(shè)為 null
expandTimer = 1;
element.style = null;
return;
}
element.style.height = height + 'px';
}, 10);
}
};最終實(shí)現(xiàn)效果
4. 總結(jié)
上面的三種方式實(shí)現(xiàn)效果都是各有千秋 - max-height 方法實(shí)現(xiàn)是最簡(jiǎn)單,也是效率最高的方式,但是也有動(dòng)畫時(shí)間不定的缺陷 - grid 方式實(shí)現(xiàn)比 max-height 稍微復(fù)雜一些,但是整體效果要比 max-height 更好,但是目前瀏覽器的支持方面可能有所不足,如果有低版本的兼容性要求的話,還是不能使用 - js 方式整體最復(fù)雜,但是卻沒有上面兩種方式的缺陷與問題,使用范圍也更廣泛,但是是 js 的實(shí)現(xiàn)方式,性能肯定是不如 css,雖然不如,但是由于整體操作也較為簡(jiǎn)單,所以也不會(huì)有什么性能問題
幾種方法的取舍全看個(gè)人需求了。
如果有鼠標(biāo)進(jìn)入展開,離開收起的操作,可以配合使用 onmouseover onmouseout 事件來監(jiān)聽鼠標(biāo)的進(jìn)入離開。
其他還有像是 transform: scale(0); 的實(shí)現(xiàn)也是可以,但是整體動(dòng)畫效果就是一個(gè)縮小的效果,而且元素還會(huì)有占位問題,如果沒什么要求也是可以使用的。
到此這篇關(guān)于css+js制作不定高度展開收起動(dòng)畫詳解的文章就介紹到這了,更多相關(guān)css+js制作展開收起動(dòng)畫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript自定義日歷實(shí)現(xiàn)簽到功能
這篇文章主要為大家詳細(xì)介紹了JavaScript自定義日歷實(shí)現(xiàn)簽到功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
js/html光標(biāo)定位的實(shí)現(xiàn)代碼
光標(biāo)定位,想必大家有所了解吧,在本文將為大家介紹的是通過自定義函數(shù)來實(shí)現(xiàn)標(biāo)簽元素的定位,感興趣的朋友可以了解下2013-09-09
es6函數(shù)之嚴(yán)格模式用法實(shí)例分析
這篇文章主要介紹了es6函數(shù)之嚴(yán)格模式用法,結(jié)合實(shí)例形式分析了es6函數(shù)嚴(yán)格模式的定義、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
Axios設(shè)置token請(qǐng)求頭的三種方式
用戶登錄時(shí),后端會(huì)返回一個(gè)token,并且保存到瀏覽器的localstorage中,可以根據(jù)localstorage中的token判斷用戶是否登錄,所以當(dāng)發(fā)送請(qǐng)求時(shí),都要攜帶token給后端進(jìn)行判斷,本文給大家介紹了Axios設(shè)置token請(qǐng)求頭的三種方式,需要的朋友可以參考下2024-02-02
Bootstrap模態(tài)框調(diào)用功能實(shí)現(xiàn)方法
這篇文章主要介紹了Bootstrap模態(tài)框調(diào)用功能實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
JavaScript實(shí)現(xiàn)人體面部活體檢測(cè)的功能
本文詳細(xì)介紹了如何在瀏覽器端使用JavaScript實(shí)現(xiàn)高可靠度的人臉活體檢測(cè),包括核心原理、可行方案、優(yōu)缺點(diǎn)對(duì)比以及示例代碼,感興趣的朋友一起看看吧2025-02-02

