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

ヒーロー

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

デモ

仕様

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

しくみと調整

画面いっぱいの線の迷路を波が通り過ぎるたびに、タイルが回って別の迷路に組み変わります。タイルは四分円の弧を2本持つ正方形(トルシェ・タイル)で、弧はどれも辺の中点で終わります。隣の弧と必ずつながるので、向きがどう組み合わさっても線は途切れません。切れて見えるのは、タイルが回っている途中だけです。

タイルは GSAP で90°ずつ回し、回転の中心は svgOrigin で各タイルの中心に置いています。ループのあいだに、その90°の波を2度通します。最初は左上から斜めに、次は右寄りの一点から輪になって広がります。このタイルは180°回すと元と同じ形になるので、両方の波が通り終わると最初の模様に戻り、継ぎ目なく繰り返せます。

波が届く順番は、stagger に grid を渡して、格子の上の位置から計算させました。イージングは back.inOut で、少し行き過ぎてから戻って止まるぶん、タイルが弾むように見えます。

背景と線の2色は差し替えられるので、配色はブランドやイベントに合わせて変えられます。模様の表情をいちばん大きく変えるのは波の起点で、角に置けば画面を斜めに走査する波に、内側に置けば同心円状に広がる波になります。

コード

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 サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。