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

見出しの中の単語が縦に回転して切り替わる

デモ

仕様

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

しくみと調整

文中の1語だけが、ドラムが回るように縦に回転して次の語へ切り替わります。語を重ねて置いた枠は中身をはみ出させないように切り取り、奥に置いた回転軸のまわりに回して入れ替えます。枠の幅も次の語の幅へなめらかに動かすので、後ろに続く文が詰まったり広がったりします。

語は1.3秒読ませてから0.7秒で切り替えます。切り替えのイージングはpower3.inOutで、軸は語の奥に置き、文字が正面を向いたまま消えるのではなく奥へ倒れて消えるように見せています。

回り続ける語をそのまま読み上げに渡すと、切り替わるたびに読み上げが割り込んで文が聞き取れません。文全体を固定の文言として読み上げに渡し、回る語そのものは読み上げから外しています。5秒より長く動き続けるので、止める操作も添えています。

つまみは語を読ませる時間・回転の速さ・軸の奥行きです。奥行きを浅くするほど勢いよく倒れて見えます。ヒーローのキャッチコピーで、提供する価値をいくつも順に見せたいときに向いています。

コード

HTML
<div class="word-rotator">
  <h2 class="word-rotator__heading" aria-label="もっと速い、美しい、使いやすいサイトを。">
    <span aria-hidden="true">もっと<span class="word-rotator__slot">
        <span class="word-rotator__word">速い</span>
        <span class="word-rotator__word">美しい</span>
        <span class="word-rotator__word">使いやすい</span>
      </span>サイトを。</span>
  </h2>
  <button class="word-rotator__toggle" type="button" aria-pressed="false">一時停止</button>
</div>

CSS
.word-rotator {
  display: grid;
  justify-items: center;
  gap: 24px;
  padding: 96px 6vw;
  background: #f2f1ed;
  color: #262626;
  text-align: center;
}

.word-rotator__heading {
  margin: 0;
  font-size: clamp(22px, 5.5vw, 64px);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.word-rotator__slot {
  display: inline-block;
  position: relative;
  vertical-align: top;
  width: 5em;
  height: 1.2em;
  overflow: hidden;
  perspective: 220px;
}

.word-rotator__word {
  position: absolute;
  left: 0;
  top: 0;
  line-height: 1.2;
  white-space: nowrap;
  opacity: 0;
  backface-visibility: hidden;
}

/* JS が動くまでの既定は、最初の語だけ見せておく */
.word-rotator__word:first-child { opacity: 1; }
.word-rotator__word:nth-child(1) { color: #4a5b66; }
.word-rotator__word:nth-child(2) { color: #b27771; }
.word-rotator__word:nth-child(3) { color: #739374; }

.word-rotator__toggle {
  height: 40px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px #262626;
  background: transparent;
  color: #262626;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
}

.word-rotator__toggle[aria-pressed="true"] { background: #262626; color: #f2f1ed; }
.word-rotator__toggle:focus-visible { outline: 2px solid #262626; outline-offset: 4px; }

JavaScript
import { gsap } from "gsap";

const HOLD = 1.3; // 語を読ませる時間
const TURN = 0.7; // 回って切り替わる時間
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".word-rotator").forEach((root) => {
  const slot = root.querySelector(".word-rotator__slot");
  const words = [...slot.querySelectorAll(".word-rotator__word")];
  const toggle = root.querySelector(".word-rotator__toggle");

  // 語の幅は字形で決まるので、フォントが確定してから測る
  document.fonts.ready.then(() => {
    const widths = words.map((word) => word.offsetWidth);
    gsap.set(words, { transformOrigin: "50% 50% -60px", rotationX: -90, opacity: 0 });
    gsap.set(words[0], { rotationX: 0, opacity: 1 });
    gsap.set(slot, { width: widths[0] });

    const turn = { duration: TURN, ease: "power3.inOut" };
    const tl = gsap.timeline({ repeat: -1 });
    words.forEach((word, i) => {
      const next = words[(i + 1) % words.length];
      tl.to(word, { rotationX: 90, opacity: 0, ...turn }, `+=${HOLD}`)
        .fromTo(next, { rotationX: -90, opacity: 0 }, { rotationX: 0, opacity: 1, ...turn }, "<")
        .to(slot, { width: widths[(i + 1) % words.length], ...turn }, "<");
    });

    // 動きを減らす設定では止めず、切り替えをほぼ一瞬にする(読ませる間だけは残す)
    if (reduceMotion) tl.timeScale(20);

    toggle.addEventListener("click", () => {
      const paused = !tl.paused();
      tl.paused(paused);
      toggle.setAttribute("aria-pressed", String(paused));
    });
  });
});

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

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