要實現一個帶動畫效果的下拉列表,通常的做法是,將外部容器的高度設為一個固定值,設置其overflow為hidden, 同時為其應用CSS過渡屬性,點擊相應的按鈕后改變外部容器的高度,如下面的實現。
<div id="p11-1-btn" onclick="toggleByHeight()">▼點我展開</div>
<ul id="p11-1" style="height: 0px;">
<li style="background: #FF6666">第一</li>
<li style="background: #FF9900">第二</li>
<li style="background: #CCFF00">第三</li>
<li style="background: #CC3399">第四</li>
</ul>
function toggleByHeight() {
const containerStyle = document.querySelector('#p11-1').style;
const buttonExpand = document.querySelector('#p11-3-btn');
if (containerStyle.height == '0px') {
containerStyle.height = '120px';
buttonExpand.innerText = '▲點我收起';
} else {
containerStyle.height = '0px';
buttonExpand.innerText = '▼點我展開 ';
}
}
#p11-1 {
overflow:hidden;
transition: height 1s;
margin: 0px;
}
#p11-1 li {
font-size: 16px;
color: #ffffff;
line-height: 30px;
text-align: center;
margin: 0px;
}
#p11-1-btn {
cursor: pointer;
background-color: #dcdcdc;
}
上面的方法只適用於內部元素高度已知的情況。我們知道每個 li 的行高為30px, 由於 li 沒有邊框、內邊距、外邊距,則每個li 的高度就是30px, 全部展開后總高度為150px.
但是現實中,我們並不總能知道內部元素的高度,而 display: none
與 display: auto
之間是無法應用過渡效果的(具體為啥我也不知道),因此這種方法就不適用了,那么內部元素的高度應該如何計算出來呢?有人會說可以為父元素設置最大高度 max-height
, 這樣只要 max-height
的值大於內部元素的總高度就行,但這樣做要求我們提供的 max-height
值不能過小也不能過大,過小則內部元素不能全部顯示出來,過大則動畫會出現延時。
更好的方法是用JS去計算內部元素的高度,假設有如下的HTML結構,雖然外部容器的高度為0px, 但內部每個元素的高度仍舊為其原來的高度, 只是它被隱藏了起來而已,我們可以通過JS去計算內部元素的總高度。
<div id="p11-2" style="height: 0px; overflow: hidden;">
<div style="height: 40px; padding:10px">第一</div>
<div style="height: 80px;">第二</div>
<div style="height: 60px;">第三</div>
</div>
<script>
const parent = document.querySelector('#p11-2');
console.log(parent.style.height) // 0px
const childs = document.querySelectorAll('#p11-2 div');
let totalHeight = 0;
childs.forEach(child => {
totalHeight += parseInt(child.style.height);
});
console.log(totalHeight) // 180
</script>
需要注意的是,你必須使用內聯樣式為元素指定高度,使用內部樣式表或者外部樣式表是無法正確通過JS獲取高度值的。解決這個問題的辦法是,獲取該元素的
offsetHeight
而不是height
,offsetHeight
包含該元素的內容高度、垂直內邊距和邊框,這樣一來,不管內部元素有沒有內邊距、邊框,我們總能正確的計算其高度。
下面利用這種方法重寫一下上面的程序。
<div id="p11-3-btn" onclick="toggleByOffsetHeight()">▼點我展開</div>
<ul id="p11-3" style="height: 0px;">
<li style="background: #FF6666; padding: 10px 0px">第一</li>
<li style="background: #FF9900; border: solid 5px red">第二</li>
<li style="background: #CCFF00; padding: 15px 0px">第三</li>
<li style="background: #CC3399; border: double 3px blue">第四</li>
</ul>
function toggleByOffsetHeight() {
const containerStyle = document.querySelector('#p11-3').style;
const childs = document.querySelectorAll('#p11-3 li');
const buttonExpand = document.querySelector('#p11-3-btn');
let totalHeight = 0;
childs.forEach(child => {
totalHeight += parseInt(child.offsetHeight);
});
if (containerStyle.height == '0px') {
containerStyle.height = `${totalHeight}px`;
buttonExpand.innerText = '▲點我收起';
} else {
containerStyle.height = '0px';
buttonExpand.innerText = '▼點我展開 ';
}
}
#p11-3 {
overflow:hidden;
transition: height 1s;
margin: 0px;
}
#p11-3 li {
font-size: 16px;
color: #ffffff;
line-height: 30px;
text-align: center;
margin: 0px;
}
#p11-3-btn {
cursor: pointer;
background-color: #dcdcdc;
}
可以看到,無論內部元素占據的高度是什么,我們總能實現一個流暢的下拉列表過渡動畫。
該篇博客內的代碼已同步到Github
參考資料:
[1]. MDN文檔 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetHeight