CSS實現展開動畫
展開收起效果是比較常見的一種交互方式,通常的做法是控制display
屬性值在none
和其它值之間切換,雖說功能可以實現,但是效果略顯生硬,所以會有這樣的需求——希望元素展開收起能具有平滑的效果。
實現
max-height
首先想到的是通過height
在0
與auto
之間切換,但是結果可能並不會是我們所預期的那樣,原因是我們將要展開的元素內容是動態的,即高度值不確定,因此height
使用的值是默認的auto
,從0px
到auto
是無法計算的,因此無法形成過渡或動畫效果。
據此我們可以使用max-height
,將max-height
從0
過渡到一個能夠大於完全顯示內部元素的值,展開后的max-height值,只需要設定為保證比展開內容高度大的值即可,在max-height
值比height
值大的情況下,元素仍會默認采用自身的高度值即auto
,如此一來一個高度不定的元素展開收起動畫效果就實現了。
請注意這種方式實現還是有限制的,使用CSS
進行過渡動畫的時候依舊是通過計算0
到設定的max-height
高度值進行計算的,在實際應用中如果max-height
值太大,在元素收起的時候將會產生延遲的效果,這是因為在收起時,max-height
從設定的特別大的值,到元素自身高度值的變化過程將占用較多時間,此時畫面表現則會產生延遲的效果。因此建議將max-height
值設置為足夠安全的最小值,這樣在收起時即使有略微延遲,也會因為時間很短,難以被用戶感知,將不會影響體驗。
<!DOCTYPE html>
<html>
<head>
<title>展開動畫</title>
<style type="text/css">
.page{
width: 200px;
padding: 10px 20px;
border: 1px solid #eee;
}
.container {
overflow: hidden;
}
.container > .options{
transition: all .5s;
max-height: 0;
}
.container > .unfold{
max-height: 150px;
}
.container > .btn{
color: #4C98F7;
cursor: pointer;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="page">
<div class="container">
<div class="btn" onclick="operate(this)" unfold="1">展開</div>
<div class="options">
<div class="option">選項1</div>
<div class="option">選項2</div>
<div class="option">選項3</div>
<div class="option">選項4</div>
<div class="option">選項5</div>
<div class="option">選項6</div>
<div class="option">選項7</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
function operate(btn){
const optionsNode = document.querySelector(".container > .options");
const unfold = btn.getAttribute("unfold");
if(unfold && unfold==="1"){
btn.innerText = "收縮";
optionsNode.classList.add("unfold");
}else{
btn.innerText = "展開";
optionsNode.classList.remove("unfold");
}
btn.setAttribute("unfold", unfold === "0" ? "1" : "0");
}
</script>
</html>
height
使用max-height
必定有一定的局限性,那么不如我們在DOM
加載完成后就取得元素的實際高度並保存,之后直接利用這個真實高度與0
進行動畫過渡即可,因為瀏覽器的渲染順序,在解析JavaScript
時會阻塞DOM
的渲染,所以在獲取元素實際高度再設置高度為0
的過程中一般不會出現閃爍的情況,如果實在擔心因為獲取高度之后再將高度設置為0
可能會有一個閃爍的過程,那么我們可以取得元素父節點后調用cloneNode(true)
方法或者innerHTML
方法取得字符串再innerHTML
到一個新創建的節點,目的就是將其拷貝,之后將其使用絕對定位等放置到屏幕外即將其設置到屏幕能夠顯示的外部區域,注意此時要設置body
的overflow: hidden;
,之后利用getComputedStyle
取得實際高度,然后再將其移出DOM
結構,此時有了實際高度就可以進行動畫過渡了,下面簡單的實現一下在DOM
加載時便取得實際高度進行動畫實現。
<!DOCTYPE html>
<html>
<head>
<title>展開動畫</title>
<style type="text/css">
.page{
width: 200px;
padding: 10px 20px;
border: 1px solid #eee;
}
.container {
overflow: hidden;
}
.container > .options{
transition: all .5s;
}
.container > .btn{
color: #4C98F7;
cursor: pointer;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="page">
<div class="container">
<div class="btn" onclick="operate(this)" unfold="1">展開</div>
<div class="options">
<div class="option">選項1</div>
<div class="option">選項2</div>
<div class="option">選項3</div>
<div class="option">選項4</div>
<div class="option">選項5</div>
<div class="option">選項6</div>
<div class="option">選項7</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
(function init(win, doc){
const optionsNode = document.querySelector(".container > .options");
optionsNode.setAttribute("real-height", win.getComputedStyle(optionsNode).height);
optionsNode.style.height = "0px";
})(window, document);
function operate(btn){
const optionsNode = document.querySelector(".container > .options");
const unfold = btn.getAttribute("unfold");
const realHeight = optionsNode.getAttribute("real-height");
if(unfold && unfold==="1"){
btn.innerText = "收縮";
optionsNode.style.height = realHeight;
}else{
btn.innerText = "展開";
optionsNode.style.height = "0px";
}
btn.setAttribute("unfold", unfold === "0" ? "1" : "0");
}
</script>
</html>
translateY
還有一種常用實現動畫的方式,即首先將外層元素沒有動畫過渡的形式直接展開,再將選項加入動畫緩慢下落,通常利用transform: translateY();
去實現這個緩慢下降的動畫,在微信的WEUI
小程序組件庫的首頁就是采用這種實現方式。
<!DOCTYPE html>
<html>
<head>
<title>展開動畫</title>
<style type="text/css">
.page{
width: 200px;
padding: 10px 20px;
border: 1px solid #eee;
}
.container, .options-container {
overflow: hidden;
}
.options-container{
height: 0;
}
.container .options{
transition: all .5s;
transform: translateY(-100%);
}
.container .unfold{
transform: translateY(0);
}
.container > .btn{
color: #4C98F7;
cursor: pointer;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="page">
<div class="container">
<div class="btn" onclick="operate(this)" unfold="1">展開</div>
<div class="options-container">
<div class="options">
<div class="option">選項1</div>
<div class="option">選項2</div>
<div class="option">選項3</div>
<div class="option">選項4</div>
<div class="option">選項5</div>
<div class="option">選項6</div>
<div class="option">選項7</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
function operate(btn){
const optionsNode = document.querySelector(".container .options");
const optionsContainer = document.querySelector(".options-container");
const unfold = btn.getAttribute("unfold");
if(unfold && unfold==="1"){
btn.innerText = "收縮";
optionsNode.classList.add("unfold");
optionsContainer.style.height = "auto";
}else{
btn.innerText = "展開";
optionsNode.classList.remove("unfold");
optionsContainer.style.height = "0px";
}
btn.setAttribute("unfold", unfold === "0" ? "1" : "0");
}
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
http://www.111com.net/wy/192615.htm
https://zhuanlan.zhihu.com/p/52582555
https://cloud.tencent.com/developer/article/1499033