Collapse組件(一) collapse過渡動畫

Collapse組件在作內容摺疊與展開顯示的時候,仍是用到不少的。這一個組件的內容相對於BadgeTag組件更多一點,因此打算分紅三篇文章來說。javascript

clipboard.png

高度不固定的css動畫

第一篇先來說一講對於高度不肯定的元素,怎麼作高度的展開動畫效果。css

看一下大佬對transition爲何不能對height:auto實現過渡動畫的解釋:html

clipboard.png

這裏貼一個css trick上對於這種問題的解決方案:Using CSS Transitions on Auto Dimensionsjava

說一下在element-ui中的實現思路吧:css3

  1. 經過transition的鉤子函數 + render渲染函數,咱們來自定義一個過渡效果,由JS來掌控變化的值
  2. 展開時,height從 0 逐步增大到 scrollHeight
  3. 收縮時,height從 scrollHeight 逐步減少到 0

過渡動畫最終實現效果

https://jsfiddle.net/huang_ju...編程

scrollHeight, paddingSize

經過自適應文本高度的輸入框那篇文章,咱們已經瞭解到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.重排何時發生
每次重排,必然會致使重繪,那麼,重排會在哪些狀況下發生?

  1. 添加或者刪除可見的DOM元素
  2. 元素位置改變
  3. 元素尺寸改變
  4. 元素內容改變(例如:一個文本被另外一個不一樣尺寸的圖片替代)
  5. 頁面渲染初始化(這個沒法避免)
  6. 瀏覽器窗口尺寸改變

能夠總結爲,當一個節點的位置、大小、內容發生改變,或者增刪節點的狀況下會發生重排。

3.渲染樹變化的排隊和刷新
瀏覽器會把屢次對節點的修改「保存」起來(大多數瀏覽器經過隊列化修改並批量執行來優化重排過程),最終一次完成!可是,有些時候你可能會(常常是不知不覺)強制刷新隊列並要求計劃任務當即執行。獲取佈局信息的操做會致使隊列刷新,好比:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight(節點位置)
  2. scrollTop, scrollLeft, scrollWidth, scrollHeight(節點位置、大小)
  3. clientTop, clientLeft, clientWidth, clientHeight(節點大小)
  4. getComputedStyle() (currentStyle in IE)(節點樣式)

能夠這麼理解,要獲取這些值,瀏覽器就須要把前面緩存的重排操做先給執行了,才能計算最新的,正確的節點信息。而這種中斷,打斷了瀏覽器自身對於重排的優化,是須要咱們避免的。

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就起做用了。

總結

經過過渡效果的實現:

  • 學到了一種新的思路,若是利用render+transition鉤子函數,本身生成一個JS掌控的transition
  • 重排與重繪對於性能的影響,以及編程中須要注意的點

參考文章:
1.Using CSS Transitions on Auto Dimensions
2.css3怎麼實現高度從固定到自動的過渡動畫?
3.高性能JavaScript 重排與重繪

相關文章
相關標籤/搜索