不定高元素動畫實(shí)現(xiàn)方案(下)
前情
最近小程序接了一個需求,需要實(shí)現(xiàn)一個列表,列表可展開收起,展開收起需要有一個動畫效果,而列表個數(shù)不定且每項(xiàng)內(nèi)容高度也不固定,所以是一個不定高的收起展開效果,于是特意抽時間嘗試了一些動畫實(shí)現(xiàn)方案,特此記錄
通過js+css變量來實(shí)現(xiàn)
實(shí)現(xiàn)思路是js獲取要實(shí)現(xiàn)動畫元素的高度,再通過css變量把高度設(shè)置在元素上,當(dāng)hover的時候,把元素的高度設(shè)為css變量
關(guān)鍵代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="container">
<div class="inner">
<div class="header">header</div>
<ul class="list" id="list" style="--height:0px">
<li>scale111111111</li>
<li>scale2222222222</li>
<li>scale333333333</li>
<li>scale444444444</li>
</ul>
</div>
</div>
</body>
</html>
*{
margin: 0;
padding: 0;
}
.container{
width: 100%;
overflow: hidden;
}
.header{
width: 100%;
height: 48px;
background-color: #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.list{
background-color: green;
height: var(--height)
}
document.addEventListener('DOMContentLoaded', () => {
var list = document.querySelector('#list')
list.style.setProperty('--height', list.scrollHeight + 'px')
})
演示地址:https://jsbin.com/besufuyihe/edit?html,css,js,output

注意:
css動畫使用比js要簡單,同時性能上也會有優(yōu)勢,所以能用css實(shí)現(xiàn)的就盡量用css實(shí)現(xiàn),此方式兼容性棒,JS干預(yù)也不是特別多
通過js+Flip動畫來實(shí)現(xiàn)
FLIP是一種高性能動畫技術(shù),常用于實(shí)現(xiàn)復(fù)雜動畫,代表四個步驟:
- First(初始):記錄元素的初始狀態(tài)
- Last(最終):設(shè)置元素到最終狀態(tài)并記錄
- Invert(反轉(zhuǎn)):計(jì)算差異并將元素恢復(fù)到初始狀態(tài)
- Play(播放):應(yīng)用過渡效果并播放動畫
代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="container" id="container">
<div class="inner">
<div class="header">header</div>
<ul class="list">
<li>Flip111111111</li>
<li>Flip2222222222</li>
<li>Flip333333333</li>
<li>Flip444444444</li>
</ul>
</div>
</div>
</body>
</html>
*{
margin: 0;
padding: 0;
}
.container{
width: 100%;
height: 48px;
overflow: hidden;
}
.header{
width: 100%;
height: 48px;
background-color: #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.list{
background-color: green;
}
document.addEventListener('DOMContentLoaded', () => {
// 獲取容器元素
const containero = document.querySelector('#container')
// 鼠標(biāo)進(jìn)入容器時的FLIP動畫實(shí)現(xiàn)
containero.addEventListener('mouseenter', () => {
// 第一步(F-First):記錄初始狀態(tài)(隱式的,初始高度為48px)
// 第二步(L-Last):設(shè)置元素為最終狀態(tài),獲取自然高度
containero.style.height = 'auto';
const conh = containero.scrollHeight; // 獲取內(nèi)容實(shí)際高度
// 第三步(I-Invert):將元素恢復(fù)到初始狀態(tài)
containero.style.height = '48px';
containero.offsetHeight; // 觸發(fā)重繪,確保樣式應(yīng)用
// 第四步(P-Play):添加過渡效果并播放動畫到最終狀態(tài)
containero.style.transition = 'height .4s';
containero.style.height = conh + 'px';
})
// 鼠標(biāo)離開時恢復(fù)初始高度
containero.addEventListener('mouseleave', () => {
containero.style.height = '48px';
})
})
演示地址:https://jsbin.com/barihazasi/edit?html,css,js,output

注意:
Flip用于實(shí)現(xiàn)一些復(fù)雜動畫是非常常見的方式,動畫還是使用css3的transition,還是遵守一個原則,能用css實(shí)現(xiàn)動畫的還是用css來做
純js來實(shí)現(xiàn)
早期js實(shí)現(xiàn)動畫都是使用setInterval/setTimeout來實(shí)現(xiàn)動畫效果的,現(xiàn)在如果要用js來實(shí)現(xiàn)動畫可以使用requestAnimationFrame來代替,來看一下他們的對比
1. 觸發(fā)時機(jī)與刷新頻率
- setTimeout/setInterval:
- 按照指定的時間間隔(毫秒)觸發(fā)回調(diào)函數(shù)
- 時間間隔是近似值,實(shí)際執(zhí)行會受 JavaScript 線程繁忙程度影響
- 即使頁面處于后臺或隱藏狀態(tài),仍可能繼續(xù)執(zhí)行
- 刷新頻率固定,無法與顯示器刷新率同步
- requestAnimationFrame:
- 由瀏覽器決定執(zhí)行時機(jī),通常與顯示器刷新率同步(60Hz 屏幕約每 16ms 執(zhí)行一次)
- 自動調(diào)整執(zhí)行頻率以匹配設(shè)備性能
- 當(dāng)頁面處于后臺或標(biāo)簽頁隱藏時,會暫停執(zhí)行以節(jié)省資源
- 執(zhí)行時機(jī)在瀏覽器重繪之前,確保動畫平滑
2. 性能表現(xiàn)
- setTimeout/setInterval:
- 可能導(dǎo)致動畫卡頓或跳幀,因?yàn)闊o法與瀏覽器渲染周期對齊
- 多個定時器同時運(yùn)行時可能導(dǎo)致性能問題
- 高頻率定時器 (如 10ms) 可能導(dǎo)致瀏覽器過度渲染,消耗不必要的資源
- requestAnimationFrame:
- 瀏覽器會優(yōu)化動畫執(zhí)行,確保流暢性
- 自動調(diào)整幀率,在性能不足時降低頻率
- 不會在頁面不可見時執(zhí)行,節(jié)省 CPU/GPU 資源
關(guān)鍵代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="container" id="container">
<div class="inner">
<div class="header">header</div>
<ul class="list">
<li>js111111111</li>
<li>js2222222222</li>
<li>js333333333</li>
<li>js444444444</li>
</ul>
</div>
</div>
</body>
</html>
*{
margin: 0;
padding: 0;
}
.container{
width: 100%;
height: 48px;
overflow: hidden;
}
.header{
width: 100%;
height: 48px;
background-color: #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.list{
background-color: green;
}
document.addEventListener('DOMContentLoaded', () => {
const containero = document.querySelector('#container')
// 初始高度為48px(與CSS中定義的一致)
const initH = 48;
// 獲取容器完全展開時的高度
const conh = containero.scrollHeight;
// 每一幀增加/減少的高度像素
const step = 5;
// 標(biāo)記動畫是否正在運(yùn)行,防止多次觸發(fā)
let isAnimating = false;
containero.addEventListener('mouseenter', () => {
// 如果已經(jīng)在動畫中,則不重新開始
if (isAnimating) return;
// 獲取當(dāng)前高度作為起點(diǎn)
let currentHeight = parseInt(containero.offsetHeight);
isAnimating = true;
const expand = function () {
// 增加高度
currentHeight += step;
// 檢查是否達(dá)到目標(biāo)高度
if (currentHeight >= conh) {
containero.style.height = conh + 'px';
isAnimating = false;
return;
}
// 設(shè)置新高度并繼續(xù)動畫
containero.style.height = currentHeight + 'px';
requestAnimationFrame(expand);
}
// 開始展開動畫
requestAnimationFrame(expand);
})
// 鼠標(biāo)離開時恢復(fù)初始高度
containero.addEventListener('mouseleave', () => {
// 如果已經(jīng)在動畫中,則不重新開始
if (isAnimating) return;
// 獲取當(dāng)前高度作為起點(diǎn)
let currentHeight = parseInt(containero.offsetHeight);
isAnimating = true;
const collapse = function () {
// 減少高度
currentHeight -= step;
// 檢查是否達(dá)到初始高度
if (currentHeight <= initH) {
containero.style.height = initH + 'px';
isAnimating = false;
return;
}
// 設(shè)置新高度并繼續(xù)動畫
containero.style.height = currentHeight + 'px';
requestAnimationFrame(collapse);
}
// 開始收起動畫
requestAnimationFrame(collapse);
})
})
演示地址:https://jsbin.com/kunuqomale/edit?html,css,js,output

注意:
早期想實(shí)現(xiàn)動畫都是依賴js來做的,看這代碼量就知道用JS實(shí)現(xiàn)既復(fù)雜又性能一般,現(xiàn)在css3動畫已經(jīng)兼容性非常好了,推薦用css3動畫來實(shí)現(xiàn),使用js來實(shí)現(xiàn)動畫是最終選擇了,當(dāng)然真正項(xiàng)目開發(fā)也不會自己去寫js動畫函數(shù)了,市面上有很多強(qiáng)大易用的動畫庫,早期jQuery就自帶動畫函數(shù),還有我覺得市面上最強(qiáng)大的動畫庫GSAP是目前js實(shí)現(xiàn)動畫的首選了
總結(jié)
對于做技術(shù)的我們,每天都是提出問題解決問題的一個過程,過程中會嘗試各種方案,因?yàn)榻鉀Q問題的方案千千萬,每種方案都有自己的適合場景,此篇文章記錄了實(shí)現(xiàn)不定高內(nèi)容過渡效果的另外三種實(shí)現(xiàn)方式,上中下篇文章一共介紹了9種實(shí)現(xiàn)動畫的方式
個人知識有限,如果你有更好的實(shí)現(xiàn)方案,希望不吝分享,一起學(xué)習(xí)一起進(jìn)步

浙公網(wǎng)安備 33010602011771號