Collapse組件在作內容摺疊與展開顯示的時候,仍是用到不少的。這一個組件的內容相對於Badge和Tag組件更多一點,因此打算分紅三篇文章來說。javascript
第一篇先來說一講對於高度不肯定的元素,怎麼作高度的展開動畫效果。css
看一下大佬對transition爲何不能對height:auto實現過渡動畫
的解釋:html
這裏貼一個css trick上對於這種問題的解決方案:Using CSS Transitions on Auto Dimensionsjava
說一下在element-ui中的實現思路吧:css3
0 逐步增大到 scrollHeight
scrollHeight 逐步減少到 0
https://jsfiddle.net/huang_ju...編程
經過自適應文本高度的輸入框那篇文章,咱們已經瞭解到scrollHeight的高度是包含了padding的,因此在這裏的動畫時,須要對padding-top,padding-bottom,height三個屬性都作變化。
element-ui
其次,對於展開時高度的值,須要scrollHeight - paddingSize瀏覽器
let Transition = { beforeEnter(el){ if(!el.dataset) el.dataset = {}; let styles = window.getComputedStyle(el); // 記錄展開前的屬性值 el.dataset.oldOverflow = styles.getPropertyValue('overflow'); el.dataset.oldPaddingTop = styles.getPropertyValue('padding-top'); el.dataset.oldPaddingBottom = styles.getPropertyValue('padding-bottom'); // 這三個都爲0,scrollHeight的高度就是真實的內容高度了 el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; el.classList.add('collapse-transition'); el.style.overflow = 'hidden'; }, enter(el) { if(el.scrollHeight !== 0) { // 動畫過程當中,逐漸增大到展開前應占的高度值 el.style.height = el.scrollHeight + 'px'; el.style.paddingTop = el.dataset.oldPaddingTop; el.style.paddingBottom = el.dataset.oldPaddingBottom; } }, afterEnter(el) { el.classList.remove('collapse-transition'); el.style.height = ''; el.style.overflow = el.dataset.oldOverflow; } // .... }
觀察jsfiddle的demo示例代碼,會發現這句緩存
let Transition = { // ... leave(el) { if (el.scrollHeight !== 0) { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; } } // ... }
爲何要加el.scrollHeight !== 0的判斷呢?
函數
試一下,若是不加這個判斷,直接變化height,paddingTop,paddingBottom的值到0,這個時候,收縮時並不會有過渡動畫,元素立刻就消失了。
咱們能夠替換一下上面的代碼
let Transition = { // ... leave(el) { setTimeout(() => { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; }, 20) } // ... }
這時候,收縮也會有過渡動畫。但當咱們嘗試將延遲時間改成0的時候,並沒有效,延遲時間須要大於0纔會有過渡效果。因此,這個過渡動畫彷佛和執行隊列就沒什麼關聯了。
接下來,咱們把目標鎖定在了重繪和重排上。
着重看一下大佬的這篇文章:高性能JavaScript 重排與重繪
1.什麼是重排和重繪
瀏覽器解析頁面生成DOM樹
和渲染樹
,DOM樹表示節點結構,渲染樹表示節點如何顯示。
DOM樹節點的屬性發生變化時,瀏覽器可能會從新計算去繪製渲染樹,這個過程叫重排
,渲染樹映射到屏幕上顯示的過程就叫重繪
。
2.重排何時發生每次重排,必然會致使重繪,那麼,重排會在哪些狀況下發生?
能夠總結爲,當一個節點的位置、大小、內容發生改變,或者增刪節點的狀況下會發生重排。
3.渲染樹變化的排隊和刷新瀏覽器會把屢次對節點的修改「保存」起來(大多數瀏覽器經過隊列化修改並批量執行來優化重排過程),最終一次完成!可是,有些時候你可能會(常常是不知不覺)強制刷新隊列並要求計劃任務當即執行
。獲取佈局信息的操做會致使隊列刷新,好比:
能夠這麼理解,要獲取這些值,瀏覽器就須要把前面緩存的重排操做先給執行了,才能計算最新的,正確的節點信息。而這種中斷,打斷了瀏覽器自身對於重排的優化,是須要咱們避免的。
4.最小化重排和重繪
雖然瀏覽器對重排進行了優化,但咱們不經意的操做會打斷這種優化。因此,修改樣式信息時,儘可能集中操做
5.fragment元素的應用前面說了,重排大多數都是因爲節點位置、大小,增刪形成的。節點位置,大小的改變咱們能夠經過集中操做來規範以優化性能。而對於節點的增刪就能夠經過fragment元素,來避免頻繁的重排和重繪了
。
儘可能不要在佈局信息改變時作查詢(會致使渲染隊列強制刷新)
同一個DOM的多個屬性改變能夠寫在一塊兒(減小DOM訪問,同時把強制渲染隊列刷新的風險降爲0)
若是要批量添加DOM,能夠先讓元素脫離文檔流,操做完後再帶入文檔流,這樣只會觸發一次重排(fragment元素的應用)
咱們再看一下出問題的這段代碼,若是不加scrollHeight的時候:
let Transition = { // ... beforeLeave(el) { // ... el.style.height = el.scrollHeight - parseFloat(el.dataset.oldPaddingTop) - parseFloat(el.dataset.oldPaddingBottom) + 'px'; el.style.overflow = 'hidden'; el.classList.add('collapse-transition'); //var tmp = el.offsetTop; }, leave(el) { el.style.height = 0; el.style.paddingTop = 0; el.style.paddingBottom = 0; }, afterLeave(el) { //... } }
咱們進行了一系列操做,瀏覽器緩存了這幾回的重排。等到主線程結束,開始渲染的時候,直接就往下一直渲染到了height:0;這句。因此就沒有了過渡動畫。
而加上大於零的定時器,應該是因爲,1.先運行js主線程。2.執行重排重繪。3.執行定時回調,變化height的值
能夠試下,講註釋的這行代碼(var tmp = el.offsetTop;)加入,也有過渡動畫效果。
結合上面對重排和重繪的學習,能夠很容易的理解到:這一句代碼強制刷新了重排的隊列。讓前面設置的屬性,增長的過渡效果的類名。讓它先渲染好,而後運行到leave函數的時候,再變化高度值,transition就起做用了。
經過過渡效果的實現:
參考文章:
1.Using CSS Transitions on Auto Dimensions
2.css3怎麼實現高度從固定到自動的過渡動畫?
3.高性能JavaScript 重排與重繪