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

見出しを 1 文字ずつせり上げる

デモ

仕様

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

しくみと調整

見出しを 1 文字ずつに分け、文字ごとに窓を付けて、その下からせり上げます。窓があるので、上がってくる途中の字が行の外へはみ出しません。添えた説明文は、見出しが半分ほど出そろったところで遅れて浮かびます。

入りは 0.9 秒の expo.out で、文字は 0.04 秒ずつずらします。出だしが速く終わりが長く効くので、字は一斉に飛び出して、そろって止まって見えます。ずらす間隔を 0.08 秒より広げると、読む速さに動きが追いつかず、波打って見えます。

字に分けるのは、書体が読み込まれてからにします。fonts.ready を待たずに分けると、仮の書体の幅で窓ができ、本物の書体に替わったところで字と窓がずれます。行の高さも 1.1 までにとどめます。これより詰めると、せり上がる途中で g や y の下が窓に切られ、別の字に読めます。

動かすのは、画面に入った 1 回だけにします。見えるたびに繰り返すと、スクロールを戻すたびに見出しが消えて出直すので、読み返す人の邪魔になります。動きを減らす設定のときは、出きった状態のまま置きます。

抜けるときは 0.6 秒の expo.in で上へ送り、入りより短くします。ヒーローの見出しや節の見出しのように、短くて大きい字に向いています。和文の長い見出しは、文字ごとではなく行ごとに分けたほうが、ばらけずに読めます。

コード

HTML
<section class="rise">
  <h2 class="rise__heading">Motion Ledger</h2>
  <p class="rise__lead">気づくより先に、心で感じる。そんな動きをデザインしています。</p>
</section>
CSS
.rise {
  --heading: clamp(40px, 9vw, 112px); /* 見出しの大きさ */
  --ink: #262626;    /* 文字の色 */
  --ground: #f2f1ed; /* 地の色 */
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 24px;
  min-height: 100vh;
  padding: 96px 6vw;
  background: var(--ground);
  color: var(--ink);
  text-align: center;
}

/* 行の高さは 1.1 まで。これより詰めると、せり上がる途中で g の下が窓に切られる */
.rise__heading {
  margin: 0;
  font-size: var(--heading);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.rise__lead {
  max-width: 34em;
  margin: 0;
  font-size: 18px;
  line-height: 1.8;
  letter-spacing: 0.04em;
}
JavaScript
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";

gsap.registerPlugin(SplitText);

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// 字形が決まってから分ける。仮の書体の幅で窓を作ると、書体が差し替わったときに字がずれる
document.fonts.ready.then(() => {
  document.querySelectorAll(".rise").forEach((section) => {
    const heading = section.querySelector(".rise__heading");
    const lead = section.querySelector(".rise__lead");
    // mask: "chars" で、文字ごとにはみ出しを隠す窓が付く(自分で overflow を切らなくてよい)
    const split = SplitText.create(heading, { type: "chars", mask: "chars" });

    const rise = gsap
      .timeline({ paused: true })
      .from(split.chars, { yPercent: 110, duration: 0.9, ease: "expo.out", stagger: 0.04 })
      .from(lead, { autoAlpha: 0, y: 16, duration: 0.6, ease: "power2.out" }, 0.3);

    // 動きを減らす設定では、出きった状態で見せる
    if (reduceMotion) {
      rise.progress(1);
      return;
    }

    // 画面に入ったら 1 回だけ流す。見るたびに繰り返すと、読み返す人の邪魔になる
    const io = new IntersectionObserver(
      (entries, self) => {
        for (const entry of entries) {
          if (!entry.isIntersecting) continue;
          self.unobserve(entry.target);
          rise.play();
        }
      },
      { rootMargin: "0px 0px -15%" } // 下から 15% 入ったら始める
    );
    io.observe(section);
  });
});

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

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