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

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

デモ

仕様

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

しくみと調整

見出しの文字を 1 字ずつ、表と裏を重ねた小さな箱にします。箱を rotationX で 180° 回すと裏の色の文字が正面を向いて現れ、少し置いてもう半分回して表へ戻ります。カーソルを重ねている間だけ動く、ホバーの仕掛けです。

半回転は 0.55 秒の power2.inOut で、隣の文字と 0.08 秒ずつずらして送ります。透視の強さ(perspective)は 350 に固定していて、これより小さくすると近づく面が誇張されて安っぽく見えます。

裏の面には backface-visibility を指定します。無いと、回転の途中で裏返った文字が透けて重なります。1 字ずつに分けた文字は読み上げで 1 字ずつ聞こえてしまうため、元の文言はリンクの名前に移し、分けた文字のほうは読み上げから外します。

裏の色を変えるとめくれた印象が変わり、ずらす間隔を広げるほど波打って見えます。ナビゲーションのリンクや、短い見出しのホバー演出に向いています。

コード

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