GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

CSS ドロップダウン メニュー アニメーション 9選 — JS最小・CSSだけ

css ドロップダウン メニューは、クリックやホバーで隠れていた選択肢を高さや透明度の変化で見せる、CSSだけで動くナビゲーション部品です。この記事では9種類を選び、grid-template-rows・clip-path・popover属性という異なる仕組みで開閉を実装しました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並び順は効果の派手さではなく、開閉を作る仕組みの難易度です。まずheight:autoをgrid-template-rowsで擬似的に再現する基本形から始め(01)、それを不透明度と縦の移動に置き換え(02)、拡大の起点をトリガー側の角に寄せ(03)、clip-pathで切り出す形に変え(04)、項目ごとに時差をつける発展形を重ね(05)、最後に背景の塗りつぶし・ホバー遅延・popover属性・モバイル全画面シートという実際の場面への転用を並べました(06〜09)。9つとも実際の高さや座標の数値を直接書き換えるのではなく、grid-template-rows・opacity・transform・clip-pathという補間できる値だけを動かしています。 項目が下へ積み重なる向きならCSS アコーディオン アニメーション 9選が01のグリッドのトリックをさらに掘り下げ、クリックの後に浮かんで消す知らせが必要ならトースト通知 css アニメーション9選がその役割です。

01height:auto遷移

動きの内容

パネルの.menu__panelがgrid-template-rowsを0frから1frへ0.3秒かけて動かし、内側の.menu__listにoverflow: hiddenを付けることで、height:autoを直接アニメーションできないCSSの制約を回避しています。

height:auto遷移
新しいタブで開く

01 · height:auto遷移 — 計測 累積面積 29.538% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-rows0fr→1froverflow:hidden

使う場面

ナビゲーションのドロップダウンや、フィルターと兼用するアコーディオンに向いています。

.menu__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .3s ease-out;
  margin-top: 8px;
}

.menu__list {
  overflow: hidden;
  min-height: 0;
}

.menu.is-open .menu__panel {
  grid-template-rows: 1fr;
}

02フェード+スライド

どんな動きか

パネルが不透明度0から1へ変わると同時に、8px上の位置から下の定位置へ0.22秒かけて滑り込み、唐突に現れる印象を和らげます。

フェード+スライド
新しいタブで開く

02 · フェード+スライド — 計測 累積面積 11.872% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

opacity+translateY0.18초ease-out

使いどころ

ヘッダーのアカウントメニューや、上部ナビのドロップダウンに向いています。

.menu__panel {
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
}

.menu.is-open .menu__panel {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  transition: opacity .22s ease-out, transform .22s ease-out;
}

03角を起点に拡大

何が起きるか

transform-originをトリガーに近い右上の角に固定し、scale(.9)からscale(1)へ0.22秒で拡大します。

角を起点に拡大
新しいタブで開く

03 · 角を起点に拡大 — 計測 累積面積 12.244% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform-originscale(0.95→1)0.15초

向いている場面

跳ねるようなcubic-bezierを使っているため、開いた瞬間にわずかな弾みが付きます。右上のプロフィールメニューや、ツールバーのオプションメニューに向いています。

.menu__panel {
  transform-origin: top right;
  transform: scale(.9);
  opacity: 0;
}

.menu.is-open .menu__panel {
  transform: scale(1);
  opacity: 1;
  transition: transform .22s cubic-bezier(.2, 1.4, .4, 1),
              opacity .22s cubic-bezier(.2, 1.4, .4, 1);
}

04クリップパスで開く

動きの内容

clip-pathの矩形がinset(0 0 100% 0)からinset(0 0 0% 0)へ0.3秒かけて動き、パネルの中身を上から切り出すように見せます。

クリップパスで開く
新しいタブで開く

04 · クリップパスで開く — 計測 累積面積 1.538% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-pathinset()0.2초

使う場面

カード内のコンテキストメニューや、狭いツールバーの「もっと見る」メニューに向いています。

.menu__panel {
  clip-path: inset(0 0 100% 0);
  pointer-events: none;
}

.menu.is-open .menu__panel {
  clip-path: inset(0 0 0% 0);
  pointer-events: auto;
  transition: clip-path .3s ease-out;
}

05項目が順番に表示

どんな動きか

パネルが開くと項目は一斉に現れず、各liのtransition-delayに(i-1)×20msを足すことで、上から20msずつ遅れて一つずつ浮かび上がります。

項目が順番に表示
新しいタブで開く

05 · 項目が順番に表示 — 計測 累積面積 1.174% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transition-delay20ms 간격순차 등장

使いどころ

項目数の多い設定メニューや、フィルターオプションのドロップダウンに向いています。

.menu__panel li {
  opacity: 0;
  transform: translateY(-6px);
}

.menu.is-open .menu__panel li {
  opacity: 1;
  transform: none;
  transition: opacity .22s ease-out, transform .22s ease-out;
}

@for $i from 1 through 4 {
  .menu.is-open .menu__panel li:nth-child(#{$i}) {
    transition-delay: ($i - 1) * 20ms;
  }
}

06メガメニュー背景の塗りつぶし

何が起きるか

背景の.menu__fillがtransform-origin: leftを起点にscaleX(0)からscaleX(1)へ0.3秒で横に広がり、続けて.menu__gridの不透明度が上がることで、複数列のカテゴリを一枚のパネルに展開します。

メガメニュー背景の塗りつぶし
新しいタブで開く

06 · メガメニュー背景の塗りつぶし — 計測 累積面積 25.75% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

width 확장배경 번짐다단 그리드

メガメニュー css の使いどころ

ECサイト上部のカテゴリメガメニューや、多段の商品ナビゲーションに向いた発想の型です。

.menu__fill {
  transform: scaleX(0);
  transform-origin: left;
}

.menu__grid { opacity: 0; }

.menu.is-open .menu__fill {
  transform: scaleX(1);
  transition: transform .3s ease-out;
}

.menu.is-open .menu__grid {
  opacity: 1;
  transition: opacity .3s ease-out .12s;
}

07遅延して開く(transition-delay)

動きの内容

トリガーにカーソルが乗っただけでは開かないよう、:hoverと.is-openのtransition-delayだけに150msを足し、素通りしたカーソルでは開かせません。

遅延して開く(transition-delay)
新しいタブで開く

07 · 遅延して開く(transition-delay) — 計測 累積面積 10.278% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transition-delay150ms 지연:hover 진입

使う場面

閉じるときはこの遅延付きセレクタが外れるため、間を置かず0.22秒で閉じます。横に並んだ複数のヘッダーメニューや、誤って開いてはいけないナビゲーションに向いた発想の型です。

.menu__panel {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity .22s ease-out, transform .22s ease-out;
}

.menu:hover .menu__panel,
.menu.is-open .menu__panel {
  opacity: 1;
  transform: none;
  transition-delay: 150ms;
}

08popover属性を使用

どんな動きか

popover属性と::backdropを使うと、開閉・バックドロップの表示・Escキーでの閉じる動作をブラウザが代わりに管理し、JSのトグル処理を書かずに済みます。

popover属性を使用
新しいタブで開く

08 · popover属性を使用 — 計測 累積面積 8.969% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

popover::backdropJS 없음

使いどころ

軽いアクションメニューや、JSバンドルを増やしたくないページに向いた発想の型です。

.menu__panel {
  transform: scale(.9);
  opacity: 0;
}

.menu__panel::backdrop {
  background: rgba(0, 0, 0, .18);
}

.menu__panel:popover-open {
  transform: scale(1);
  opacity: 1;
  transition: transform .3s cubic-bezier(.2, 1.4, .4, 1),
              opacity .3s cubic-bezier(.2, 1.4, .4, 1);
}

09モバイル全画面シート

何が起きるか

狭い画面ではドロップダウンの代わりに、.menu__sheetがtranslateY(100%)から0へ0.3秒かけて画面下からせり上がり、指で押しやすい全幅のシートになります。

モバイル全画面シート
新しいタブで開く

09 · モバイル全画面シート — 計測 累積面積 62.561% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

미디어쿼리env(safe-area-inset-bottom)전체 폭

向いている場面

max-width: 360pxのメディアクエリではenv(safe-area-inset-bottom)も足し、ノッチのある端末でも項目が隠れません。モバイルWebのナビゲーションメニューや、レスポンシブヘッダーの「もっと見る」メニューに向いています。

.menu__sheet {
  transform: translateY(100%);
}

.menu__sheet.is-open {
  transform: translateY(0);
  transition: transform .3s ease-out;
}

@media (max-width: 360px) {
  .menu__sheet {
    padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  }
}

どこで崩れるか — 落とし穴

overflow hidden の付け忘れ

9つとも高さや座標の数値を直接書き換えず、grid-template-rows・opacity・transform・clip-pathという補間できる値だけを動かしています。1つ目の落とし穴は01を組み立てているときに踏んだもので、内側の.menu__listにoverflow: hiddenを付け忘れると、grid-template-rowsが0frの状態でも中の文字がはみ出して見えてしまいました。高さの単位そのものを補間するinterpolate-sizeというプロパティもありますが、MDNの対応状況によると2026年9月時点ではChrome 129以降にしか実装されておらず、SafariとFirefoxではまだ使えないため、この記事では全ブラウザで動くgrid-template-rowsのトリックを選びました。

背景より先に文字が出る

2つ目は06のメガメニューを作っていたときのことです。背景の.menu__fillと項目の.menu__gridを同時に不透明度1へ変えると、背景がまだ左端しか塗られていない途中の段階でリンクの文字が先に見えてしまい、文字だけが余白の上に浮いているように見えました。.menu__gridのtransitionに0.3秒の40%にあたる0.12秒の遅延を足し、背景が塗り終わってから項目を出すようにしてから、この崩れは直りました。修正済みの9ファイルを開くには wncdnnc7 という文字列が必要です。下のカードからzipを取ってください。

popover の対応ブラウザ

3つ目は08のpopover属性を試したときのことです。popoverはJSのトグルなしに開閉・バックドロップ・Escキーをブラウザが代わりに処理してくれますが、MDNのPopover API対応表によるとChrome 114以降・Safari 17以降・Firefox 125以降でしか動かず、それより古いブラウザではpopover属性ごと無視されるため、パネルは開閉できないまま常に非表示で残ります。popoverだけに頼らず、対応していないブラウザ向けに別の開閉手段を残しておく必要があると分かりました。 なお、メニューの中身を開閉ではなく流し続けるべき場所では、CSS 無限ループ マーキー9選が同じヘッダーの位置の別の答えになります。

アクセシビリティ

動きを減らす設定のとき

9つともprefers-reduced-motion: reduceで開閉のtransitionとアニメーションが止まりますが、要素そのものは消えません。01のgrid-template-rowsは0frのまま、02のフェード+スライドと03の拡大、04のクリップパス、05の項目、07の遅延メニューはどれも「閉じた状態の見た目」で静止します。06はメガメニューの背景.menu__fillがscaleX(0)のまま、.menu__gridがopacity 0のままで止まり、09の全画面シートはtranslateY(100%)のまま画面外に残ります。08のpopoverだけは逆に「開いた状態」で止まり、transform: scale(1)とopacity: 1を保ったまま表示され続けます。

@media (prefers-reduced-motion: reduce) {
  .menu__panel, .menu__panel li,
  .menu__fill, .menu__grid,
  .menu__sheet { transition: none !important; animation: none !important; }
  .menu.is-demo .menu__panel { grid-template-rows: 0fr; }
  .menu__panel:popover-open { transform: scale(1); opacity: 1; }
}

css ドロワー メニューなどドロップダウン以外のCSSアニメーションはこちらのカテゴリー一覧に、このサイトの成り立ちは紹介ページにまとめています。

FAQ

フローティングメニュー デザインを9つ同じページに置いても動きは重なりませんか?

重なりません。それぞれのtransitionやanimationは自分自身のクラス名にしか結び付いていないため、01のgrid-template-rowsと06のscaleXを同じページに並べても、片方の動きがもう片方の要素に影響することはありません。08のpopoverだけはidで対象を指定するため、複数置く場合はpopovertargetとidの組を項目ごとに変える必要があります。 06のメガメニューの背景の塗りを手書きのルールではなくユーティリティで組みたい場合は、Tailwindアニメーション9選がこの開閉の仕組みの移し先です。

Reactでは何を使って開閉を実装しますか?

zipのreact/フォルダの各コンポーネントは、is-openのようなbooleanをuseStateで持ち、そのstateに応じてクラス名を付け外しするだけで、開閉の速さと軌道はCSSのtransitionとanimationが担います。08だけはネイティブなpopover属性を使うため、booleanではなくref経由でshowPopover()とhidePopover()を呼び分ける形になっています。

スマホでも同じように開閉しますか?

はい、01から07はモバイルのSafariやChromeでも同じ速さで開閉します。09はもともと狭い画面向けに全幅シートへ切り替わる作りです。08のpopoverは前述のMDNの対応表にあるとおりSafari 17以降で動きますが、それより古いiOSでは属性ごと無視され、メニューが開閉できないまま残ります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。