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

CSS フォト ギャラリー 9選 — メイソンリーからライトボックスまで

CSS フォト ギャラリーは、複数の写真をグリッドやウォーターフォール状に並べ、ホバーやクリックに反応させるレイアウト技法です。Pinterestで保存したあのギャラリーのように見えても、実際にコードに落とすとグリッドの数式や画像のクロップでつまずきがちです。

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

9つは人気順ではなく、css ギャラリー テンプレートを実際に作るときにぶつかる順番に並べました。まず一番シンプルな均等グリッドから始め(01)、写真の比率がバラバラという現実的な問題を解決し(02)、画面幅に合わせて列数が自動で変わるレスポンシブを加え(03)、高さの違う写真を隙間なく積むウォーターフォールを2通りで比べ(04〜05)、Instagramのように正方形だけに揃え(06)、代表写真1枚を大きく見せる編集レイアウトを乗せ(07)、最後にキャプションとクリック拡大というインタラクションを付け加えます(08〜09)。2枚を重ねてつまみで見比べる並べ方はビフォーアフター スライダー UI 9選に、枠そのものをドラッグで広げる方式は画面分割 css レイアウト9選にまとめてあります。

# 名前 何が動くか JS
01 均等2カラムグリッド タイル translateY・scale なし
02 3カラムグリッド・比率統一 写真 scale、フレーム translateY なし
03 自動レスポンシブグリッド タイル opacity・scale の登場 なし
04 マルチカラム・ウォーターフォール タイル translateY なし
05 グリッド行スパン・ウォーターフォール タイル scale なし
06 正方形グリッド固定 写真 scale、フレーム translateY なし
07 先頭強調レイアウト タイル scale なし
08 ホバーキャプションオーバーレイ キャプション translateY・opacity なし
09 クリックで開くライトボックス パネル scale・opacity なし

01均等2カラムグリッド

何が動くか

写真4枚を2×2の均等なマスに並べ、カーソルが乗ったタイルだけがわずかに浮き上がり、影が濃くなって、今見ている写真がどれかを示します。

均等2カラムグリッド
新しいタブで開く

01 · 均等2カラムグリッド — 計測 累積面積 3.564% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-columns:repeat(2,1fr)gaptranslateY hover

コードで見る所

ポートフォリオのサムネイル4〜6枚や、ブログ記事内の写真の並びのような、一番シンプルなギャラリーに使います。

.gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: $sp-2;
}
.tile {
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.tile:hover, .tile:focus-visible {
  transform: translateY(-4px) scale(1.03);
  box-shadow: $shadow-raised;
}

023カラムグリッド・比率統一

何が動くか

縦長・横長・正方形の写真が混ざっていても、各画像に aspect-ratio: 4/3 と object-fit: cover を指定すればすべてのマスが同じサイズに揃い、ホバーするとそのフレームの中だけ少し拡大します。

3カラムグリッド・比率統一
新しいタブで開く

02 · 3カラムグリッド・比率統一 — 計測 累積面積 3.778% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aspect-ratio:4/3object-fit:coveroverflow:hidden

コードで見る所

スマホで撮って比率がバラバラな写真を並べるギャラリーに向いています。

.cell {
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.photo {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform $duration $easing;
}
.cell:hover .photo, .cell:focus-within .photo {
  transform: scale(1.12);
}

03自動レスポンシブグリッド

何が動くか

grid-template-columns: repeat(auto-fit, minmax(56px, 1fr)) の一行だけで、メディアクエリなしで画面幅に応じて1行に入るマス数が自動で変わり、新しいタイルは薄く現れて定位置に収まります。

自動レスポンシブグリッド
新しいタブで開く

03 · 自動レスポンシブグリッド — 計測 累積面積 23.517% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

repeat(auto-fit,minmax())그리드 자동 배치opacity+scale 진입

コードで見る所

カテゴリごとに写真枚数が違う商品ギャラリーに使います。

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(56px, 1fr));
  gap: $sp-2;
}
.tile {
  opacity: .35;
  transform: scale(.88);
  transition: transform $duration $easing, opacity $duration $easing;
}
.tile.is-visible {
  opacity: 1;
  transform: scale(1);
}

04マルチカラム・ウォーターフォール

何が動くか

写真5枚に columns: 2 を指定するだけで、JavaScriptなしでも高さがバラバラな写真がカラムに沿って隙間なく積み重なるウォーターフォールになり、break-inside: avoid で写真がカラムの境目で真っ二つに切れるのを防ぎます。

マルチカラム・ウォーターフォール
新しいタブで開く

04 · マルチカラム・ウォーターフォール — 計測 累積面積 3.547% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

columns:2break-inside:avoidcolumn-gap

コードで見る所

Pinterest風のムードボードの定番レイアウトです。

.gallery {
  columns: 2;
  column-gap: $sp-2;
}
.tile {
  break-inside: avoid;
  margin: 0 0 $sp-2;
  transition: transform $duration $easing;
}
.tile:hover, .tile:focus-visible {
  transform: translateY(-3px);
}

05グリッド行スパン・ウォーターフォール(読む順番を維持)

何が動くか

grid-auto-rows を4pxのような極小単位に刻み、写真ごとに自分の高さに合わせた grid-row-end: span N を計算しておくと、カラム方式と違って写真が上から下ではなく左から右へ読む順番どおりに並ぶウォーターフォールになります。

グリッド行スパン・ウォーターフォール
新しいタブで開く

05 · グリッド行スパン・ウォーターフォール — 計測 累積面積 2.089% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-auto-rows:4pxgrid-row-end:span N읽는 순서 유지

コードで見る所

ニュース一覧のように掲載順が意味を持つ場面に向いています。

.gallery {
  display: grid;
  grid-auto-flow: row dense;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 4px;
  column-gap: $sp-2;
}
.tile--1 { grid-row-end: span 10; }
.tile--2 { grid-row-end: span 13; }
.tile--3 { grid-row-end: span 9; }

06正方形グリッド固定

何が動くか

すべてのタイルに aspect-ratio: 1 / 1 と object-fit: cover を指定し、元の写真の比率に関係なくInstagramのフィードのような完全な正方形だけを並べます。

正方形グリッド固定
新しいタブで開く

06 · 正方形グリッド固定 — 計測 累積面積 3.329% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aspect-ratio:1/1object-fit:coverscale hover

コードで見る所

ホバーするとタイルがわずかに浮き上がり、中の写真も一緒に拡大します。

.cell {
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.photo {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform $duration $easing;
}
.cell:hover .photo {
  transform: scale(1.1);
}

07先頭強調レイアウト

何が動くか

最初の写真だけに grid-column: span 2 と grid-row: span 2 を与えて他の4倍の大きさで目立たせ、その周りに小さな写真を並べる雑誌風レイアウトを、CSSセレクタ一つで作ります。

先頭強調レイアウト
新しいタブで開く

07 · 先頭強調レイアウト — 計測 累積面積 7.026% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

:nth-of-typegrid-column: 1 / 3grid-row: 1 / 3

コードで見る所

代表写真1枚を強調するブログのヘッダーギャラリーに使います。

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 56px);
  grid-template-rows: repeat(2, 56px);
  gap: $sp-2;
}
.tile:first-child {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}

08ホバーキャプションオーバーレイ

何が動くか

普段は写真の下端にわずかに隠れている半透明の帯が、ホバーすると translateY で完全に上がってきて、タイトルや場所などのキャプション文字も一緒に現れます。写真ごとに地名を添える旅行ギャラリー向けです。

ホバーキャプションオーバーレイ
新しいタブで開く

08 · ホバーキャプションオーバーレイ — 計測 累積面積 4.308% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateY hover반투명 그러데이션opacity

コードで見る所

.caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  transform: translateY(64%);
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}
.tile:hover .caption, .tile:focus-within .caption {
  transform: translateY(0);
  opacity: 1;
}

09クリックで開くライトボックス(JSなし)

何が動くか

タイルをクリックすると popovertarget 属性ひとつで原寸大の写真を収めたポップオーバーが開き、::backdrop が背景を暗く覆う間に @starting-style が写真を小さく透明な状態から始めて大きく表示します。

クリックで開くライトボックス(JSなし)
新しいタブで開く

09 · クリックで開くライトボックス(JSなし) — 計測 累積面積 11.947% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

popovertarget::backdrop@starting-style

コードで見る所

<button popovertarget="lb-01"> と <div id="lb-01" popover> を対にするだけで、開閉のロジックはすべてブラウザが引き受けます。

.lightbox {
  opacity: 0;
  transform: scale(.8);
  transition: opacity $duration $easing, transform $duration $easing,
    overlay $duration $easing allow-discrete, display $duration $easing allow-discrete;
  @starting-style { opacity: 0; transform: scale(.8); }
}
.lightbox:popover-open { opacity: 1; transform: scale(1); }
.lightbox::backdrop { background: rgba($stage-ink, .7); opacity: 0; }
.lightbox:popover-open::backdrop { opacity: 1; }

どこで壊れるか — 落とし穴ひとつ

css ギャラリー、どこで壊れるか?

05を最初にレンダリングしたとき、カラムの並び順が想定と違っていました。dense を付けない普通の grid-auto-flow: row では、大きくスパンするタイルの後ろにできた隙間をブラウザが埋めずに次の行へ進んでしまうことがあり、dense を付けるとその隙間を後ろのタイルが埋めてくれます。ただし dense はDOMの順番より隙間を埋めることを優先するため、スパンの組み合わせによってはタイルが実際のDOM順序と異なる位置に描画されることがあります。04(マルチカラム)は定義からして上から下・左カラムからの順なのでこの問題は起きませんが、05(グリッド行スパン)は dense を有効にした時点でその可能性を抱えることになります。枠の中で写真自体がマウスを追って傾く演出はマウス 追従 アニメーションの3D場面のほうで扱いました。掲載日のように順番そのものに意味があるギャラリーなら、dense を外して隙間を許容するか、圧縮ファイルのパスワード24p2nyh2で開けるvanillaフォルダのspan値を自分で調整し、あらかじめ隙間が出ないようにしておく必要があります。

アクセシビリティ

押さえた点

prefers-reduced-motion: reduce が有効な画面では、9つとも遷移とループだけを止め、途中状態ではなく完成した状態がそのまま残るようにしています。01・02・06は拡大や移動が瞬時に適用され、03のタイルはフェードインなしで最初から定位置(不透明・原寸)に現れ、04・05は浮き上がらずに静止したままで、07の強調タイルは弾む演出なしでそのままの大きさを保ち、08はキャプションが常に開いた状態で固定され、09のライトボックスは拡大の遷移なしで開閉します。09のトリガーはネイティブの <button> 要素なので、クリックと同じようにキーボード(Tab、続けてEnter)でも開き、popovertargetaction="hide" の閉じるボタンも同じ方法で操作できます。残り8項目もホバー専用の動きを :focus-visible や :focus-within にも適用しているため、キーボード操作でもマウス操作と同じ結果が得られます。CSS Grid メイソンリー提案のブラウザ対応状況はMDNで、Popover APIの対応表はMDNのPopover APIページで確認できます。カード型のホバー効果に興味があれば、カードホバー集やグラスモーフィズム集も参考になります。

FAQ

09のライトボックスはPopover APIに対応していないブラウザではどうなりますか?

対応していないブラウザは popovertarget 属性を単に無視するため、ボタンはただのボタンのままでクリックしても何も起きません。レイアウトが崩れたりエラーが出たりしない、安全な性能劣化です。最新のChrome・Edge・Safari・Firefoxはすでに対応しています。

画像 ギャラリー cssとして、04と05のどちらのウォーターフォールを使えばいいですか?

写真が上から下・次のカラムへという順で読まれても構わない場合(ムードボードや一般的なギャラリー)は、columns 方式(04)のほうがコードが短く保守しやすいです。「新着順」のように左上から順番どおりに読ませたい場合はグリッド方式(05)を使い、上の落とし穴で触れた dense による順序の入れ替わりを見込んでおいてください。並び順を手でドラッグして入れ替えるギャラリーはポートフォリオ ギャラリー UI 9選で別に扱っています。

React版の03自動レスポンシブグリッドは、カラム数をどうやって把握していますか?

計算していません。AutoFitGallery はリサイズイベントも状態も持たず、grid-template-columns: repeat(auto-fit, minmax()) がブラウザのレイアウトエンジンの中でコンテナ幅だけを見て決めているため、ウィンドウサイズが変われば自動的に並びが変わります。

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

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