CSSで円形に要素を配置する方法まとめ|transform・三角関数・アニメーションまで徹底解説!

css
記事内に広告が含まれています。

Web制作の中で「見た目のインパクト」を大きく左右するのが、レイアウトや配置の工夫です。その中でも、要素を円形に配置するUIは、ナビゲーションやギャラリー、アニメーションなどでよく見かける人気のスタイルです。ただし、いざ実装しようとすると「どうやって円形に要素を並べるの?」「三角関数って必要?」「レスポンシブ対応はできる?」といった悩みが出てきませんか?

CSSだけで円形配置を実現する方法は意外と多彩で、基本的なtransformの使い方から高度なカスタムプロパティ・三角関数の応用、そしてアニメーションまで幅広く存在します。本記事では、CSS初心者の方でもわかりやすく、そして中上級者の方でも役立つよう、実用的なテクニックやアイデアを段階的にご紹介していきます。

この記事を読んでわかること

  • CSSで要素を円形に並べる基本構造と仕組み
  • transform: rotate()translate()を組み合わせた円形レイアウトの実装方法
  • CSS変数やcalc()を活用した柔軟で再利用しやすい実装テクニック
  • 三角関数(sincos)を使った精密な円形配置とその計算ロジック
  • 円形UIに動きをつけるCSSアニメーションの作り方
  • hover・クリックに反応するインタラクティブな円形メニューの実装法
  • 実際のデザインパターンに応じた円形配置アイデア集(ナビ・ギャラリー・カードなど)

「円形に要素を配置したいけど、どこから手をつけていいかわからない」という方は、ぜひこの記事を参考に、CSSの力だけで自由自在に円形レイアウトを操ってみてください!

CSSで円形配置を簡単に実装する方法

CSSを使って要素を円形に配置する技術は、モダンなWebデザインにおいて非常に注目されている手法です。従来のグリッドやフレックスボックスでは表現が困難だった円形レイアウトも、適切なCSSプロパティの組み合わせによって実現できます。

円形配置の実装には複数のアプローチがありますが、最も基本的で理解しやすい方法から段階的に学習することで、より高度な表現技法への道筋が見えてきます。この章では、初心者でも理解できる基本的な仕組みから、実用的な実装方法まで詳しく解説していきます。

CSSで円形に要素を並べる基本原理と仕組み

円形配置を理解するためには、まずCSSの座標系と回転の概念を把握することが重要です。HTMLの要素は通常、左上を原点とした直交座標系に配置されますが、円形配置では極座標系の考え方を取り入れる必要があります。

円形配置の基本原理は以下の通りです:

  1. 中心点の設定: 円の中心となる要素(親要素)を定義
  2. 半径の決定: 要素を配置する円の大きさを設定
  3. 角度の計算: 各要素の配置角度を決定
  4. 座標変換: 極座標を直交座標に変換して配置

この原理を実装する際、CSSのtransformプロパティが中心的な役割を果たします。特にrotate()translate()transform-originの組み合わせが重要になります。

/* 基本的な円形配置の概念 */
.circular-container {
  position: relative;
  width: 300px;
  height: 300px;
  border: 2px dashed #ccc; /* 円の境界を視覚化 */
  border-radius: 50%;
  margin: 50px auto;
}

.circular-item {
  position: absolute;
  width: 50px;
  height: 50px;
  background-color: #3498db;
  border-radius: 50%;
  top: 50%;
  left: 50%;
  transform-origin: 0 0; /* 変形の基準点を要素の中心に設定 */
}

上記のコードでは、.circular-containerが円形レイアウトの基盤となり、.circular-itemが実際に配置される要素です。transform-originを適切に設定することで、回転や移動の基準点を制御できます。

transform: rotate() と translate() を組み合わせた基本構造

円形配置の実装において、transform: rotate()translate()の組み合わせは最も直感的で理解しやすいアプローチです。この手法では、各要素を一度中心に配置してから、指定された角度で回転させ、その後半径分だけ外側に移動させます。

以下に具体的な実装例を示します:

/* 6個の要素を円形に配置する例 */
.circular-layout {
  position: relative;
  width: 400px;
  height: 400px;
  margin: 100px auto;
}

.circle-item {
  position: absolute;
  width: 60px;
  height: 60px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  font-size: 14px;

  /* 中心に配置 */
  top: 50%;
  left: 50%;
  margin: -30px 0 0 -30px; /* 要素の半分のサイズでオフセット */

  /* トランジション効果を追加 */
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

/* hover効果 */
.circle-item:hover {
  transform: rotate(var(--rotation)) translateY(-160px) scale(1.1);
  box-shadow: 0 10px 20px rgba(0,0,0,0.3);
}

/* 各要素の回転角度を設定 */
.circle-item:nth-child(1) {
  --rotation: 0deg;
  transform: rotate(0deg) translateY(-150px);
}

.circle-item:nth-child(2) {
  --rotation: 60deg;
  transform: rotate(60deg) translateY(-150px);
}

.circle-item:nth-child(3) {
  --rotation: 120deg;
  transform: rotate(120deg) translateY(-150px);
}

.circle-item:nth-child(4) {
  --rotation: 180deg;
  transform: rotate(180deg) translateY(-150px);
}

.circle-item:nth-child(5) {
  --rotation: 240deg;
  transform: rotate(240deg) translateY(-150px);
}

.circle-item:nth-child(6) {
  --rotation: 300deg;
  transform: rotate(300deg) translateY(-150px);
}

対応するHTMLコード:

<div class="circular-layout">
  <div class="circle-item">1</div>
  <div class="circle-item">2</div>
  <div class="circle-item">3</div>
  <div class="circle-item">4</div>
  <div class="circle-item">5</div>
  <div class="circle-item">6</div>
</div>

See the Pen circle-layout-sample-01 by watashi-xyz (@watashi-xyz) on CodePen.

この実装のポイント:

  • 角度の計算: 6個の要素なので360°÷6=60°ずつ回転
  • translateY()の使用: Y軸方向の移動で半径を制御
  • CSS変数の活用: -rotationでhover時の動きを統一
  • マージンでの中央配置: 要素の中心を親要素の中心に合わせる

参考リンク: MDN – CSS Transform

CSS変数(カスタムプロパティ)とcalc()を活用した柔軟な実装

より柔軟で保守性の高い円形配置を実装するには、CSS変数(カスタムプロパティ)とcalc()関数の組み合わせが効果的です。この手法により、要素数や半径を動的に変更できる、スケーラブルなソリューションを構築できます。

/* 柔軟な円形配置システム */
.flexible-circular {
  --item-count: 8; /* 配置する要素数 */
  --circle-radius: 180px; /* 円の半径 */
  --item-size: 50px; /* 各要素のサイズ */

  position: relative;
  width: calc(var(--circle-radius) * 2 + var(--item-size));
  height: calc(var(--circle-radius) * 2 + var(--item-size));
  margin: 50px auto;
}

.flexible-item {
  position: absolute;
  width: var(--item-size);
  height: var(--item-size);
  background: conic-gradient(from 0deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #ffeaa7);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  text-shadow: 1px 1px 2px rgba(0,0,0,0.5);

  /* 中心配置 */
  top: 50%;
  left: 50%;
  margin: calc(var(--item-size) / -2) 0 0 calc(var(--item-size) / -2);

  /* アニメーション効果 */
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* 動的な角度計算と配置 */
.flexible-item:nth-child(1) {
  --index: 0;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(2) {
  --index: 1;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(3) {
  --index: 2;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(4) {
  --index: 3;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(5) {
  --index: 4;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(6) {
  --index: 5;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(7) {
  --index: 6;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

.flexible-item:nth-child(8) {
  --index: 7;
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1));
}

/* ホバー効果 */
.flexible-item:hover {
  transform: rotate(calc(360deg / var(--item-count) * var(--index)))
             translateY(calc(var(--circle-radius) * -1.2))
             scale(1.2);
  z-index: 10;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
  .flexible-circular {
    --circle-radius: 120px;
    --item-size: 40px;
  }
}

@media (max-width: 480px) {
  .flexible-circular {
    --circle-radius: 80px;
    --item-size: 30px;
  }
}

対応するHTMLコード:

<div class="flexible-circular">
  <div class="flexible-item">A</div>
  <div class="flexible-item">B</div>
  <div class="flexible-item">C</div>
  <div class="flexible-item">D</div>
  <div class="flexible-item">E</div>
  <div class="flexible-item">F</div>
  <div class="flexible-item">G</div>
  <div class="flexible-item">H</div>
</div>

See the Pen circle-layout-sample-02 by watashi-xyz (@watashi-xyz) on CodePen.

このアプローチの主な利点:

  1. 設定の一元化: CSS変数により、サイズや要素数の変更が容易
  2. 計算の自動化: calc()関数による動的な角度計算
  3. レスポンシブ対応: メディアクエリとの組み合わせで画面サイズに対応
  4. 保守性の向上: コードの再利用性と可読性が向上

この基本的な実装方法を理解することで、より複雑な円形レイアウトや動的なインタラクションを持つUIコンポーネントへの発展が可能になります。次章では、さらに精密な制御を可能にする三角関数を使った実装方法について詳しく解説していきます。

三角関数・カスタムプロパティで作る精密な円形レイアウト

CSS の進化により、三角関数を直接使用した精密な円形配置が可能になりました。従来の transform: rotate()translate() を組み合わせた手法と比較して、三角関数を使用することで数学的により正確で、より柔軟な制御が可能な円形レイアウトを実現できます。

この章では、CSS の sin() および cos() 関数を活用した高度な円形配置技術について詳しく解説します。これらの手法を習得することで、動的な要素数変更やアニメーション効果、レスポンシブ対応など、プロフェッショナルレベルの実装が可能になります。

CSS三角関数(sin, cos)を使った等間隔配置の計算ロジック

CSS の三角関数は比較的新しい機能ですが、Safari 15.4+、Chrome 111+、Firefox 108+ でサポートされており、モダンブラウザでの使用が可能です。三角関数を使った円形配置では、各要素の X, Y 座標を直接計算することで、より直感的で精密な配置制御を実現できます。

"sin()" | Can I use... Support tables for HTML5, CSS3, etc
"Can I use" provides up-to-date browser support tables for support of front-end web technologies on desktop and mobile web browsers.
"cos()" | Can I use... Support tables for HTML5, CSS3, etc
"Can I use" provides up-to-date browser support tables for support of front-end web technologies on desktop and mobile web browsers.

円形配置における座標計算の基本公式:

  • X座標 = 中心X + 半径 × cos(角度)
  • Y座標 = 中心Y + 半径 × sin(角度)

この数学的アプローチを CSS で実装する具体例を見てみましょう:

/* 三角関数を使った精密な円形配置 */
.trigonometric-circle {
  --item-count: 10; /* 配置する要素数 */
  --radius: 200px; /* 円の半径 */
  --center-x: 250px; /* 中心のX座標 */
  --center-y: 250px; /* 中心のY座標 */
  --item-size: 50px; /* 各要素のサイズ */

  position: relative;
  width: 500px;
  height: 500px;
  margin: 50px auto;
  border: 2px solid #e0e0e0;
  border-radius: 50%;
  background: radial-gradient(circle at center, #f8f9fa 0%, #e9ecef 100%);
}

.trig-item {
  position: absolute;
  width: var(--item-size);
  height: var(--item-size);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  font-size: 16px;
  text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

  /* 要素のサイズ分だけオフセット調整 */
  margin-left: calc(var(--item-size) / -2);
  margin-top: calc(var(--item-size) / -2);
}

/* 各要素の座標を三角関数で計算 */
.trig-item:nth-child(1) {
  --angle: calc(0 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.trig-item:nth-child(2) {
  --angle: calc(1 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}

.trig-item:nth-child(3) {
  --angle: calc(2 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}

.trig-item:nth-child(4) {
  --angle: calc(3 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}

.trig-item:nth-child(5) {
  --angle: calc(4 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}

.trig-item:nth-child(6) {
  --angle: calc(5 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}

.trig-item:nth-child(7) {
  --angle: calc(6 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}

.trig-item:nth-child(8) {
  --angle: calc(7 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
}

.trig-item:nth-child(9) {
  --angle: calc(8 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #d299c2 0%, #fef9d7 100%);
}

.trig-item:nth-child(10) {
  --angle: calc(9 * 360deg / var(--item-count));
  left: calc(var(--center-x) + var(--radius) * cos(var(--angle)));
  top: calc(var(--center-y) + var(--radius) * sin(var(--angle)));
  background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%);
}

/* ホバー効果 */
.trig-item:hover {
  transform: scale(1.2);
  z-index: 10;
  box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}

対応するHTMLコード:

<div class="trigonometric-circle">
  <div class="trig-item">1</div>
  <div class="trig-item">2</div>
  <div class="trig-item">3</div>
  <div class="trig-item">4</div>
  <div class="trig-item">5</div>
  <div class="trig-item">6</div>
  <div class="trig-item">7</div>
  <div class="trig-item">8</div>
  <div class="trig-item">9</div>
  <div class="trig-item">10</div>
</div>

See the Pen circle-layout-sample-03 by watashi-xyz (@watashi-xyz) on CodePen.

この実装の優位性:

  1. 数学的精度: 三角関数による正確な座標計算
  2. 直感的な理解: X, Y座標での直接的な配置指定
  3. ブラウザサポート: モダンブラウザでのネイティブサポート
  4. パフォーマンス: CSS エンジンレベルでの最適化

カスタムプロパティ・calc()で柔軟に制御する方法

三角関数とカスタムプロパティを組み合わせることで、極めて柔軟で保守性の高い円形配置システムを構築できます。この手法では、JavaScript を使わずに純粋な CSS だけで動的な制御を実現します。

/* 高度な制御システム */
.advanced-circular-system {
  /* グローバル設定 */
  --total-items: 12;
  --base-radius: 180px;
  --item-diameter: 45px;
  --animation-duration: 0.6s;
  --hover-scale: 1.3;
  --rotation-offset: 0deg; /* 全体の回転オフセット */

  /* 計算値 */
  --container-size: calc(var(--base-radius) * 2 + var(--item-diameter) + 40px);
  --center-point: calc(var(--container-size) / 2);
  --angle-step: calc(360deg / var(--total-items));

  position: relative;
  width: var(--container-size);
  height: var(--container-size);
  margin: 50px auto;

  /* 視覚的フィードバック */
  background:
    radial-gradient(circle at center, transparent 45%, rgba(100, 150, 250, 0.1) 50%),
    conic-gradient(from 0deg, transparent 0deg, rgba(100, 150, 250, 0.05) 30deg, transparent 60deg);
  border-radius: 50%;
}

.advanced-item {
  --item-index: 0; /* JavaScript または SCSS で動的に設定 */
  --current-angle: calc(var(--angle-step) * var(--item-index) + var(--rotation-offset));

  /* 座標計算 */
  --x-pos: calc(var(--center-point) + var(--base-radius) * cos(var(--current-angle)));
  --y-pos: calc(var(--center-point) + var(--base-radius) * sin(var(--current-angle)));

  position: absolute;
  left: var(--x-pos);
  top: var(--y-pos);
  width: var(--item-diameter);
  height: var(--item-diameter);
  margin: calc(var(--item-diameter) / -2) 0 0 calc(var(--item-diameter) / -2);

  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;

  /* スタイリング */
  background: linear-gradient(
    calc(var(--current-angle) + 45deg),
    hsl(calc(var(--item-index) * 30), 70%, 60%),
    hsl(calc(var(--item-index) * 30 + 60), 80%, 70%)
  );
  color: white;
  font-weight: bold;
  font-size: calc(var(--item-diameter) * 0.35);
  text-shadow: 1px 1px 2px rgba(0,0,0,0.4);
  box-shadow:
    0 4px 12px rgba(0,0,0,0.15),
    inset 0 1px 3px rgba(255,255,255,0.3);

  /* アニメーション */
  transition: all var(--animation-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55);
  cursor: pointer;
}

/* 各要素のインデックス設定 */
.advanced-item:nth-child(1) { --item-index: 0; }
.advanced-item:nth-child(2) { --item-index: 1; }
.advanced-item:nth-child(3) { --item-index: 2; }
.advanced-item:nth-child(4) { --item-index: 3; }
.advanced-item:nth-child(5) { --item-index: 4; }
.advanced-item:nth-child(6) { --item-index: 5; }
.advanced-item:nth-child(7) { --item-index: 6; }
.advanced-item:nth-child(8) { --item-index: 7; }
.advanced-item:nth-child(9) { --item-index: 8; }
.advanced-item:nth-child(10) { --item-index: 9; }
.advanced-item:nth-child(11) { --item-index: 10; }
.advanced-item:nth-child(12) { --item-index: 11; }

/* インタラクティブな効果 */
.advanced-item:hover {
  --hover-radius: calc(var(--base-radius) * 1.15);
  --x-pos: calc(var(--center-point) + var(--hover-radius) * cos(var(--current-angle)));
  --y-pos: calc(var(--center-point) + var(--hover-radius) * sin(var(--current-angle)));

  transform: scale(var(--hover-scale));
  z-index: 10;
  box-shadow:
    0 8px 25px rgba(0,0,0,0.25),
    inset 0 2px 6px rgba(255,255,255,0.4);
}

/* コンテナ全体の回転アニメーション */
.advanced-circular-system:hover {
  --rotation-offset: 30deg;
}

/* テーマバリエーション */
.theme-minimal {
  --base-radius: 150px;
  --item-diameter: 35px;
  --animation-duration: 0.4s;
}

.theme-large {
  --base-radius: 220px;
  --item-diameter: 55px;
  --animation-duration: 0.8s;
}

/* アクセシビリティ対応 */
@media (prefers-reduced-motion: reduce) {
  .advanced-item {
    transition: none;
  }

  .advanced-circular-system:hover {
    --rotation-offset: 0deg;
  }
}

SCSS/Sass を使用したより効率的な実装:

// SCSS版:ループを使った効率的な実装
.scss-circular-layout {
  --item-count: 8;
  --radius: 160px;
  --item-size: 50px;

  $item-count: 8; // SCSS変数として定義

  position: relative;
  width: calc(var(--radius) * 2 + var(--item-size) + 20px);
  height: calc(var(--radius) * 2 + var(--item-size) + 20px);
  margin: 50px auto;

  .scss-item {
    position: absolute;
    width: var(--item-size);
    height: var(--item-size);
    border-radius: 50%;

    // ループで各要素の位置を計算
    @for $i from 1 through $item-count {
      &:nth-child(#{$i}) {
        --index: #{$i - 1};
        --angle: calc(360deg / var(--item-count) * var(--index));

        left: calc(50% + var(--radius) * cos(var(--angle)));
        top: calc(50% + var(--radius) * sin(var(--angle)));
        margin: calc(var(--item-size) / -2);

        background: hsl(calc(360deg / #{$item-count} * #{$i - 1}), 70%, 60%);
      }
    }
  }
}

See the Pen circle-layout-sample-04 by watashi-xyz (@watashi-xyz) on CodePen.

動的な要素数やレスポンシブ対応のテクニック

実用的な円形配置では、要素数の動的変更やデバイスサイズに応じた適応的なレイアウトが重要です。CSS単体での限界を理解しつつ、JavaScriptとの連携も含めた包括的なアプローチを紹介します。

/* レスポンシブ対応の円形配置システム */
.responsive-circular {
  /* デフォルト設定(デスクトップ) */
  --items: 12;
  --radius: 200px;
  --item-size: 50px;
  --spacing-factor: 1;

  /* 計算値 */
  --effective-radius: calc(var(--radius) * var(--spacing-factor));
  --container-size: calc(var(--effective-radius) * 2 + var(--item-size) + 40px);

  position: relative;
  width: var(--container-size);
  height: var(--container-size);
  margin: 20px auto;
  transition: all 0.3s ease;
}

.responsive-item {
  --index: 0;
  --angle: calc(360deg / var(--items) * var(--index));
  --x: calc(50% + var(--effective-radius) * cos(var(--angle)));
  --y: calc(50% + var(--effective-radius) * sin(var(--angle)));

  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--item-size);
  height: var(--item-size);
  margin: calc(var(--item-size) / -2);

  border-radius: 50%;
  background: conic-gradient(
    from var(--angle),
    hsl(calc(var(--index) * 360deg / var(--items)), 70%, 65%),
    hsl(calc(var(--index) * 360deg / var(--items) + 60deg), 80%, 75%)
  );

  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  font-size: calc(var(--item-size) * 0.3);

  transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  cursor: pointer;
}

/* タブレット対応(768px以下) */
@media (max-width: 768px) {
  .responsive-circular {
    --radius: 140px;
    --item-size: 40px;
    --spacing-factor: 0.9;
  }

  /* 要素数を減らす場合の制御 */
  .responsive-item:nth-child(n+9) {
    display: none; /* 9個目以降を非表示 */
  }

  .responsive-circular {
    --items: 8; /* 表示要素数を8個に調整 */
  }
}

/* スマートフォン対応(480px以下) */
@media (max-width: 480px) {
  .responsive-circular {
    --radius: 100px;
    --item-size: 35px;
    --spacing-factor: 0.8;
  }

  .responsive-item:nth-child(n+7) {
    display: none; /* 6個まで表示 */
  }

  .responsive-circular {
    --items: 6;
  }
}

/* 極小画面対応(320px以下) */
@media (max-width: 320px) {
  .responsive-circular {
    --radius: 70px;
    --item-size: 28px;
    --spacing-factor: 0.7;
  }

  .responsive-item:nth-child(n+5) {
    display: none; /* 4個まで表示 */
  }

  .responsive-circular {
    --items: 4;
  }
}

/* 高解像度画面対応 */
@media (min-width: 1200px) {
  .responsive-circular {
    --radius: 250px;
    --item-size: 60px;
    --spacing-factor: 1.1;
  }
}

/* ダークモード対応 */
@media (prefers-color-scheme: dark) {
  .responsive-circular {
    background: radial-gradient(circle, rgba(30, 30, 30, 0.1) 0%, transparent 70%);
  }

  .responsive-item {
    box-shadow:
      0 4px 12px rgba(0,0,0,0.4),
      inset 0 1px 3px rgba(255,255,255,0.1);
  }
}

JavaScript を使った動的制御の例:

// 動的な要素数制御
class DynamicCircularLayout {
  constructor(container, options = {}) {
    this.container = container;
    this.options = {
      maxItems: 16,
      minItems: 3,
      baseRadius: 180,
      itemSize: 45,
      ...options
    };

    this.updateLayout();
  }

  // 要素数に応じてCSS変数を更新
  updateLayout() {
    const itemCount = this.container.children.length;
    const clampedCount = Math.max(
      this.options.minItems,
      Math.min(this.options.maxItems, itemCount)
    );

    // CSS変数を動的に設定
    this.container.style.setProperty('--items', clampedCount);
    this.container.style.setProperty('--radius', `${this.options.baseRadius}px`);
    this.container.style.setProperty('--item-size', `${this.options.itemSize}px`);

    // 各要素にインデックスを設定
    Array.from(this.container.children).forEach((item, index) => {
      if (index < clampedCount) {
        item.style.setProperty('--index', index);
        item.style.display = 'flex';
      } else {
        item.style.display = 'none';
      }
    });
  }

  // 要素の追加
  addItem(content) {
    const item = document.createElement('div');
    item.className = 'responsive-item';
    item.textContent = content;
    this.container.appendChild(item);
    this.updateLayout();
  }

  // 要素の削除
  removeItem(index) {
    if (this.container.children[index]) {
      this.container.children[index].remove();
      this.updateLayout();
    }
  }

  // レスポンシブ対応の再計算
  handleResize() {
    const width = window.innerWidth;
    let newRadius = this.options.baseRadius;

    if (width <= 480) {
      newRadius = this.options.baseRadius * 0.6;
    } else if (width <= 768) {
      newRadius = this.options.baseRadius * 0.8;
    } else if (width >= 1200) {
      newRadius = this.options.baseRadius * 1.2;
    }

    this.container.style.setProperty('--radius', `${newRadius}px`);
  }
}

// 使用例
document.addEventListener('DOMContentLoaded', () => {
  const circularContainer = document.querySelector('.responsive-circular');
  const layout = new DynamicCircularLayout(circularContainer);

  // リサイズイベントの監視
  window.addEventListener('resize', () => layout.handleResize());
});

See the Pen circle-layout-sample-05 by watashi-xyz (@watashi-xyz) on CodePen.

パフォーマンス最適化のベストプラクティス:

  1. CSS変数の活用: 再計算の最小化
  2. will-change プロパティ: アニメーション最適化
  3. transform の使用: リフローの回避
  4. 適切なz-index管理: 重なり順序の制御
/* パフォーマンス最適化 */
.optimized-item {
  will-change: transform, opacity;
  transform: translateZ(0); /* ハードウェアアクセラレーション */
  backface-visibility: hidden; /* 裏面の描画を無効化 */
}

この高度な実装により、あらゆるデバイスと要求に対応できる、プロフェッショナルレベルの円形配置システムを構築できます。次章では、これらの技術を活用したアニメーション効果とインタラクティブな表現について詳しく解説していきます。

CSSアニメーションで魅せる!円形軌道とインタラクティブ表現

円形配置の要素にアニメーションを加えることで、静的なレイアウトを動的で魅力的なインターフェースに変身させることができます。ここでは、CSSアニメーションを活用した円形軌道の実装方法と、ユーザーの操作に反応するインタラクティブな表現について詳しく解説します。

@keyframes と transform を使った「円形軌道アニメーション」

円形軌道上を要素が移動するアニメーションは、ウェブサイトに動きと視覚的な興味を加える効果的な手法です。まずは基本的な円形軌道アニメーションから実装してみましょう。

基本的な円形軌道アニメーションの実装

円形軌道アニメーションの核心は、要素を中心点を軸に回転させることです。以下のコードは、単一の要素が円軌道を描いて動き続けるアニメーションの基本構造です。

/* 円形軌道アニメーションの基本構造 */
.orbit-container {
  position: relative;
  width: 300px;
  height: 300px;
  margin: 50px auto;
}

.orbit-path {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 200px;
  height: 200px;
  transform: translate(-50%, -50%);
  animation: rotate 10s linear infinite;
}

.orbit-item {
  position: absolute;
  top: 0;
  left: 50%;
  width: 40px;
  height: 40px;
  background: #3498db;
  border-radius: 50%;
  transform: translateX(-50%);
  /* 要素自体の逆回転で常に正立を維持 */
  animation: counter-rotate 10s linear infinite;
}

@keyframes rotate {
  from {
    transform: translate(-50%, -50%) rotate(0deg);
  }
  to {
    transform: translate(-50%, -50%) rotate(360deg);
  }
}

@keyframes counter-rotate {
  from {
    transform: translateX(-50%) rotate(0deg);
  }
  to {
    transform: translateX(-50%) rotate(-360deg);
  }
}

<!-- HTML構造 -->
<div class="orbit-container">
  <div class="orbit-path">
    <div class="orbit-item"></div>
  </div>
</div>

See the Pen circle-layout-sample-06 by watashi-xyz (@watashi-xyz) on CodePen.

このコードでは、.orbit-pathが円軌道の半径を定義し、回転アニメーションを適用しています。.orbit-itemは軌道上を移動する要素で、逆回転アニメーションにより常に正立状態を保ちます。

複数要素の円形軌道アニメーション

複数の要素を異なるタイミングで円軌道上に配置することで、より複雑で視覚的に魅力的なアニメーションを作成できます。

/* 複数要素の円形軌道アニメーション */
.multi-orbit-container {
  position: relative;
  width: 400px;
  height: 400px;
  margin: 50px auto;
}

.orbit-group {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 250px;
  height: 250px;
  transform: translate(-50%, -50%);
  animation: orbit-rotate 15s linear infinite;
}

.orbit-element {
  position: absolute;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  animation: element-counter-rotate 15s linear infinite;
}

/* 各要素の初期位置とスタイル */
.orbit-element:nth-child(1) {
  top: 0;
  left: 50%;
  background: #e74c3c;
  transform: translateX(-50%);
}

.orbit-element:nth-child(2) {
  top: 50%;
  right: 0;
  background: #f39c12;
  transform: translateY(-50%);
  animation-delay: -3.75s; /* 1/4周分の遅延 */
}

.orbit-element:nth-child(3) {
  bottom: 0;
  left: 50%;
  background: #2ecc71;
  transform: translateX(-50%);
  animation-delay: -7.5s; /* 1/2周分の遅延 */
}

.orbit-element:nth-child(4) {
  top: 50%;
  left: 0;
  background: #9b59b6;
  transform: translateY(-50%);
  animation-delay: -11.25s; /* 3/4周分の遅延 */
}

@keyframes orbit-rotate {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to { transform: translate(-50%, -50%) rotate(360deg); }
}

@keyframes element-counter-rotate {
  from { transform: translateX(-50%) rotate(0deg); }
  to { transform: translateX(-50%) rotate(-360deg); }
}

See the Pen circle-layout-sample-07 by watashi-xyz (@watashi-xyz) on CodePen.

hoverやクリック時に反応するインタラクティブな円形メニュー

ユーザーの操作に反応する円形メニューは、モダンなウェブデザインでよく使用される効果的なUI要素です。ここでは、hover時に展開する円形メニューの実装方法を詳しく解説します。

展開式円形メニューの基本実装

/* インタラクティブ円形メニューの基本構造 */
.circular-menu {
  position: relative;
  width: 80px;
  height: 80px;
  margin: 100px auto;
}

.menu-toggle {
  width: 80px;
  height: 80px;
  background: #3498db;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 24px;
  transition: all 0.3s ease;
  position: relative;
  z-index: 10;
}

.menu-toggle:hover {
  background: #2980b9;
  transform: scale(1.1);
}

.menu-items {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.menu-item {
  position: absolute;
  width: 50px;
  height: 50px;
  background: #e74c3c;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  text-decoration: none;
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0);
}

/* ホバー時のメニュー展開 */
.circular-menu:hover .menu-item {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

/* 各メニューアイテムの位置計算(8方向配置) */
.circular-menu:hover .menu-item:nth-child(1) {
  transform: translate(-50%, -50%) translateY(-100px) scale(1);
  transition-delay: 0.1s;
}

.circular-menu:hover .menu-item:nth-child(2) {
  transform: translate(-50%, -50%) translate(71px, -71px) scale(1);
  transition-delay: 0.15s;
}

.circular-menu:hover .menu-item:nth-child(3) {
  transform: translate(-50%, -50%) translateX(100px) scale(1);
  transition-delay: 0.2s;
}

.circular-menu:hover .menu-item:nth-child(4) {
  transform: translate(-50%, -50%) translate(71px, 71px) scale(1);
  transition-delay: 0.25s;
}

.circular-menu:hover .menu-item:nth-child(5) {
  transform: translate(-50%, -50%) translateY(100px) scale(1);
  transition-delay: 0.3s;
}

.circular-menu:hover .menu-item:nth-child(6) {
  transform: translate(-50%, -50%) translate(-71px, 71px) scale(1);
  transition-delay: 0.35s;
}

.circular-menu:hover .menu-item:nth-child(7) {
  transform: translate(-50%, -50%) translateX(-100px) scale(1);
  transition-delay: 0.4s;
}

.circular-menu:hover .menu-item:nth-child(8) {
  transform: translate(-50%, -50%) translate(-71px, -71px) scale(1);
  transition-delay: 0.45s;
}

.menu-item:hover {
  background: #c0392b;
  transform: translate(-50%, -50%) translateY(-100px) scale(1.2) !important;
}

<!-- インタラクティブ円形メニューのHTML -->
<div class="circular-menu">
  <div class="menu-toggle">☰</div>
  <div class="menu-items">
    <a href="#" class="menu-item">🏠</a>
    <a href="#" class="menu-item">👤</a>
    <a href="#" class="menu-item">💼</a>
    <a href="#" class="menu-item">📱</a>
    <a href="#" class="menu-item">✉️</a>
    <a href="#" class="menu-item">⚙️</a>
    <a href="#" class="menu-item">❓</a>
    <a href="#" class="menu-item">📊</a>
  </div>
</div>

See the Pen circle-layout-sample-08 by watashi-xyz (@watashi-xyz) on CodePen.

クリック操作による円形メニューの制御

より実用的なインターフェースとして、クリック操作でメニューの開閉を制御する実装も重要です。JavaScriptと組み合わせることで、より細かい制御が可能になります。

<!-- インタラクティブ円形メニューのHTML -->
<div class="click-circular-menu">
  <div class="click-menu-toggle">☰</div>
  <div class="click-menu-items">
    <a href="#" class="menu-item">🏠</a>
    <a href="#" class="menu-item">👤</a>
    <a href="#" class="menu-item">💼</a>
    <a href="#" class="menu-item">📱</a>
    <a href="#" class="menu-item">✉️</a>
    <a href="#" class="menu-item">⚙️</a>
    <a href="#" class="menu-item">❓</a>
    <a href="#" class="menu-item">📊</a>
  </div>
</div>
/* クリック制御用の円形メニュー */
.click-circular-menu {
  position: relative;
  width: 80px;
  height: 80px;
  margin: 100px auto;
}

.click-menu-toggle {
  width: 80px;
  height: 80px;
  background: #9b59b6;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 24px;
  transition: all 0.3s ease;
  position: relative;
  z-index: 10;
  border: none;
  outline: none;
}

.click-menu-toggle.active {
  background: #8e44ad;
  transform: rotate(45deg);
}

.click-menu-items .menu-item {
  position: absolute;
  width: 45px;
  height: 45px;
  background: #e67e22;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  text-decoration: none;
  transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0) rotate(180deg);
  top: 50%;
  left: 50%;
}

/* アクティブ状態でのメニュー展開 */
.click-circular-menu.active .menu-item {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1) rotate(0deg);
}

/* 各アイテムの配置(CSS変数を使った動的配置) */
.click-circular-menu.active .menu-item:nth-child(1) {
  transform: translate(-50%, -50%) translateY(-90px) scale(1) rotate(0deg);
  transition-delay: 0.1s;
}

.click-circular-menu.active .menu-item:nth-child(2) {
  transform: translate(-50%, -50%) translate(64px, -64px) scale(1) rotate(0deg);
  transition-delay: 0.15s;
}

.click-circular-menu.active .menu-item:nth-child(3) {
  transform: translate(-50%, -50%) translateX(90px) scale(1) rotate(0deg);
  transition-delay: 0.2s;
}

.click-circular-menu.active .menu-item:nth-child(4) {
  transform: translate(-50%, -50%) translate(64px, 64px) scale(1) rotate(0deg);
  transition-delay: 0.25s;
}

.click-circular-menu.active .menu-item:nth-child(5) {
  transform: translate(-50%, -50%) translateY(90px) scale(1) rotate(0deg);
  transition-delay: 0.3s;
}

.click-circular-menu.active .menu-item:nth-child(6) {
  transform: translate(-50%, -50%) translate(-64px, 64px) scale(1) rotate(0deg);
  transition-delay: 0.35s;
}


.click-circular-menu.active .menu-item:nth-child(7) {
  transform: translate(-50%, -50%) translateX(-90px) scale(1) rotate(0deg);
  transition-delay: 0.4s;
}

.click-circular-menu.active .menu-item:nth-child(8) {
  transform: translate(-50%, -50%) translate(-64px, -64px) scale(1) rotate(0deg);
  transition-delay: 0.45s;
}
// クリック制御のJavaScript
document.addEventListener('DOMContentLoaded', function() {
  const menuToggle = document.querySelector('.click-menu-toggle');
  const circularMenu = document.querySelector('.click-circular-menu');

  menuToggle.addEventListener('click', function() {
    circularMenu.classList.toggle('active');
    menuToggle.classList.toggle('active');
  });

  // メニュー外をクリックした時の閉じる処理
  document.addEventListener('click', function(e) {
    if (!circularMenu.contains(e.target)) {
      circularMenu.classList.remove('active');
      menuToggle.classList.remove('active');
    }
  });
});

See the Pen circle-layout-sample-09 by watashi-xyz (@watashi-xyz) on CodePen.

レスポンシブ対応のアニメーション付き円形UIの実装方法

モバイルデバイスからデスクトップまで、様々な画面サイズに対応した円形UIの実装は現代のウェブ開発において不可欠です。ここでは、レスポンシブデザインとアニメーションを両立させる手法を解説します。

CSS Grid とメディアクエリを活用したレスポンシブ円形レイアウト

/* レスポンシブ円形UIの基本構造 */
.responsive-circular-ui {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 20px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.responsive-circle-container {
  position: relative;
  width: min(80vw, 500px);
  height: min(80vw, 500px);
  max-width: 500px;
  max-height: 500px;
}

.center-element {
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(80px, 15vw, 120px);
  height: clamp(80px, 15vw, 120px);
  background: white;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(1rem, 3vw, 1.5rem);
  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  z-index: 5;
}

.orbital-item {
  position: absolute;
  width: clamp(40px, 8vw, 60px);
  height: clamp(40px, 8vw, 60px);
  background: white;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(0.8rem, 2vw, 1.2rem);
  box-shadow: 0 5px 15px rgba(0,0,0,0.1);
  transition: all 0.3s ease;
  animation: gentle-pulse 3s ease-in-out infinite alternate;
}

.orbital-item:hover {
  transform: scale(1.2);
  box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}

/* CSS変数を使った動的配置(レスポンシブ対応) */
:root {
  --radius: min(35vw, 200px);
  --item-count: 8;
}

.orbital-item:nth-child(2) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 0));
  animation-delay: 0s;
}

.orbital-item:nth-child(3) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 1));
  animation-delay: 0.2s;
}

.orbital-item:nth-child(4) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 2));
  animation-delay: 0.4s;
}

.orbital-item:nth-child(5) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 3));
  animation-delay: 0.6s;
}

.orbital-item:nth-child(6) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 4));
  animation-delay: 0.8s;
}

.orbital-item:nth-child(7) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 5));
  animation-delay: 1s;
}

.orbital-item:nth-child(8) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 6));
  animation-delay: 1.2s;
}

.orbital-item:nth-child(9) {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
             translate(0, calc(-1 * var(--radius)))
             rotate(calc(360deg / var(--item-count) * 7));
  animation-delay: 1.4s;
}

/* アニメーション定義 */
@keyframes gentle-pulse {
  0% {
    transform: translate(-50%, -50%)
               translate(0, calc(-1 * var(--radius)))
               rotate(var(--rotation))
               scale(1);
  }
  100% {
    transform: translate(-50%, -50%)
               translate(0, calc(-1 * var(--radius)))
               rotate(var(--rotation))
               scale(1.05);
  }
}

/* メディアクエリによる詳細調整 */
@media (max-width: 768px) {
  :root {
    --radius: 40vw;
  }

  .responsive-circle-container {
    width: 90vw;
    height: 90vw;
  }

  .orbital-item {
    font-size: 0.9rem;
  }

  /* モバイルでのタッチ操作を考慮したホバー効果の調整 */
  .orbital-item:hover {
    transform: scale(1.1);
  }
}

@media (max-width: 480px) {
  :root {
    --radius: 45vw;
  }

  .center-element {
    font-size: 1rem;
  }

  .orbital-item {
    width: 35px;
    height: 35px;
    font-size: 0.8rem;
  }
}

/* ダークモード対応 */
@media (prefers-color-scheme: dark) {
  .responsive-circular-ui {
    background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
  }

  .center-element,
  .orbital-item {
    background: #34495e;
    color: white;
  }
}

/* 動作軽減設定への対応 */
@media (prefers-reduced-motion: reduce) {
  .orbital-item {
    animation: none;
  }

  .orbital-item:hover {
    transform: none;
  }
}

この実装では、CSS変数(カスタムプロパティ)とclamp()関数を活用することで、画面サイズに応じて要素のサイズと配置が自動的に調整されます。また、アクセシビリティを考慮したprefers-reduced-motionメディアクエリや、ダークモード対応も含まれています。

円形配置にアニメーションを組み合わせることで、単なる静的なレイアウトを超えた、ユーザーエンゲージメントを高める動的なインターフェースを作成できます。次のセクションでは、これらの技術を活用した実践的なデザインパターンとアイデア集について詳しく解説していきます。

実践的な円形配置:円形配置のデザインパターンとアイデア集

円形ナビゲーションメニューの作り方(アイコン8個のUI)

円形ナビゲーションメニューは、スペースを効率的に活用しながら視覚的に美しいUIを提供する手法として、多くのWebサイトで採用されています。特に8個のアイコンを円形に配置したメニューは、ユーザビリティと美しさを両立する理想的なデザインパターンです。

円形ナビゲーションメニューを実装する際の基本的な考え方は、中央にメインボタン(ハンバーガーメニューなど)を配置し、その周囲に各機能へのアクセスポイントとなるアイコンを等間隔で配置することです。8個のアイコンを配置する場合、360度を8で割った45度間隔で要素を配置することになります。

以下は、実用的な円形ナビゲーションメニューの完全なコード例です:

<div class="circular-nav">
  <div class="nav-center">
    <button class="nav-toggle" onclick="toggleNav()">☰</button>
  </div>
  <nav class="nav-items">
    <a href="#home" class="nav-item" data-index="0">🏠</a>
    <a href="#about" class="nav-item" data-index="1">👤</a>
    <a href="#services" class="nav-item" data-index="2">⚙️</a>
    <a href="#portfolio" class="nav-item" data-index="3">💼</a>
    <a href="#blog" class="nav-item" data-index="4">📝</a>
    <a href="#contact" class="nav-item" data-index="5">📞</a>
    <a href="#gallery" class="nav-item" data-index="6">🖼️</a>
    <a href="#shop" class="nav-item" data-index="7">🛒</a>
  </nav>
</div>

.circular-nav {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 200px;
  height: 200px;
  z-index: 1000;
}

.nav-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
}

.nav-toggle {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: none;
  color: white;
  font-size: 24px;
  cursor: pointer;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  transition: all 0.3s ease;
}

.nav-toggle:hover {
  transform: scale(1.1);
  box-shadow: 0 6px 25px rgba(0, 0, 0, 0.4);
}

.nav-items {
  position: relative;
  width: 100%;
  height: 100%;
}

.nav-item {
  position: absolute;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: white;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  font-size: 20px;
  box-shadow: 0 2px 15px rgba(0, 0, 0, 0.2);
  transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  opacity: 0;
  transform: scale(0) translate(-50%, -50%);
  transform-origin: center;
}

/* 各アイテムの配置計算 */
.nav-item[data-index="0"] {
  top: 10px;
  left: 50%;
  transform: translateX(-50%) scale(0);
}
.nav-item[data-index="1"] {
  top: 25px;
  right: 25px;
  transform: scale(0);
}
.nav-item[data-index="2"] {
  top: 50%;
  right: 10px;
  transform: translateY(-50%) scale(0);
}
.nav-item[data-index="3"] {
  bottom: 25px;
  right: 25px;
  transform: scale(0);
}
.nav-item[data-index="4"] {
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%) scale(0);
}
.nav-item[data-index="5"] {
  bottom: 25px;
  left: 25px;
  transform: scale(0);
}
.nav-item[data-index="6"] {
  top: 50%;
  left: 10px;
  transform: translateY(-50%) scale(0);
}
.nav-item[data-index="7"] {
  top: 25px;
  left: 25px;
  transform: scale(0);
}

/* アクティブ状態のスタイル */
.circular-nav.active .nav-item {
  opacity: 1;
  transform: scale(1);
}

.circular-nav.active .nav-item[data-index="0"] {
  transform: translateX(-50%) scale(1);
}
.circular-nav.active .nav-item[data-index="2"] {
  transform: translateY(-50%) scale(1);
}
.circular-nav.active .nav-item[data-index="4"] {
  transform: translateX(-50%) scale(1);
}
.circular-nav.active .nav-item[data-index="6"] {
  transform: translateY(-50%) scale(1);
}

.nav-item:hover {
  transform: scale(1.2);
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

function toggleNav() {
  const nav = document.querySelector('.circular-nav');
  nav.classList.toggle('active');

  // アニメーション遅延を追加
  const items = document.querySelectorAll('.nav-item');
  if (nav.classList.contains('active')) {
    items.forEach((item, index) => {
      setTimeout(() => {
        item.style.transitionDelay = `${index * 0.1}s`;
      }, 0);
    });
  } else {
    items.forEach((item, index) => {
      item.style.transitionDelay = `${(7 - index) * 0.05}s`;
    });
  }
}

See the Pen circle-layout-sample-10 by watashi-xyz (@watashi-xyz) on CodePen.

このコードでは、CSS transformプロパティを使用して各アイテムを円形に配置し、JavaScriptでインタラクティブな動作を制御しています。アニメーション効果により、ユーザーエクスペリエンスが向上し、プロフェッショナルな印象を与えることができます。

メンバー紹介やギャラリーに最適!円形カードの活用例

円形配置は、チームメンバーの紹介やギャラリー表示において、従来の格子状レイアウトでは表現できない独特の美しさと視覚的なインパクトを提供します。特に人物写真やアート作品などの表示において、円形配置は自然で調和のとれた印象を演出できます。

メンバー紹介での円形配置は、チームの結束感や一体感を視覚的に表現する効果があります。中央にチーム名やロゴを配置し、その周囲にメンバーの写真を円形に配置することで、階層的でありながら平等性も感じられるデザインを実現できます。

以下は、6人のチームメンバーを円形に配置するカード型レイアウトの実装例です:

<section class="team-circle">
  <div class="team-center">
    <h2>わたしたちのチーム</h2>
    <p>Creative Professionals</p>
  </div>
  <div class="member-container">
    <div class="member-card" data-member="0">
      <div class="member-image">
        <img src="//robohash.org/boss" alt="">
      </div>
      <div class="member-info">
        <h3>ヒンメル</h3>
        <p>勇者</p>
      </div>
    </div>
    <!-- 他のメンバーカードも同様に配置 -->
    <div class="member-card" data-member="1">
      <div class="member-image">
        <img src="//robohash.org/seed" alt="">
      </div>
      <div class="member-info">
        <h3>ハイター</h3>
        <p>僧侶</p>
      </div>
    </div>
    <div class="member-card" data-member="2">
      <div class="member-image">
        <img src="//robohash.org/foo" alt="">
      </div>
      <div class="member-info">
        <h3>アイゼン</h3>
        <p>戦士</p>
      </div>
    </div>
    <div class="member-card" data-member="3">
      <div class="member-image">
        <img src="//robohash.org/bar" alt="">
      </div>
      <div class="member-info">
        <h3>フリーレン</h3>
        <p>魔法使い</p>
        <div class="member-social">
          <a href="#" aria-label="GitHub">Github</a>
          <a href="#" aria-label="Twitter">X(twitter)</a>
        </div>
      </div>
    </div>
    <div class="member-card" data-member="4">
      <div class="member-image">
        <img src="//robohash.org/hoge" alt="">
      </div>
      <div class="member-info">
        <h3>フェルン</h3>
        <p>魔法使い</p>
        <div class="member-social">
          <a href="#" aria-label="GitHub">Github</a>
          <a href="#" aria-label="Twitter">X(twiiter)</a>
        </div>
      </div>
    </div>
    <div class="member-card" data-member="5">
      <div class="member-image">
        <img src="//robohash.org/fuga" alt="">
      </div>
      <div class="member-info">
        <h3>シュタルク</h3>
        <p>戦士</p>
        <div class="member-social">
          <a href="#" aria-label="GitHub">Github</a>
          <a href="#" aria-label="Twitter">X(twitter)</a>
        </div>
      </div>
    </div>
  </div>
</section>

.team-circle {
  position: relative;
  width: 100%;
  max-width: 800px;
  height: 600px;
  margin: 80px auto;
  padding: 40px;
}

.team-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  background: rgba(255, 255, 255, 0.95);
  padding: 30px;
  border-radius: 20px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(10px);
  z-index: 2;
}

.team-center h2 {
  margin: 0 0 10px 0;
  color: #333;
  font-size: 28px;
  font-weight: 700;
}

.team-center p {
  margin: 0;
  color: #666;
  font-size: 16px;
}

.member-container {
  position: relative;
  width: 100%;
  height: 100%;
}

.member-card {
  position: absolute;
  width: 140px;
  height: 180px;
  background: white;
  border-radius: 15px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  overflow: hidden;
  cursor: pointer;
}

/* 円形配置の計算 - 6人の場合は60度間隔 */
.member-card[data-member="0"] {
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
}
.member-card[data-member="1"] {
  top: 80px;
  right: 40px;
}
.member-card[data-member="2"] {
  bottom: 80px;
  right: 40px;
}
.member-card[data-member="3"] {
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
}
.member-card[data-member="4"] {
  bottom: 80px;
  left: 40px;
}
.member-card[data-member="5"] {
  top: 80px;
  left: 40px;
}

.member-image {
  width: 100%;
  height: 100px;
  overflow: hidden;
  position: relative;
}

.member-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.member-info {
  padding: 15px;
  text-align: center;
}

.member-info h3 {
  margin: 0 0 5px 0;
  font-size: 14px;
  font-weight: 600;
  color: #333;
}

.member-info p {
  margin: 0 0 10px 0;
  font-size: 12px;
  color: #666;
}

.member-social {
  display: flex;
  justify-content: center;
  gap: 8px;
}

.member-social a {
  text-decoration: none;
  font-size: 16px;
  transition: transform 0.2s ease;
}

.member-social a:hover {
  transform: scale(1.2);
}

/* ホバー効果 */
.member-card:hover {
  transform: translateY(-10px) scale(1.05);
  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2);
  z-index: 3;
}

.member-card:hover .member-image img {
  transform: scale(1.1);
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
  .team-circle {
    height: 500px;
    padding: 20px;
  }

  .member-card {
    width: 120px;
    height: 160px;
  }

  .team-center {
    padding: 20px;
  }

  .team-center h2 {
    font-size: 24px;
  }
}

See the Pen circle-layout-sample-11 by watashi-xyz (@watashi-xyz) on CodePen.

このレイアウトでは、中央の情報エリアを基準として、計算された位置に各メンバーカードを配置しています。ホバー効果やアニメーションにより、インタラクティブ性も向上させています。

ギャラリー表示においても同様のアプローチが効果的です。作品や商品画像を円形に配置することで、ユーザーの視線を自然に誘導し、すべての要素に均等な注目を集めることができます。

clip-pathやconic-gradientで表現するクリエイティブな円形デザイン

CSS の先進的な機能である clip-pathconic-gradient を活用することで、従来の円形配置を超えた、より創造的で視覚的にインパクトのあるデザインを実現できます。これらのプロパティは、円形配置の概念を拡張し、幾何学的な美しさと機能性を両立した革新的なUIを構築する可能性を提供します。

clip-path プロパティは、要素の表示領域を任意の形状にクリッピングする機能で、円形配置された要素に独特の視覚効果を加えることができます。一方、conic-gradient は円錐勾配を作成する機能で、円形配置のアクセント効果や背景パターンとして極めて効果的です。

以下は、これらの機能を組み合わせた創造的な円形デザインの実装例です:

<div class="creative-circle-container">
  <div class="gradient-background"></div>
  <div class="clipped-sections">
    <div class="section" data-section="0">
      <div class="section-content">
        <h3>Design</h3>
        <p>創造的なデザイン</p>
      </div>
    </div>
    <div class="section" data-section="1">
      <div class="section-content">
        <h3>Development</h3>
        <p>技術的な実装</p>
      </div>
    </div>
    <div class="section" data-section="2">
      <div class="section-content">
        <h3>Strategy</h3>
        <p>戦略的思考</p>
      </div>
    </div>
    <div class="section" data-section="3">
      <div class="section-content">
        <h3>Marketing</h3>
        <p>効果的な宣伝</p>
      </div>
    </div>
    <div class="section" data-section="4">
      <div class="section-content">
        <h3>Analytics</h3>
        <p>データ分析</p>
      </div>
    </div>
    <div class="section" data-section="5">
      <div class="section-content">
        <h3>Support</h3>
        <p>継続的サポート</p>
      </div>
    </div>
  </div>
  <div class="center-hub">
    <div class="hub-content">
      <h2>Services</h2>
      <p>総合的なソリューション</p>
    </div>
  </div>
</div>

.creative-circle-container {
  position: relative;
  width: 500px;
  height: 500px;
  margin: 50px auto;
  border-radius: 50%;
  overflow: hidden;
}

/* コニック勾配を使用した背景 */
.gradient-background {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: conic-gradient(
    from 0deg,
    #ff6b6b 0deg 60deg,
    #4ecdc4 60deg 120deg,
    #45b7d1 120deg 180deg,
    #96ceb4 180deg 240deg,
    #ffeaa7 240deg 300deg,
    #dda0dd 300deg 360deg
  );
  animation: rotate 20s linear infinite;
}

@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.clipped-sections {
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 2;
}

.section {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.4s ease;
  cursor: pointer;
}

/* clip-pathを使用した扇形のクリッピング */
.section[data-section="0"] {
  clip-path: polygon(50% 50%, 50% 0%, 93.3% 25%);
  background: rgba(255, 107, 107, 0.9);
}
.section[data-section="1"] {
  clip-path: polygon(50% 50%, 93.3% 25%, 93.3% 75%);
  background: rgba(78, 205, 196, 0.9);
}
.section[data-section="2"] {
  clip-path: polygon(50% 50%, 93.3% 75%, 50% 100%);
  background: rgba(69, 183, 209, 0.9);
}
.section[data-section="3"] {
  clip-path: polygon(50% 50%, 50% 100%, 6.7% 75%);
  background: rgba(150, 206, 180, 0.9);
}
.section[data-section="4"] {
  clip-path: polygon(50% 50%, 6.7% 75%, 6.7% 25%);
  background: rgba(255, 234, 167, 0.9);
}
.section[data-section="5"] {
  clip-path: polygon(50% 50%, 6.7% 25%, 50% 0%);
  background: rgba(221, 160, 221, 0.9);
}

.section-content {
  text-align: center;
  color: white;
  font-weight: bold;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  transform: rotate(0deg);
  padding: 20px;
}

/* 各セクションのコンテンツ回転調整 */
.section[data-section="0"] .section-content {
  transform: translateY(-80px) rotate(0deg);
}
.section[data-section="1"] .section-content {
  transform: translateX(80px) rotate(0deg);
}
.section[data-section="2"] .section-content {
  transform: translateX(60px) translateY(60px) rotate(0deg);
}
.section[data-section="3"] .section-content {
  transform: translateY(80px) rotate(0deg);
}
.section[data-section="4"] .section-content {
  transform: translateX(-60px) translateY(60px) rotate(0deg);
}
.section[data-section="5"] .section-content {
  transform: translateX(-80px) rotate(0deg);
}

.section h3 {
  margin: 0 0 8px 0;
  font-size: 18px;
  font-weight: 700;
}

.section p {
  margin: 0;
  font-size: 12px;
  opacity: 0.9;
}

/* ホバー効果 */
.section:hover {
  transform: scale(1.05);
  z-index: 3;
}

.section:hover .section-content {
  transform: scale(1.1);
}

/* 中央のハブ */
.center-hub {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 180px;
  height: 180px;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(240, 240, 240, 0.95));
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  backdrop-filter: blur(10px);
  z-index: 4;
}

.hub-content {
  text-align: center;
  color: #333;
}

.hub-content h2 {
  margin: 0 0 8px 0;
  font-size: 24px;
  font-weight: 700;
}

.hub-content p {
  margin: 0;
  font-size: 14px;
  color: #666;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
  .creative-circle-container {
    width: 350px;
    height: 350px;
  }

  .center-hub {
    width: 120px;
    height: 120px;
  }

  .hub-content h2 {
    font-size: 18px;
  }

  .hub-content p {
    font-size: 12px;
  }

  .section h3 {
    font-size: 14px;
  }

  .section p {
    font-size: 10px;
  }
}

See the Pen circle-layout-sample-12 by watashi-xyz (@watashi-xyz) on CodePen.

この実装では、conic-gradient により動的に回転する背景グラデーションを作成し、clip-path で各セクションを扇形にクリッピングしています。結果として、従来の円形配置を超えた、視覚的に魅力的で機能的なインターフェースが実現されます。

さらに高度な表現として、CSS カスタムプロパティと組み合わせることで、動的にセクション数を変更したり、テーマカラーを調整したりすることも可能です:

:root {
  --section-count: 6;
  --primary-color: #667eea;
  --secondary-color: #764ba2;
  --section-angle: calc(360deg / var(--section-count));
}

/* 動的な扇形生成の例 */
.dynamic-section {
  clip-path: polygon(
    50% 50%,
    calc(50% + 50% * cos(var(--start-angle))) calc(50% + 50% * sin(var(--start-angle))),
    calc(50% + 50% * cos(var(--end-angle))) calc(50% + 50% * sin(var(--end-angle)))
  );
}

これらの技術を組み合わせることで、単純な円形配置から、インタラクティブで美しい視覚体験を提供するコンポーネントまで、幅広い表現が可能になります。


よくある質問(FAQ)

円形配置はFlexboxやGridでは実現できないのですか?

FlexboxやCSS Gridは、水平・垂直方向への整列やグリッド構造には強力ですが、角度をつけて要素を回転配置するようなケース(=円形配置)には向いていません。

たとえば、以下のようにFlexboxで中央揃えはできますが、要素を円周上に並べるのは困難です:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}

円形配置のように回転や半径をベースに位置を調整したい場合は、transform: rotate()translate()、三角関数といった幾何学的な計算を伴う手法が必要になります。

JavaScriptなしで動的な要素数に対応できますか?

CSSだけで動的な要素数(例:CMSで出力される要素数が可変)に対応するのはかなり難しいです。

CSSではループ処理ができないため、要素ごとの角度計算を手動で書く必要があります。

/* 例えば6個の要素を60度ずつ配置する例 */
.item:nth-child(1) { transform: rotate(0deg) translate(100px); }
.item:nth-child(2) { transform: rotate(60deg) translate(100px); }
.item:nth-child(3) { transform: rotate(120deg) translate(100px); }
/* ... */

こうした制約があるため、要素数が可変の場合はJavaScriptを併用する方が柔軟です。ただし、少数固定の要素であればCSSのみでも十分に対応できます。

CSSの三角関数(sin, cos)はどのように使うの?

CSSレベル4以降、sin()cos()などの三角関数が使える仕様になっていますが、2025年6月時点では、主要ブラウザの対応はまだ限定的です。

代替としては、JavaScriptで角度を指定して transform: translate() などに反映させる方法が一般的です:

const angle = (360 / itemCount) * index;
const x = Math.cos(angle * (Math.PI / 180)) * radius;
const y = Math.sin(angle * (Math.PI / 180)) * radius;

CSSだけで三角関数を使いたい場合は、ブラウザの最新動向をチェックし、サポート状況を確認する必要があります。

clip-pathやconic-gradientでも円形配置は可能ですか?

はい、clip-pathconic-gradient を使えば、装飾的・視覚的に円形の表現を行うことができますが、要素そのものの物理的な配置には不向きです。

例えば、以下は clip-path: circle() を使った円形マスクの一例です:

.circle {
  clip-path: circle(50%);
}

また conic-gradient を使うことで、円形に色分けされた背景を作ることはできますが、要素の並びを制御する目的では使用できません

Tailwind CSSで円形配置はできますか?

Tailwind CSSはユーティリティファーストなCSSフレームワークですが、円形配置のための専用ユーティリティは存在しません

ただし、rotate-*translate-* などのクラスを組み合わせて、円形配置を実現できます。

<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-[60deg]">
  <!-- 子要素 -->
</div>

より複雑な構成が必要な場合は、カスタムCSSを併用することが現実的です。

円形配置で要素が重なってしまいます。どうすればいい?

要素の回転中心やtranslate()の方向にミスがあると、円形に配置したつもりでも要素が重なってしまうことがあります。

以下のように回転後に平行移動させる順番が重要です:

.item {
  transform: rotate(45deg) translate(100px); /* 順番が重要! */
}

また、要素の中央揃えや、サイズの均等性も確認してみてください。z-indexの調整で重なりの順序も制御できます。

アニメーションを付けると動きがガタつきます。なぜ?

CSSアニメーションでガタつき(ジャダー)が起きる原因は主に以下の通りです:

  • ブラウザのハードウェアアクセラレーションが効いていない
  • 小数px単位のサブピクセル描画
  • 複雑なDOM構造と大量のリペイント

可能な対策:

  • transformopacity のみをアニメーション対象にする(リフローを避ける)
  • will-change: transform; を追加することで描画を最適化
.item {
  will-change: transform;
  animation: rotateCircle 10s linear infinite;
}

まとめ

ここまで、CSSで円形配置を実装するための様々な手法とテクニックについて詳しく解説してきました。円形配置は単なるデザインの装飾ではなく、ユーザビリティとビジュアルインパクトを両立させる強力なレイアウト手法です。

円形配置の実装において、最も重要なポイントを改めて整理すると以下のようになります:

実装における重要なポイント

  • 基本原理の理解: transform: rotate()translate() の組み合わせによる配置制御
  • 三角関数の活用: CSS の sin() cos() 関数を使った精密な位置計算
  • CSS変数の重要性: カスタムプロパティと calc() による柔軟で保守性の高い実装
  • レスポンシブ対応: デバイスサイズに応じた適切なサイズ調整とレイアウト変更
  • アニメーション効果: @keyframes による魅力的な視覚演出とユーザーエクスペリエンスの向上
  • 先進技術の活用: clip-pathconic-gradient を使った創造的なデザイン表現

今回ご紹介した手法は、基本的な円形配置から高度なインタラクティブUIまで幅広くカバーしています。特に実践編では、円形ナビゲーションメニューやメンバー紹介カード、創造的なデザインパターンなど、実際のWebサイトですぐに活用できる具体例をお示ししました。

選択すべき実装方法

どの手法を選ぶかは、プロジェクトの要件によって決まります。シンプルな円形配置であれば基本的な transform プロパティの組み合わせで十分ですが、より動的で柔軟な実装を求める場合は、CSS三角関数とカスタムプロパティを活用した方法がおすすめです。

また、ブラウザサポートを重視する場合は従来の手法を、最新ブラウザでの表現力を追求する場合は clip-pathconic-gradient などの新しい機能を積極的に取り入れることが効果的です。

パフォーマンスとアクセシビリティへの配慮

円形配置を実装する際は、パフォーマンスとアクセシビリティにも十分な注意を払うことが大切です:

  • パフォーマンス最適化: 不要なアニメーションや重い計算処理の回避
  • アクセシビリティ: 適切な aria-label やキーボードナビゲーションへの対応
  • フォールバック: 古いブラウザでも基本機能が動作する代替手段の準備

今後の発展性

CSS円形配置の技術は、Webデザインのトレンドとブラウザ技術の進歩とともに、さらなる発展を遂げていくでしょう。Container Queriesや新しいCSS機能の登場により、より柔軟で表現力豊かな円形レイアウトが可能になることが期待されます。

ぜひ今回ご紹介したテクニックを実際のプロジェクトで試してみてください。基本的な実装から始めて、徐々に高度な表現にチャレンジしていくことで、円形配置の真の魅力と可能性を実感していただけるはずです。読者の皆様のWebサイトが、より魅力的で機能的なものになることを心から願っています。

下スクロールで隠れ、上スクロールで表示!ヘッダーサイズ変更アニメーションの作り方【jQuery不要】
スクロールに応じてヘッダーを表示・非表示に切り替える方法を、初心者にもわかりやすく解説。JavaScript+CSSの実装コード付きで、UX向上やCV率改善のメリットも紹介します。モバイル対応や透過エフェクト、チラつき対策などの実践ポイントも網羅しているので、現場でそのまま使える内容です。
タイトルとURLをコピーしました