[譯] 如何實現隱藏元素的過渡效果?

原文連接:Transitioning Hidden Elements,Paul Hebertjavascript

若是你在設置了 hidden 屬性或者 display: none 聲明的元素上使用 CSS 過渡(transition),這是個很差實現的效果。我遇到這個問題不少次了,最後決定編寫一個 npm 軟件包 提供一個可重用的解決方案。css

問題

有不少種隱藏元素的方法。好比使用 visibilityopacitytransformposition 甚至是 clip-path,但這些屬性有時並不能知足咱們的預期效果。元素使用 visibility: hidden 隱藏後,原來佔據的空間顯示爲空白,並且上述這些屬性的隱藏效果對屏幕閱讀器而言都是可見的、可以解析到的。html

使用 hidden 屬性display: none 聲明隱藏的元素就沒有這個問題。但這兩種隱藏的元素方式還有一個明顯的缺點:就是咱們不能對它們應用 transtion 過渡效果。那麼,咱們該怎樣解決這個問題呢?我最終編寫了一個 npm 軟件包 來處理這個問題,並在這個過程當中學到了很多關於文檔流、transtion 事件方面的知識。下面跟你們分享下。java

顯示和隱藏 .drawer

假設網頁裏有一個 .drawer 元素,它使用 CSS transform 屬性設置偏移,移動到視口以外,隨後使用 hidden 屬性從文檔流中移除。git

(本文裏的全部例子都是使用 hidden 屬性隱藏元素,這種隱藏方式與 display: none 效果同樣。npm 包 同時支持這兩種方式的設置。)github

<div class="drawer" hidden>Hello World!</div>

<style> .drawer { transform: translateX(100%); transition: transform ease-out 0.3s; } .drawer.is-open { transform: translateX(0); } </style>
複製代碼

下面展現了咱們最終要達到的效果(點擊這裏 查看 demo):npm

GIF.gif

咱們如何實現這種方式的滑入滑出效果,同時還能將隱藏元素從文檔流中移除呢?瀏覽器

繼續看。ide

顯示 .drawer

因爲 hidden 元素不在文檔流中,所以是不能對它使用過渡效果的。但能夠這樣作:刪除元素 hidden 屬性後,強制文檔從新排版(reflow),這樣就能夠對 CSS 屬性作 transition 效果了。同時,該須要一點 JS 代碼協助完成這個工做。函數

const drawer = document.querySelector('.drawer');

function show() {
  drawer.removeAttribute('hidden');

  /** * 強制瀏覽器重繪(re-paint),而後瀏覽器就能知道 * 元素不是 `hidden` 的了,這樣 transition 也就起做用了。 */
  const reflow = element.offsetHeight;

  // 觸發 CSS transition 效果
  drawer.classList.add('is-open');
}
複製代碼

隱藏 .drawer

到目前爲止,咱們知道如何顯示 .drawer 了,但怎樣實現元素隱藏時的過渡效果呢?這就要保證隱藏元素再一次應用了 hidden 屬性。但問題是,若是咱們在元素隱藏的那一時刻,就應用 hidden,是看不見過渡效果的。相反,咱們應該先等待過渡效果結束後,再 hidden 元素。有兩種方式能夠實現:transitionend 和 setTimeout

transitionend 事件

CSS 的 transition 效果結束時會觸發 transitionend 事件。對應到咱們的示例中,經過刪除 .is-open 這個類名,就能自動觸發過渡效果發生。經過利用這個事件回調,咱們就能在過渡完成後,爲元素添加 hidden 屬性。

同時,還要確保過渡完成後要移除事件監聽器,不然當元素再次顯示時,還會觸發一次以前綁定的事件回調,就有問題了。爲此,咱們能夠將監聽器(listener)做爲一個變量存儲,並在過渡完成後將其移除:

const listener = () => {
  // 3) 爲元素添加 hidden 屬性
  drawer.setAttribute('hidden', true);
  // 4) 最後移除元素的事件監聽器
  drawer.removeEventListener('transitionend', listener);
};
// 隱藏元素
function hide() {
  // 1) 先刪除 .is-open 這個類名
  drawer.classList.remove('is-open');
  // 2) 在過渡效果結束後,進入 3)
  drawer.addEventListener('transitionend', listener);
}
複製代碼

還要注意的是,中間可能還會發生這種操做:當前的 .drawer 正在隱藏、但尚未徹底隱藏的時候,又再次點擊顯示。對於這種狀況,咱們還須要在上述的 show() 函數中手動再移除一次事件監聽器:

function show() {
  drawer.removeEventListener('transitionend', listener);

  /* ... */
}
複製代碼

transitionend 是冒泡事件

transitionend 是冒泡事件,所以使用 transitionend 事件時,有一個邊緣狀況須要考慮:在 DOM 中,事件冒泡是從子元素傳遞到祖先元素的。若是 .drawer 元素內部包含具備過渡效果的子元素,那麼就會引起問題。

若是一個子元素的過渡效果先於 .drawer 元素完成,那麼因爲事件冒泡,該元素上的 transitionend 事件監聽器會提早觸發。

爲了不這個問題的發生,能夠在事件監聽器中檢查當前觸發 transitionend 事件的目標元素是不是 .drawer。是的話,在再執行以前的處理邏輯:

const listener = e => {
  // 只有在目標元素是 .drawer 的狀況下,在再執行以前的處理邏輯
  if(e.target === drawer) {
    drawer.setAttribute('hidden', true);
    drawer.removeEventListener('transitionend', listener);
  }
};
複製代碼

使用 setTimeout 等待

使用 transitionend 事件有一個侷限性,就是隻能處理一個元素的過渡場景。若是要實現的是下面這種涉及到多個元素過渡效果的 交錯動畫 場景,就不適應了。

GIF.gif

點擊這裏 查看 demo

須要實現的功能:點擊右上角按鈕實現側邊菜單帶有過渡效果的顯示和隱藏;同時點擊菜單子連接也會觸發菜單的隱藏過渡效果。這須要咱們等待全部子元素的過渡效果完成,才能將 hidden 屬性添加給 .drawer

最開始的處理方式,是強制等待全部子元素的 transitionend 事件都觸發後,再作處理。可是,若是用戶是在進行快速切換,那麼就可能有問題了:有些子元素就沒有發生過渡,那麼這些元素也永遠不會觸發 transitionend 事件。

爲了解決這個問題,咱們選擇使用 setTimeout,而不是去監聽 transitionend 事件:

function hide() {
  element.classList.remove(visibleClass);
  const timeoutDuration = 200;
  const timeout = setTimeout(() => {
    element.setAttribute('hidden', true);
  }, timeoutDuration);
}
複製代碼

除此以外,爲了不前面發生的(菜單正在隱藏時就點擊顯示)場景,不要忘記在 show() 中手動清除定時器:

function show() {
  if (this.timeout) {
    clearTimeout(this.timeout);
  }

  /* ... */
}
複製代碼

我最終建立的 npm 包中,同時提供對 timeout  和 transitionend 選項的支持, 方便你們根據須要使用。

使用 npm 包

通過上面的分析,你們可以知道隱藏元素的過渡效果實現起來仍是稍微有點複雜的。個人 npm 包將本例所提到到兩類場景所使用的代碼,都包裝到了 transitionHiddenElement 模塊中。

使用方式以下:

import { transitionHiddenElement } from '@cloudfour/transition-hidden-element';

const drawerTransitioner = transitionHiddenElement({
  element: document.querySelector('.drawer'),
  visibleClass: 'is-open',
});

// 使用
drawerTransitioner.show();
drawerTransitioner.hide();
drawerTransitioner.toggle();
複製代碼

我但願這個方案對你們的項目有所幫助。你能夠從 npm 獲取或者在 GitHub 上查看源代碼!

(正文完)


廣告時間(長期有效)

我有一位好朋友開了一間貓舍,在此幫她宣傳一下。如今貓舍裏養的都是布偶貓。若是你也是個愛貓人士而且有須要的話,不妨掃一掃她的【閒魚】二維碼。不買也沒關係,看看也行。

(完)

相關文章
相關標籤/搜索