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

ヒーロー

見出しの文字が 3D で順に裏返る

デモ

仕様

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

しくみと調整

カーソルを重ねると、見出しの文字が端から順に横軸のまわりで半回転し、裏に用意しておいた別の色の文字が正面に並びます。離せば同じ動きを逆にたどって、元の文字へ戻ります。文字はそれぞれ、表と裏の面を重ねた小さな要素に分けてあり、それを rotationX で180°回しているだけです。

backface-visibility は表と裏の両方の面に指定しています。指定しないと、回転の途中で裏返った文字が透けて重なって見えます。ばらばらに分けた文字は、スクリーンリーダーでも区切って読まれてしまうので、元の文言はリンクの名前に移し、分けた文字のほうは読み上げの対象から外しています。

半回転は0.55秒の power2.inOut。隣の文字とは0.08秒ずつずらしてあり、この間隔を広げるほど、めくれが波のように見出しを渡っていきます。遠近の強さ(perspective)は350に固定しました。これより小さくすると、手前へ迫ってくる面が誇張されすぎて、安っぽく見えます。

コード

HTML
<!-- 文言はそのまま書く。1 字ずつの要素に分けるのは script の役目 -->
<a class="flip-text" href="/works/">
  <span class="flip-text__label">制作実績を見る</span>
</a>

CSS
.flip-text {
  --ink: #262626;   /* 表の文字 */
  --back: #b27771;  /* 裏の色。表より明るくすると、裏返ったのが明るさでもわかる */
  display: inline-flex;
  font-size: clamp(28px, 5vw, 56px);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.flip-text__label {
  display: flex;
}

.flip-text__space {
  display: inline-block;
  width: 0.32em;
}

/* 1 字ぶんの要素。preserve-3d で中の表と裏を立体として扱う */
.flip-text__char {
  display: inline-block;
  position: relative;
  transform-style: preserve-3d;
}

.flip-text__face {
  display: block;
  backface-visibility: hidden;
}

/* 裏ははじめから伏せて重ねておく。要素が 180° 回ると正立して現れる */
.flip-text__face--back {
  position: absolute;
  inset: 0;
  color: var(--back);
  transform: rotateX(180deg);
}

.flip-text:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 6px;
}

JavaScript
import { gsap } from "gsap";

const HALF = 0.55; // 半回転(表 → 裏)にかける時間
const EACH = 0.08; // 隣の文字とのずれ
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".flip-text").forEach((link) => {
  const label = link.querySelector(".flip-text__label");
  const text = label.textContent;

  // 1 字ずつに分けた span は 1 字ずつ読み上げられるので、元の文言はリンクの名前に移す
  link.setAttribute("aria-label", text);
  label.setAttribute("aria-hidden", "true");
  label.textContent = "";

  const flips = [];
  for (const ch of text) {
    if (ch === " ") {
      const space = document.createElement("span");
      space.className = "flip-text__space";
      label.append(space);
      continue;
    }
    const flip = document.createElement("span");
    flip.className = "flip-text__char";
    const front = document.createElement("span");
    front.className = "flip-text__face";
    front.textContent = ch;
    // 裏は同じ要素の中心で伏せておく。要素が 180° 回ると、表と同じ位置に正立して現れる
    const back = document.createElement("span");
    back.className = "flip-text__face flip-text__face--back";
    back.textContent = ch;
    flip.append(front, back);
    label.append(flip);
    flips.push(flip);
  }
  gsap.set(flips, { transformPerspective: 350 });

  const flip = gsap
    .timeline({ paused: true })
    .to(flips, { rotationX: 180, duration: HALF, ease: "power2.inOut", stagger: EACH });

  // 動きを減らす設定では、ほぼ一瞬で裏返す
  if (reduceMotion) flip.timeScale(20);

  const open = () => flip.play();
  const close = () => flip.reverse();
  link.addEventListener("pointerenter", open);
  link.addEventListener("pointerleave", close);
  link.addEventListener("focus", open);
  link.addEventListener("blur", close);
});

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

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