本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
ヒーロー

ヒーローの背景で四分円のタイルが順に回って模様が組み変わる

デモ

仕様

用途
ヒーロー
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

SVG のトルシェ・タイル(四分円の弧を 2 本持つタイル)を格子状に敷き詰め、各タイルの transform 属性の rotate を t から直接書き換えて回します。弧は辺の中点で隣のタイルの弧とつながるので、どの向きの組み合わせでも途切れない線の迷路になり、回っている途中だけ線が切れます。

1 ループのあいだに 90° の波を 2 回通します。1 回目は左上から斜めに、2 回目は右側の一点から輪になって広がり、合計 180° 回ると元の模様に戻ります(このタイルは 180° 回すと元と同じ形になるため)。back.inOut の ease で少し行き過ぎてから止まるので、タイルが弾んで見えます。

タイルの大きさと位置は整数 px にそろえます。端数の座標にすると、回転のたびに縁の画素がちらつきます。GSAP の stagger に grid を渡すと、格子上の位置から波の順番を自動で計算してくれます。

つまみはタイルの大きさ・線の太さ・波が始まる時刻と広がる速さ・波の起点です。地と線の色を差し替えれば、ブランドやイベントごとに配色を変えられます。ヒーローの背景の模様や、スタジオ・イベントのジェネラティブなアイデンティティに向いています。

コード

HTML
<section class="tile-hero">
  <!-- 背景は飾りなので、読み上げからは外す -->
  <svg class="tile-hero__pattern" viewBox="0 0 960 540" preserveAspectRatio="xMidYMid slice" aria-hidden="true"></svg>
  <div class="tile-hero__inner">
    <p class="tile-hero__eyebrow">DESIGN STUDIO</p>
    <h1 class="tile-hero__title">動き続ける模様を、<br>ブランドの顔に。</h1>
    <p class="tile-hero__lead">ここに説明文が入ります。</p>
    <a class="tile-hero__button" href="/work/">システムを見る</a>
  </div>
</section>

CSS
.tile-hero {
  --bg: #688297; /* 模様の地(スレート) */
  --line: #e6dccb; /* 模様の線(淡いグレージュ) */
  --ink: #ffffff;
  --on-ink: #111111; /* 白い主ボタンの文字 */
  --gutter: clamp(24px, 7vw, 96px);
  position: relative;
  display: grid;
  align-items: center;
  min-height: min(90vh, 720px);
  padding: 0 var(--gutter);
  overflow: hidden;
  color: var(--ink);
  background-color: var(--bg);
}

.tile-hero__pattern {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* 模様の上・文字の下の幕。文字のある左ほど濃くする */
.tile-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgb(0 0 0 / 0.6) 0%, rgb(0 0 0 / 0.3) 100%);
}

.tile-hero__inner {
  position: relative;
  max-width: 34em;
}

.tile-hero__eyebrow {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.tile-hero__title {
  margin: 24px 0 0;
  font-size: clamp(32px, 8vw, 60px);
  line-height: 1.05;
  letter-spacing: -0.02em;
}

.tile-hero__lead {
  margin: 24px 0 0;
  font-size: 18px;
  line-height: 1.8;
}

.tile-hero__button {
  display: inline-grid;
  place-items: center;
  height: 48px;
  margin-top: 32px;
  padding: 0 32px;
  border-radius: 999px;
  background-color: var(--ink);
  color: var(--on-ink);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
}

.tile-hero__button:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

JavaScript
import { gsap } from "gsap";

const TILE = 60; // タイルの大きさ(px)
const TURN = 0.9; // 1 枚が 90° 回る時間(秒)
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".tile-hero__pattern").forEach((svg) => {
  const hero = svg.closest(".tile-hero");
  const line = getComputedStyle(hero).getPropertyValue("--line").trim();
  const [, , W, H] = svg.getAttribute("viewBox").split(" ").map(Number);
  const cols = Math.ceil(W / TILE);
  const rows = Math.ceil(H / TILE);
  const h = TILE / 2;
  const tiles = [];

  for (let j = 0; j < rows; j++) {
    for (let i = 0; i < cols; i++) {
      const x = i * TILE;
      const y = j * TILE;
      const path = document.createElementNS(svg.namespaceURI, "path");
      // 左上と右下の角を中心にした四分円。どちらも辺の中点で終わるので隣のタイルの弧とつながる
      path.setAttribute("d", `M${x + h} ${y}A${h} ${h} 0 0 1 ${x} ${y + h}M${x + h} ${y + TILE}A${h} ${h} 0 0 1 ${x + TILE} ${y + h}`);
      path.setAttribute("fill", "none");
      path.setAttribute("stroke", line);
      path.setAttribute("stroke-width", String(Math.round(TILE * 0.3)));
      path.dataset.center = `${x + h} ${y + h}`;
      if (Math.random() < 0.5) path.setAttribute("transform", `rotate(90 ${x + h} ${y + h})`);
      svg.append(path);
      tiles.push(path);
    }
  }

  if (reduceMotion) return; // 動きを減らす設定では、組み上がった模様のまま止めておく

  gsap
    .timeline({ repeat: -1, repeatDelay: 0.8 })
    .to(tiles, {
      rotation: "+=90",
      svgOrigin: (i, el) => el.dataset.center,
      duration: TURN,
      ease: "back.inOut(1.4)",
      stagger: { grid: [rows, cols], from: "start", amount: 2.2 },
    })
    .to(
      tiles,
      {
        rotation: "+=90",
        svgOrigin: (i, el) => el.dataset.center,
        duration: TURN,
        ease: "back.inOut(1.4)",
        stagger: { grid: [rows, cols], from: [0.74, 0.5], amount: 2.4 },
      },
      "+=0.9"
    );
});

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。