CSSだけで作る、サムネをクリックすると大きな画像が切り替わるギャラリー

ビジュアル

CSSだけで作る、サムネをクリックすると大きな画像が切り替わるギャラリー

投稿日2026/02/19

更新日2026/2/11

サムネを押したときに、大きな画像が気持ちよく切り替わるギャラリーをCSSだけで作りたい。
このサンプルは、クリックしたサムネ(:target)の画像をanchor-positioningで大きな画像の表示エリアにぴったり合わせて配置し、別要素なのに同じ場所へ収まる“入れ替え”表現を作っています。

Preview プレビュー

Code コード

<div class="kumonosu-gallery">
	<div class="kumonosu-main-img">
		<h1>Image swap using <code>anchor-positioning</code></h1>
	</div>
	<a href="#kumonosu-item-1" id="kumonosu-item-1">
		<img src="https://picsum.photos/400/400?random=1" alt="Title 1">
		<h2>Title 1</h2>
	</a>
	<a href="#kumonosu-item-2" id="kumonosu-item-2">
		<img src="https://picsum.photos/400/400?random=2" alt="Title 2">
		<h2>Title 2</h2>
	</a>
	<a href="#kumonosu-item-3" id="kumonosu-item-3">
		<img src="https://picsum.photos/400/400?random=3" alt="Title 3">
		<h2>Title 3</h2>
	</a>
	<a href="#kumonosu-item-4" id="kumonosu-item-4">
		<img src="https://picsum.photos/400/400?random=4" alt="Title 4">
		<h2>Title 4</h2>
	</a>
	<a href="#kumonosu-item-5" id="kumonosu-item-5">
		<img src="https://picsum.photos/400/400?random=5" alt="Title 5">
		<h2>Title 5</h2>
	</a>
	<a href="#kumonosu-item-6" id="kumonosu-item-6">
		<img src="https://picsum.photos/400/400?random=6" alt="Title 6">
		<h2>Title 6</h2>
	</a>
	<a href="#kumonosu-item-7" id="kumonosu-item-7">
		<img src="https://picsum.photos/400/400?random=7" alt="Title 7">
		<h2>Title 7</h2>
	</a>
	<a href="#kumonosu-item-8" id="kumonosu-item-8">
		<img src="https://picsum.photos/400/400?random=8" alt="Title 8">
		<h2>Title 8</h2>
	</a>
	<a href="#kumonosu-item-9" id="kumonosu-item-9">
		<img src="https://picsum.photos/400/400?random=9" alt="Title 9">
		<h2>Title 9</h2>
	</a>
</div>
@import url(https://fonts.bunny.net/css?family=jura:300,500);
/* --- 基本設定 --- */
* {
	box-sizing: border-box;
}
:root {
	color-scheme: light dark;
	--bg-dark: rgb(12, 10, 9);
	--bg-light: rgb(248, 244, 238);
	--txt-light: rgb(10, 10, 10);
	--txt-dark: rgb(245, 245, 245);
	--line-light: rgba(0 0 0 / .25);
	--line-dark: rgba(255 255 255 / .25);
	--clr-bg: light-dark(var(--bg-light), var(--bg-dark));
	--clr-txt: light-dark(var(--txt-light), var(--txt-dark));
	--clr-lines: light-dark(var(--line-light), var(--line-dark));
}
body {
	background-color: var(--clr-bg);
	color: var(--clr-txt);
	min-height: 100svh;
	margin: 0;
	padding: 2rem;
	font-family: "Jura", sans-serif;
	font-size: 1rem;
	line-height: 1.5;
	display: grid;
	place-items: center;
	gap: 2rem;
}
img {
	max-width: 100%;
	display: block;
}
@supports not (position-anchor: --test) {
	body::before {
		content: "Sorry, your browser doesn't support anchor-positioning. Please use Chrome 125+ or Edge 125+.";
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
		background: #ff4757;
		color: white;
		text-align: center;
		padding: 0.5rem;
		font-size: 0.8rem;
		z-index: 100;
	}
}
/* --- ギャラリーのスタイル --- */
.kumonosu-gallery {
	--size: 100%;
	--grid-cols: repeat(3, 1fr);
	--grid-rows: repeat(6, 1fr);
	--gap: .5rem;
	--main-image-columns: 1 / -1;
	--main-image-rows: 1 / span 3;
	--main-img-w: 100%;
	--main-img-h: auto;
	width: min(100%, 900px);
	display: grid;
	grid-template-columns: var(--grid-cols);
	grid-template-rows: var(--grid-rows);
	gap: var(--gap);
	position: relative;
}
@media (width > 600px) {
	body .kumonosu-gallery {
		--size: 120px;
		--grid-cols: repeat(6, 1fr);
		--grid-rows: repeat(3, 1fr);
		--main-image-columns: 4 / -1;
		--main-image-rows: 1 / span 3;
		--main-img-w: auto;
		--main-img-h: 100%;
	}
}
.kumonosu-gallery>.kumonosu-main-img {
	position: relative;
	grid-column: var(--main-image-columns);
	grid-row: var(--main-image-rows);
	display: grid;
	place-content: center;
	padding: 1em;
	font-size: 2rem;
	anchor-name: --main-img;
	overflow: hidden;
	border-image: fill 0 linear-gradient(transparent 50%, black);
}
.kumonosu-gallery>.kumonosu-main-img>h1 {
	margin: 0;
	font-size: clamp(1rem, 2.5vw + 0.45rem, 1.2rem);
	text-align: center;
}
.kumonosu-gallery>.kumonosu-main-img:has(~:target) h1 {
	display: none;
}
.kumonosu-gallery>a {
	display: block;
	aspect-ratio: 1;
	overflow: hidden;
	text-decoration: none;
}
.kumonosu-gallery>a:hover:not(:target) img {
	opacity: .5;
}
.kumonosu-gallery>a>img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: opacity 300ms ease-in-out;
}
.kumonosu-gallery>a:target img {
	position: absolute;
	inset: anchor(--main-img top) anchor(--main-img right) anchor(--main-img bottom) anchor(--main-img left);
	position-anchor: --main-img;
	width: var(--main-img-w, 0);
	height: var(--main-img-h, 0);
	animation: --kumonosu-fade-in 750ms ease-in-out;
	z-index: -1;
}
.kumonosu-gallery>a>h2 {
	color: white;
	font-size: 1.2rem;
	font-weight: 300;
	opacity: 0;
	translate: 0 1lh;
	transition: all 300ms ease-in-out;
	margin: 0;
	pointer-events: none;
}
.kumonosu-gallery>a:target h2 {
	position: absolute;
	inset: auto;
	bottom: anchor(--main-img bottom);
	left: calc(anchor(--main-img left) + 1rem);
	position-anchor: --main-img;
	opacity: 1;
	translate: 0 -1rem;
}
@keyframes --kumonosu-fade-in {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

Explanation 詳しい説明

仕様

全体はCSS Gridでレイアウトし、大きな画像の表示エリア.main-img)にanchor-name: --main-imgを設定して基準にしています。

サムネはa要素で、クリックするとURLのハッシュが変わり、その要素が:targetになります。

:targetになったサムネのimgだけをposition: absoluteに切り替え、inset: anchor(...)大きな画像の表示エリアの四辺に合わせて配置します。

見た目としては、サムネが大きな画像の場所に“切り替わった”ように見えます(実際はアンカー基準で再配置している仕組みです)。表示のつながりはフェードインアニメーションで補っています。

タイトル(h2)も同様に、:target時だけbottom: anchor(--main-img bottom)などで表示エリアに追従させています。

初期説明のh1は、どれかが:targetになると非表示にして、画像の切り替えに集中できるようにしています。

  • 大きな画像の表示エリアanchor-nameを付けて基準化
  • サムネは:targetで選択状態を作る
  • :target imgをアンカー位置へabsolute配置して大きな画像を切り替える
  • h2もアンカーに追従して表示する

カスタム

カスタムは「大きな画像の表示エリアの位置」と「切り替えの印象」を触るのが最優先です。レスポンシブはCSS変数で切り替えているので、レイアウト変更も破綻しにくい構成です。

  • グリッド構成:.gallery--grid-cols / --grid-rows
  • 大きな画像の表示エリアの場所:--main-image-columns / --main-image-rows
  • 大きな画像の収まり:--main-img-w / --main-img-h
  • 余白と密度:--gap.main-imgpadding
  • 切り替えの印象:@keyframes --fade-inの時間・イージング

タイトルの位置は、:target h2left/bottom/translateを調整すると作りやすいです。

注意点

この実装はanchor-positioningposition-anchor / anchor())前提のため、未対応ブラウザでは同じ切り替えになりません。コード内では@supports not (position-anchor: --test)で注意表示を出していますが、実運用では対応ブラウザを明示するのが安全です。

また、切り替えは:target=URLハッシュ依存なので、クリックするとURL末尾が変わります。共有できるメリットがある一方、同一ページ内で他のハッシュ遷移を使っている場合はIDの衝突に注意が必要です。

  • anchor-positioning未対応環境では動作しない(ブラウザ要件が必要)
  • :targetなのでURLハッシュが変わる(他のアンカーと干渉注意)
  • 表示エリアのoverflowや背景(マスク)設定で見え方が変わる