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


アコーディオン攻めですね。
星間旅路のメロディ
「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」
「この電波はどこの星からきたのだろうか。」
水の豊かな惑星の街の風景。
生命も輝いていた、そんな雰囲気がします。



