Web production note

 【更新日 :

detailsとsummaryタグでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装する

Category:
css / js

アクセシビリティ対策が簡単にできるdetailsとsummaryタグでスライド系アニメーション(slideDown、slideUp、slideToggle)を実装したサンプルです。

余分なdivなどは追加せずdetailsとsummaryタグのみで動作することを目指しました。

動作サンプル

codepen

See the Pen slideToggle details & summary + Vanilla JS by web_corder (@web_walking_nak) on CodePen.

以下は最低限動作させるための設定方法です。

HTML

<details class="js-toggle">
  <summary>
    開閉ボタン
  </summary>
  開閉パネルのコンテンツ
</details>

設定

  • detailsタグに class="js-toggle" を追加。

CSS

アニメーションに関係するsyle

.js-toggle {
  overflow: hidden;
}

ボタンの表示切り替えに関係するstyle

open属性の値か開いた時に is-active が付きます。
両者には閉じるアニメーションの時の外れるタイミングに違いがあるため都合の良い方を選択してください。

  • 閉じるアニメーションを実行しながら処理したい時:is-active
  • 閉じるアニメーションが終わってから処理したい時:open属性
/* パネルが開いている時 */
/* 開→閉:クリックアクションが発火した瞬間(閉じるアニメーション終了前)にis-activeが削除される */
.js-toggle.is-active {
}
/* 開→閉:閉じるアニメーション終了後にopen属性が削除される */
.js-toggle[open] {
}

/* パネルが閉じている時 */
.js-toggle {
}

JavaScript

イージングなどアニメーションの詳細設定は環境に合わせて適宜調整してください。

//details と summary で実装する開閉アニメーション
function detailsToggle() {
  const toggleItems = document.querySelectorAll('.js-toggle');
  if(toggleItems.length === 0) {
    return;
  }

  const ACTIVE_CLASS = 'is-active';

  toggleItems.forEach(function(toggleItem) {
    const summary = toggleItem.querySelector('summary');
    if(!summary) {
      return;
    }
    
    const toggleItemStyles = getComputedStyle(toggleItem);
    const toggleItemClassList = toggleItem.classList;
    let isBusy = false;
    let startHeight,endHeight;
    summary.addEventListener('click', async function(event) {
      //デフォルトの挙動を無効化(手動でopen属性の切り替えを行うため)
      event.preventDefault();

      //アニメーションが終了するまでリクエストを無効化
      if (isBusy) {
        return;
      }
      isBusy = true;

      const isOpen = toggleItem.open;
      //クリック時に閉じていた場合は先に開く
      if(!isOpen) {
        toggleItem.open = true;
      }
      toggleItemClassList.toggle(ACTIVE_CLASS,!isOpen);

      //summaryエリアの高さを取得(閉じている状態の高さ)
      const summaryStyles = getComputedStyle(summary);
      const summaryHeight = summary.offsetHeight + parseFloat(summaryStyles.marginTop) + parseFloat(summaryStyles.marginBottom) + parseFloat(toggleItemStyles.paddingTop) + parseFloat(toggleItemStyles.paddingBottom) + parseFloat(toggleItemStyles.borderTopWidth) + parseFloat(toggleItemStyles.borderBottomWidth);

      const toggleItemHeight = toggleItem.offsetHeight;
      if(isOpen) {
        //閉じる
        startHeight = toggleItemHeight;
        endHeight = summaryHeight;
      } else {
        //開く
        startHeight = summaryHeight;
        endHeight = toggleItemHeight;
      }

      //開閉アニメーション
      await toggleItem.animate(
        {
          height: [startHeight + 'px', endHeight + 'px']
        },
        {
          duration: 400,
          easing: 'ease'
        }
      ).finished;

      //クリック時に開いていた場合はアニメーション終了後に閉じる
      if(isOpen) {
        toggleItem.open = false;
      }
      isBusy = false;
    });
    
    //ページ内検索での開閉
    toggleItem.addEventListener('toggle', function() {
      if(toggleItem.open) {
        toggleItemClassList.add(ACTIVE_CLASS);
      } else {
        toggleItemClassList.remove(ACTIVE_CLASS);
      }
    });
  });
}

関数の呼び出し

任意の場所で以下を実行し関数を呼び出してください。

detailsToggle();

関連リンク

参考リンク