star back image
people4
電飾 電飾
moon
men

アコーディオンのアニメーション(引き出しタイプ)

BLOG WEBログ
読了約:30分

ホームページ内にアコーディオンの設置は結構、あると思います。

figmaのPrototypeでデザイナーさんが、こうしてくれとアニメーションを見せてくれました。なんかいつものアコーディオンとちょっと違う、普通じゃない動きをしています。

これは「引き出し」みたいだ。。

引き出し?タンスの?

簡単にできると思っていたのですが、いつもと違う。。

何が違うのですかね。

言葉だと説明が難しいので図を作成。

動作イメージを作成

健忘録として記録するものとします。

【共有】サンプルコード

通常のアコーディオンとは違う「引き出し型」の動作サンプルです。
実装が少し面倒で、CSSとJSの連携が必要になります。

以下にサンプル表示を作成です。押せます。

  • 肛門診療を受けるか悩んでいます。どのように相談すればいいですか?
    まずは当院の公式LINEからご連絡下さい。お電話での予約は承っておりません。専門のカウンセラーが丁寧にご相談に応じます。
  • 初診に掛かる時間はどれくらいですか?
    医師との診察時間は20分程になります。お時間に余裕を持ってお越しください。
  • 予約なしで来院できますか?
    当院は完全予約制となります。事前にLINEまたはお電話でご予約をお願いいたします。
  • 友人や付き添いと一緒に来院してもいいですか?
    ご同伴は原則お断りしております。プライバシー保護の観点から、お一人でのご来院をお願いしております。
  • 当日、施術はカウンセリングと同時にできますか?
    カウンセリングと手術は基本的には別日でご案内しております。症状やお薬の内容により可否を判断します。詳しくはカウンセリング時にご相談ください。

確かに「机の引き出しを引いた感じ」がしますよ。

要件は以下です。

  •  机の引き出しのような動き(内容物もついてくる)
  • コンテンツが最初からサイズを持っている(高さが0にならない)
  • max-heightとtransformが完全に同期
  • どんな長さのコンテンツでも対応
<!-- FAQ 引き出し型アコーディオン -->
<style>
  /* ===== FAQ アコーディオン ===== */
  .faq-accordion-wrapper {
    background-color: #FAFAFA;
    padding: 40px 20px;
    font-family: "Noto Sans JP", sans-serif;
  }

  .faq-accordion-list {
    max-width: 800px;
    margin: 0 auto;
    list-style: none;
    padding: 0;
  }

  .faq-accordion-item {
    background: #fff;
    margin-bottom: 0;
    list-style: none;
  }

  /* マーカーを完全に消す */
  .faq-accordion-item::marker {
    display: none;
    content: none;
  }

  .faq-question {
    position: relative;
    padding: 24px 48px 24px 24px;
    font-size: 16px;
    font-weight: 500;
    line-height: 1.6;
    color: #1A1311;
    cursor: pointer;
    border-bottom: 1px solid #F2EFEB;
    transition: background-color 0.2s ease;
  }

  .faq-question:hover {
    background-color: #f9f9f9;
  }

  /* 矢印アイコン */
  .faq-question::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 24px;
    width: 10px;
    height: 10px;
    border-right: 2px solid #1A1311;
    border-bottom: 2px solid #1A1311;
    transform: translateY(-50%) rotate(45deg);
    transition: transform 0.3s ease;
  }

  .faq-question.open::after {
    transform: translateY(-25%) rotate(-135deg);
  }

  .faq-question.open {
    border-bottom: 0;
  }

  /* アコーディオンコンテナ */
  .faq-answer-container {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.35s ease;
  }

  /* 答えの内容 */
  .faq-answer {
    background: #F2F2F2;
    padding: 20px 24px 32px;
    font-size: 14px;
    font-weight: 400;
    line-height: 2;
    color: #1A1311;
    transform: translateY(-100%);
    transition: transform 0.35s ease;
    will-change: transform;
  }

  /* 開いた状態 */
  .faq-question.open + .faq-answer-container {
    max-height: var(--actual-height, 500px);
  }

  .faq-question.open + .faq-answer-container .faq-answer {
    transform: translateY(0);
  }

  /* レスポンシブ */
  @media (max-width: 768px) {
    .faq-accordion-wrapper {
      padding: 24px 16px;
    }

    .faq-question {
      padding: 20px 40px 20px 16px;
      font-size: 14px;
    }

    .faq-question::after {
      right: 16px;
    }

    .faq-answer {
      padding: 16px 16px 24px;
      font-size: 13px;
    }
  }
</style>

<div class="faq-accordion-wrapper">
  <ul class="faq-accordion-list">
    
    <li class="faq-accordion-item">
      <div class="faq-question">
        肛門診療を受けるか悩んでいます。どのように相談すればいいですか?
      </div>
      <div class="faq-answer-container">
        <div class="faq-answer">
          まずは当院の公式LINEからご連絡下さい。お電話での予約は承っておりません。専門のカウンセラーが丁寧にご相談に応じます。
        </div>
      </div>
    </li>

    <li class="faq-accordion-item">
      <div class="faq-question">
        初診に掛かる時間はどれくらいですか?
      </div>
      <div class="faq-answer-container">
        <div class="faq-answer">
          医師との診察時間は20分程になります。お時間に余裕を持ってお越しください。
        </div>
      </div>
    </li>

    <li class="faq-accordion-item">
      <div class="faq-question">
        予約なしで来院できますか?
      </div>
      <div class="faq-answer-container">
        <div class="faq-answer">
          当院は完全予約制となります。事前にLINEまたはお電話でご予約をお願いいたします。
        </div>
      </div>
    </li>

    <li class="faq-accordion-item">
      <div class="faq-question">
        友人や付き添いと一緒に来院してもいいですか?
      </div>
      <div class="faq-answer-container">
        <div class="faq-answer">
          ご同伴は原則お断りしております。プライバシー保護の観点から、お一人でのご来院をお願いしております。
        </div>
      </div>
    </li>

    <li class="faq-accordion-item">
      <div class="faq-question">
        当日、施術はカウンセリングと同時にできますか?
      </div>
      <div class="faq-answer-container">
        <div class="faq-answer">
          カウンセリングと手術は基本的には別日でご案内しております。症状やお薬の内容により可否を判断します。詳しくはカウンセリング時にご相談ください。
        </div>
      </div>
    </li>

  </ul>
</div>

<script>
(function() {
  'use strict';
  
  // ページ読み込み時に実行
  function initFAQ() {
    const containers = document.querySelectorAll('.faq-answer-container');
    
    // 各アコーディオンの実際の高さを測定
    containers.forEach(function(container) {
      const answer = container.querySelector('.faq-answer');
      if (answer) {
        const actualHeight = answer.scrollHeight;
        container.style.setProperty('--actual-height', actualHeight + 'px');
      }
    });
    
    // クリックイベント
    const questions = document.querySelectorAll('.faq-question');
    questions.forEach(function(question) {
      question.addEventListener('click', function() {
        // openクラスをトグル
        question.classList.toggle('open');
      });
    });
  }
  
  // DOMが読み込まれたら実行
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initFAQ);
  } else {
    initFAQ();
  }
})();
</script>

このままカスタムHTMLに放り込んで使えます。

ポイントは以下です。

ポイント1: transform: translateY(-100%) で引き出し効果
通常のアコーディオンは高さが0→100%に変化しますが、この実装では最初から高さを持ったまま、位置を移動させます。

/* 閉じている状態 */
.faq-answer {
transform: translateY(-100%); /* 自分の高さ分、上に移動 */
/* ↑ ボタンの後ろに隠れる */
}

/* 開いた状態 */
.faq-question.open + .faq-answer-container .faq-answer {
transform: translateY(0); /* 元の位置に戻る */
/* ↑ 引き出しのように下にスライド */
}

ポイント2: var(–actual-height) でJSとCSSの連携
max-height: 500px のような固定値だと、実際のコンテンツが150pxの場合、アニメーションが60%で完了してしまい、transformとずれます。

JavaScript側:
// 各コンテンツの実際の高さを測定
const answer = container.querySelector('.faq-answer');
const actualHeight = answer.scrollHeight; // 例: 150px

// CSS変数としてセット
container.style.setProperty('--actual-height', actualHeight + 'px');
// → --actual-height: 150px;


CSS側:
.faq-question.open + .faq-answer-container {
max-height: var(--actual-height, 500px);
/* ↑ JS計測値を使用(フォールバック: 500px)*/
}

ポイント3: will-change: transform でパフォーマンス最適
ブラウザに「このプロパティがアニメーションするよ」と事前に伝えることで、スムーズな描画を実現。

.faq-answer {
transform: translateY(-100%);
transition: transform 0.35s ease;
will-change: transform; /* ← パフォーマンス最適化 */
}

期待:
・GPUアクセラレーションを有効化
・描画の最適化でもたつきを軽減
・60fps のスムーズなアニメーション

ポイント4: 同じduration & easingで完全同期
2つのアニメーションを完全に同期させるため、全く同じ設定にします。

.faq-answer-container {
max-height: 0;
transition: max-height 0.35s ease;
/* ↑duration ↑easing */
}

.faq-answer {
transform: translateY(-100%);
transition: transform 0.35s ease;
/* ↑duration ↑easing */
}

設定:なんとか同期したい。
max-height: 0.35s ease
transform: 0.35s ease

う〜ん。ヘぇ〜。

【まとめ】3つの組み合わせです

ポイントを短くまとめるとこんな感じでしょうか。

/* 1. コンテナの高さアニメーション */
.faq-answer-container {
max-height: 0;
transition: max-height 0.35s ease;
}

/* 2. コンテンツの位置アニメーション */
.faq-answer {
transform: translateY(-100%); /* 引き出し効果 */
transition: transform 0.35s ease;
will-change: transform; /* 最適化 */
}

/* 3. 開いた状態(JS計測値を使用)*/
.faq-question.open + .faq-answer-container {
max-height: var(--actual-height); /* JS連携 */
}
// 4. JavaScript で実際の高さを測定
const actualHeight = answer.scrollHeight;
container.style.setProperty('--actual-height', actualHeight + 'px');

これらが組み合わさって、引き出し型のアコーディオンを作成しています。

大丈夫でしょうか。

いろんな方法がまだまだあるかもしれません。

しかし、以上です。

【AI】イラストを描いてもらった

今回の記事のキャッチ画像で使わせてもらいます「Google ImageFX」で作成した画像です。誰でもgoogleアカウントでログインして使えます。

この記事にピッタリなイラストのための考えたリクエストは、「昭和の日本の子供部屋にある机の引き出しが開いて、光が溢れる。一緒にその中から狐のお面(ハイテク装飾)をつけた和服の男性(未来人)が楽器のアコーディオン(ハイテク)を広げ演奏しながら出てくる。驚くネズミ。未来人をダッチアングルのサイバーパンク風で。」です。

アコーディオン攻めですね。

星間旅路のメロディ

「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」

「この電波はどこの星からきたのだろうか。」

水の豊かな惑星の街の風景。
生命も輝いていた、そんな雰囲気がします。