aspect-ratio メディアグリッド

aspect-ratio をCSS変数で制御し、1:1/4:3/16:9などの比率を保ったままタイルを敷き詰める画像ギャラリー。比率切替も滑らかに。

#css#aspect-ratio#grid

ライブデモ

使用例(お題: アイドルグループ Sakura)

この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- Sakura MVギャラリー:aspect-ratio をCSS変数で制御し比率を保ったタイル -->
<section class="sk-mv" id="skMv">
  <header class="sk-mv__head">
    <h2 class="sk-mv__title">🌸 楽曲MV</h2>
    <div class="sk-mv__ratios">
      <button data-r="16/9" class="is-active" type="button">16:9</button>
      <button data-r="4/3" type="button">4:3</button>
      <button data-r="1/1" type="button">1:1</button>
    </div>
  </header>

  <div class="sk-mv__grid" id="skGrid">
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=61')"><span class="sk-mv__play">▶</span></div>
      <figcaption>桜ロード<small>4:12</small></figcaption>
    </figure>
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=62')"><span class="sk-mv__play">▶</span></div>
      <figcaption>はじまりのチャイム<small>3:48</small></figcaption>
    </figure>
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=63')"><span class="sk-mv__play">▶</span></div>
      <figcaption>キミと春風<small>4:30</small></figcaption>
    </figure>
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=64')"><span class="sk-mv__play">▶</span></div>
      <figcaption>ひらり、夢の中<small>3:55</small></figcaption>
    </figure>
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=65')"><span class="sk-mv__play">▶</span></div>
      <figcaption>ペタル・ダンス<small>4:02</small></figcaption>
    </figure>
    <figure class="sk-mv__tile">
      <div class="sk-mv__thumb" style="background-image:url('https://picsum.photos/400/300?random=66')"><span class="sk-mv__play">▶</span></div>
      <figcaption>満開エモーション<small>4:21</small></figcaption>
    </figure>
  </div>
</section>
CSS
/* Sakura:aspect-ratio をCSS変数(--r)で制御し、比率を保ったMVタイル */
:root {
  --pink: #ffd1e0;
  --pink-deep: #ff8fb3;
  --gray: #fbf7f9;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
  background: linear-gradient(165deg, #fff5f9, var(--gray));
  color: #6a2740;
  overflow: hidden;
}

.sk-mv {
  height: 400px;
  display: flex;
  flex-direction: column;
  padding: 14px 16px 0;
  /* タイルの比率はここで一括管理 */
  --r: 16/9;
}

.sk-mv__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.sk-mv__title { margin: 0; font-size: 16px; font-weight: 800; }

.sk-mv__ratios { display: flex; gap: 6px; }
.sk-mv__ratios button {
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid #ffc4d8;
  background: #fff;
  color: #c06088;
  cursor: pointer;
  transition: all 0.2s ease;
}
.sk-mv__ratios button.is-active {
  background: var(--pink-deep);
  border-color: var(--pink-deep);
  color: #fff;
}

.sk-mv__grid {
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  padding-bottom: 14px;
  align-content: start;
}

.sk-mv__tile {
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
  border: 1px solid #ffe0ea;
  box-shadow: 0 6px 14px rgba(214, 94, 140, 0.14);
}

/* aspect-ratio を変数で受け取り、比率を保ったまま敷き詰める */
.sk-mv__thumb {
  aspect-ratio: var(--r);
  background-size: cover;
  background-position: center;
  position: relative;
  display: grid;
  place-items: center;
  transition: aspect-ratio 0.35s ease;
}
.sk-mv__play {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.82);
  color: var(--pink-deep);
  display: grid;
  place-items: center;
  font-size: 12px;
  padding-left: 2px;
  box-shadow: 0 4px 10px rgba(214, 94, 140, 0.3);
}

.sk-mv__tile figcaption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 7px 10px;
  font-size: 11px;
  font-weight: 700;
  color: #8a4a64;
}
.sk-mv__tile figcaption small { font-size: 9.5px; color: #b88; font-weight: 500; }

@media (prefers-reduced-motion: reduce) {
  .sk-mv__thumb, .sk-mv__ratios button { transition: none; }
}
JavaScript
// 比率ボタンで --r を切替。全タイルの aspect-ratio が滑らかに変わる
const root = document.getElementById("skMv");
const buttons = document.querySelectorAll(".sk-mv__ratios button");

buttons.forEach((btn) => {
  btn.addEventListener("click", () => {
    // アクティブ表示を付け替え
    buttons.forEach((b) => b.classList.remove("is-active"));
    btn.classList.add("is-active");
    // CSS変数を更新(dataset の比率をそのまま渡す)
    root?.style.setProperty("--r", btn.dataset.r);
  });
});

コード

HTML
<!-- aspect-ratio メディアグリッド:比率を保つタイルを敷き詰めたギャラリー -->
<div class="ar">
  <div class="ar__top">
    <h1 class="ar__head">Aspect <span>Gallery</span></h1>
    <div class="ar__ratios" id="ratioBtns" role="group" aria-label="比率切替">
      <button class="ar__chip is-active" data-ratio="1 / 1">1:1</button>
      <button class="ar__chip" data-ratio="4 / 3">4:3</button>
      <button class="ar__chip" data-ratio="16 / 9">16:9</button>
      <button class="ar__chip" data-ratio="3 / 4">3:4</button>
    </div>
  </div>
  <div class="ar__grid" id="arGrid">
    <!-- タイルはJSで生成(picsum画像) -->
  </div>
</div>
CSS
/* 全体:黒地のギャラリー */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: "Hiragino Sans", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% -20%, #1f2937, #0b0f17 70%);
  color: #f3f4f6; min-height: 100vh; padding: 16px;
}

.ar { width: min(900px, 100%); margin: 0 auto; }
.ar__top {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 14px;
}
.ar__head { font-size: clamp(18px, 3.4vw, 26px); font-weight: 800; letter-spacing: .03em; }
.ar__head span {
  background: linear-gradient(90deg, #fda4af, #c084fc);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* 比率切替チップ */
.ar__ratios { display: flex; gap: 6px; flex-wrap: wrap; }
.ar__chip {
  font: inherit; font-size: 12px; font-weight: 700; cursor: pointer;
  color: #cbd5e1; background: #1e293b; border: 1px solid #334155;
  padding: 6px 12px; border-radius: 20px; transition: all .2s;
}
.ar__chip:hover { border-color: #64748b; }
.ar__chip.is-active {
  color: #0b0f17; border-color: transparent;
  background: linear-gradient(90deg, #fda4af, #c084fc);
}

/* グリッド:自動敷き詰め */
.ar__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}

/* タイル:aspect-ratio で比率を維持(CSS変数で動的に) */
.ar__tile {
  position: relative; overflow: hidden; border-radius: 12px;
  aspect-ratio: var(--ratio, 1 / 1);
  background: #1e293b;
  border: 1px solid #2b3648;
  transition: aspect-ratio .35s ease, transform .25s ease;
}
.ar__tile:hover { transform: translateY(-4px) scale(1.02); z-index: 1; }
.ar__tile img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.ar__tile span {
  position: absolute; left: 8px; bottom: 8px;
  font-size: 10px; font-weight: 700; letter-spacing: .1em;
  background: rgba(0,0,0,.55); color: #fff; padding: 3px 8px; border-radius: 6px;
  backdrop-filter: blur(2px);
}

@media (prefers-reduced-motion: reduce) {
  .ar__tile, .ar__chip { transition: none; }
}
JavaScript
// 画像タイルを生成し、チップで全タイルの aspect-ratio を一括変更
(() => {
  const grid = document.getElementById('arGrid');
  const btnBox = document.getElementById('ratioBtns');
  if (!grid || !btnBox) return;

  // picsum のランダム画像でタイルを生成(seed で安定化)
  const COUNT = 8;
  const frag = document.createDocumentFragment();
  for (let i = 0; i < COUNT; i++) {
    const tile = document.createElement('div');
    tile.className = 'ar__tile';
    const img = document.createElement('img');
    img.loading = 'lazy';
    img.alt = '';
    img.src = `https://picsum.photos/seed/ar${i + 10}/400/400`;
    const tag = document.createElement('span');
    tag.textContent = `0${i + 1}`;
    tile.append(img, tag);
    frag.appendChild(tile);
  }
  grid.appendChild(frag);

  // チップで比率を切り替え
  const tiles = grid.querySelectorAll('.ar__tile');
  const setRatio = (ratio) => {
    tiles.forEach((t) => t.style.setProperty('--ratio', ratio));
  };
  setRatio('1 / 1'); // 初期比率

  btnBox.addEventListener('click', (e) => {
    const btn = e.target.closest('.ar__chip');
    if (!btn) return;
    btnBox.querySelectorAll('.ar__chip').forEach((b) => b.classList.remove('is-active'));
    btn.classList.add('is-active');
    setRatio(btn.dataset.ratio);
  });
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「aspect-ratio メディアグリッド」の効果を追加してください。

# 追加してほしい効果
aspect-ratio メディアグリッド(レイアウト & グリッド)
aspect-ratio をCSS変数で制御し、1:1/4:3/16:9などの比率を保ったままタイルを敷き詰める画像ギャラリー。比率切替も滑らかに。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- aspect-ratio メディアグリッド:比率を保つタイルを敷き詰めたギャラリー -->
<div class="ar">
  <div class="ar__top">
    <h1 class="ar__head">Aspect <span>Gallery</span></h1>
    <div class="ar__ratios" id="ratioBtns" role="group" aria-label="比率切替">
      <button class="ar__chip is-active" data-ratio="1 / 1">1:1</button>
      <button class="ar__chip" data-ratio="4 / 3">4:3</button>
      <button class="ar__chip" data-ratio="16 / 9">16:9</button>
      <button class="ar__chip" data-ratio="3 / 4">3:4</button>
    </div>
  </div>
  <div class="ar__grid" id="arGrid">
    <!-- タイルはJSで生成(picsum画像) -->
  </div>
</div>

【CSS】
/* 全体:黒地のギャラリー */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: "Hiragino Sans", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% -20%, #1f2937, #0b0f17 70%);
  color: #f3f4f6; min-height: 100vh; padding: 16px;
}

.ar { width: min(900px, 100%); margin: 0 auto; }
.ar__top {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 14px;
}
.ar__head { font-size: clamp(18px, 3.4vw, 26px); font-weight: 800; letter-spacing: .03em; }
.ar__head span {
  background: linear-gradient(90deg, #fda4af, #c084fc);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* 比率切替チップ */
.ar__ratios { display: flex; gap: 6px; flex-wrap: wrap; }
.ar__chip {
  font: inherit; font-size: 12px; font-weight: 700; cursor: pointer;
  color: #cbd5e1; background: #1e293b; border: 1px solid #334155;
  padding: 6px 12px; border-radius: 20px; transition: all .2s;
}
.ar__chip:hover { border-color: #64748b; }
.ar__chip.is-active {
  color: #0b0f17; border-color: transparent;
  background: linear-gradient(90deg, #fda4af, #c084fc);
}

/* グリッド:自動敷き詰め */
.ar__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}

/* タイル:aspect-ratio で比率を維持(CSS変数で動的に) */
.ar__tile {
  position: relative; overflow: hidden; border-radius: 12px;
  aspect-ratio: var(--ratio, 1 / 1);
  background: #1e293b;
  border: 1px solid #2b3648;
  transition: aspect-ratio .35s ease, transform .25s ease;
}
.ar__tile:hover { transform: translateY(-4px) scale(1.02); z-index: 1; }
.ar__tile img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.ar__tile span {
  position: absolute; left: 8px; bottom: 8px;
  font-size: 10px; font-weight: 700; letter-spacing: .1em;
  background: rgba(0,0,0,.55); color: #fff; padding: 3px 8px; border-radius: 6px;
  backdrop-filter: blur(2px);
}

@media (prefers-reduced-motion: reduce) {
  .ar__tile, .ar__chip { transition: none; }
}

【JavaScript】
// 画像タイルを生成し、チップで全タイルの aspect-ratio を一括変更
(() => {
  const grid = document.getElementById('arGrid');
  const btnBox = document.getElementById('ratioBtns');
  if (!grid || !btnBox) return;

  // picsum のランダム画像でタイルを生成(seed で安定化)
  const COUNT = 8;
  const frag = document.createDocumentFragment();
  for (let i = 0; i < COUNT; i++) {
    const tile = document.createElement('div');
    tile.className = 'ar__tile';
    const img = document.createElement('img');
    img.loading = 'lazy';
    img.alt = '';
    img.src = `https://picsum.photos/seed/ar${i + 10}/400/400`;
    const tag = document.createElement('span');
    tag.textContent = `0${i + 1}`;
    tile.append(img, tag);
    frag.appendChild(tile);
  }
  grid.appendChild(frag);

  // チップで比率を切り替え
  const tiles = grid.querySelectorAll('.ar__tile');
  const setRatio = (ratio) => {
    tiles.forEach((t) => t.style.setProperty('--ratio', ratio));
  };
  setRatio('1 / 1'); // 初期比率

  btnBox.addEventListener('click', (e) => {
    const btn = e.target.closest('.ar__chip');
    if (!btn) return;
    btnBox.querySelectorAll('.ar__chip').forEach((b) => b.classList.remove('is-active'));
    btn.classList.add('is-active');
    setRatio(btn.dataset.ratio);
  });
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。