這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 不定高度展開收起動畫 最近在做需求的時候,遇見了元素高度展開收起的動畫需求,一開始是想到了使用 transition: all .3s; 來做動畫效果,在固定高度的情況下,transition 動畫很好使,滿足了需求,但是如果要考慮之後可 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
不定高度展開收起動畫
最近在做需求的時候,遇見了元素高度展開收起的動畫需求,一開始是想到了使用 transition: all .3s;
來做動畫效果,在固定高度的情況下,transition
動畫很好使,滿足了需求,但是如果要考慮之後可能還會有更改的情況下,如果每次都是用固定高度來做動畫,會顯得很繁瑣,也很呆,就想到了使用 height: auto;
來做高度動畫,但是,眾所周知,高度設置成 auto
時是不會觸發 transition
動畫的
.container { height: 0; background-color: #ccc; overflow: hidden; transition: all .3s; } .container:hover { height: 1000px; }
效果如圖,不能滿足動畫的要求
在一番查找實驗之後,目前發現瞭如下幾種方法:
1. max-height
最大高度
transition
動畫可以響應 max-height
.container { max-height: 0; background-color: #ccc; overflow: hidden; transition: all .3s; } .container:hover { max-height: 1000px; }
但是使用 max-height
做動畫有一個問題,如果設置的最大高度越大,但是實際高度確與最大高度相差甚遠,那麼整體的動畫速度就會非常快,動畫的時間只會是 實際高度 / 最大高度 * 動畫時間,因為展開動畫原本預期高度是設置的最大高度,所以整體時間是以最大高度完全展開所用時間來進行的,但是當到達實際高度的時候動畫就停止了,所以最終動畫時間會與期望時間相差甚遠。
max-height
方法做動畫也是一個好方法,如果能夠確定大致高度的話,使用此方法是最簡單也是最快的方法,但是如果不能確定大致高度或整體高度經常變化的話,可以考慮其他方法。
2. grid
動畫
grid
網格佈局,是一種較新的佈局,號稱是最強大的佈局方案。grid
佈局不是本文的介紹重點,並且較為複雜,如果感興趣的話,可以參考相關文章,如:
grid
佈局中可以使用 fr
單位,fr
單位是支持過度動畫的(0fr=>1fr
),將 grid
佈局下的子元素,初始設置為0fr
,在 :hover
狀態下設置為 1fr
,就能夠實現不定高度動畫效果,但是如果子元素有內容,在設置 0fr
的時候,會被其內容撐開,所以要給子元素添加 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; }
如果想要實現帶有基礎高度的展開收起動畫,我們可以設置 min-height: 100px;
.container .child { min-height: 100px; }
雖然此時實現了帶有基礎高度的動畫效果,但是可以看到,如果我把 transition: all 3s;
的動畫時間設置的較大,就可以看出來,雖然有基礎高度,但是整個動畫的效果還是要實現 0fr
到 1fr
的動畫效果,基礎高度部分不會有動畫效果,這也算是一個小的缺點,如果動畫時間較短並且基礎高度也不大的話,可以這樣使用,並不會有太大的影響效果。
但是 grid
佈局有可能有相容性的問題,grid-template-rows
動畫的支持可能有相容性問題
3. js 控制動畫
寫這篇文章的原因是因為在看項目代碼的時候看見了 $(.xx).slideDown()
方法實現了元素的下滑動畫,覺得很不錯,想學習一下怎麼實現的,實現效果如下:
但是在看元素的時候卻只能看見下麵的樣子,發現不是 css 實現的,是使用 js 不斷改變元素的高度來實現的:
我又去看了一下 ant-design
的 Menu
組件,通過觀察元素,發現其也是不斷改變高度來實現的(Ps: 我並沒有去看源碼,如果有誤,多謝指正)。
實現
首先要思考整個實現的思路
展開的時候,元素從無到有,我們應該首先獲取整個元素的實際高度使用 offsetHeight
來獲取,獲取到整體高度後就要計算每一次增加或者減少的高度,通過定時器不斷增加或減少元素的高度,直到到了最大高度或 0 後停止
展開
const element = document.getElementById('container'); let expandTimer = null; let offsetHeight = 0; // 獲取元素總高度 element.style.display = 'block'; let height = 0; // 先將 display 設置為 block,獲取到的 offsetHeight 才是正確的高度,之後才能設置元素高度 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);
現在能夠正確展開收起,但是我們在展開收起的時候也會有相反的操作,比如滑鼠進入元素展開離開收起,在展開的過程中滑鼠離開了,我們應該立刻就將元素收起,而不是等動畫結束後在進行下一個動畫,所以要將展開收起操作合併操作才可以
const element = document.getElementById('container'); let expandTimer = null; let collapseTimer = null; // 我認為在一次展開後,直到收起完成之前,這個元素的實際高度都不應該發生變化,但是可以在下一次展開時發生變化,所以在展開時會進行賦值,在收起完成時會將此值清空 let offsetHeight = 0; let stepHeight = 0; const handleClick = () => { // 如果當前 expandTimer 值存在,就標識當前是正在展開或已經展開,接下來要進行的是收起操作 if (expandTimer) { clearInterval(expandTimer); expandTimer = null; // 收起時的初始高度是元素的當前實際高度,即使是元素在展開動畫過程中,也要從當前元素高度進行收起動畫 let height = element.offsetHeight; collapseTimer = setInterval(() => { height -= stepHeight; if (height <= 0) { // 高度小於等於 0 代表動畫完成,將數據進行重置 clearInterval(collapseTimer); offsetHeight = 0; // 要將元素的高度置為 null,不然會影響下一次展開時獲取正確的高度 element.style.height = null; // display 設為 null,要將元素隱藏 element.style.display = 'none'; return; } element.style.height = height + 'px'; }, 10); } else { clearInterval(collapseTimer); collapseTimer = null; // 獲取元素總高度 element.style.display = 'block'; let height = 0; 如果當前沒有 offsetHeight 就要重新獲取 if (!offsetHeight) { offsetHeight = element.offsetHeight; // 每一次給元素添加或減少的高度,除以 30 是自己設定的,跟下麵定時器的每次間隔時間一起控制整個高度動畫的時長,也可以給函數添加第二個時間參數,可以自由控制動畫時間 stepHeight = offsetHeight / 30; } else { // 如果有 offsetHeight 就代表正在進行收起動畫,應該從收起動畫的當前高度進行展開動畫 height = element.offsetHeight; } element.style.height = height + 'px'; expandTimer = setInterval(() => { height += stepHeight; if (height >= offsetHeight) { // 當前高度如果已經到了元素的實際高度,就要清除定時器 clearInterval(expandTimer); // 將 expandTimer 設為 1 是因為當前是以 expandTimer 判斷是否正在或已經進行了展開動畫,所以要在完成是設為 1,在收起動畫的開始時會將值設為 null expandTimer = 1; element.style = null; return; } element.style.height = height + 'px'; }, 10); } };
最終實現效果
4. 總結
上面的三種方式實現效果都是各有千秋 - max-height
方法實現是最簡單,也是效率最高的方式,但是也有動畫時間不定的缺陷 - grid
方式實現比 max-height
稍微複雜一些,但是整體效果要比 max-height
更好,但是目前瀏覽器的支持方面可能有所不足,如果有低版本的相容性要求的話,還是不能使用 - js
方式整體最複雜,但是卻沒有上面兩種方式的缺陷與問題,使用範圍也更廣泛,但是是 js
的實現方式,性能肯定是不如 css
,雖然不如,但是由於整體操作也較為簡單,所以也不會有什麼性能問題
幾種方法的取捨全看個人需求了。
如果有滑鼠進入展開,離開收起的操作,可以配合使用 onmouseover
onmouseout
事件來監聽滑鼠的進入離開。
其他還有像是 transform: scale(0);
的實現也是可以,但是整體動畫效果就是一個縮小的效果,而且元素還會有占位問題,如果沒什麼要求也是可以使用的。