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

ヒーロー

見出しの筆記体の文字が書き順どおりに描かれる

デモ

仕様

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

しくみと調整

筆記体の見出しが一筆ずつ書かれていき、書き終えたところで差し色の下線ときらめきが添えられます。文字は書き順どおりに並べた SVG の path で、DrawSVGPlugin で線の表示範囲を伸ばしていくと、ペンで書いているように見えます。描くのは画面に入ったときの一度だけです。

線ごとの時間は決め打ちにせず、線の長さをペンの速さで割って求めることにしました。速さは毎秒 viewBox の単位で1150進む想定です。長い線はゆっくり、短い線はすばやく描かれるので、どの線も同じペンで書いたように見えますし、線を差し替えても時間を直さずに済みます。それでも短すぎる線が一瞬で終わらないよう、最低0.2秒は確保し、線と線の間には0.12秒ほどの間をあけています。

線の端は丸くしてありますが、描き始めを 0% 0% にしておけば、長さ0の破線が始点の外へずれるので、描く前にドットが浮くことはありません。厄介なのは逆の向きで、線を縮めて消す描き方だと終点に丸いドットが残ります。読ませたあとで消したいときは、縮めるのではなく、線ごとフェードさせてください。

コード

HTML
<section class="handwrite" aria-labelledby="handwrite-title">
  <!-- data-ease: 描くイージング。data-gap: 次の線までの間隔(秒。負なら重ねて描く) -->
  <svg class="handwrite__ink" viewBox="9 20 660 280" role="img" aria-label="Hello">
    <path class="handwrite__stroke" data-ease="sine.inOut" data-gap="0.12"
      d="M157.4 44 C137.9 110 115 186 98 234 C91 254 77 262 67.5 252"></path>
    <path class="handwrite__stroke" data-ease="sine.inOut" data-gap="0.12"
      d="M80.4 160 C120.9 150 177.4 140 223.9 138 C250.4 136 265.9 90 263.4 60 C262.4 40 242.9 38 235.9 58
         C220.9 110 196.5 200 191.5 236 C190.5 256 212.5 256 235 238 C257 222 291 206 307.5 188 C321.4 172 314.4 160 297.9 162
         C277.4 164 259 190 255 214 C249.5 244 275 254 309.5 236 C338 218 376.9 150 397.4 100 C412.4 64 410.4 40 395.9 42
         C381.4 44 368.4 80 358.4 120 C345.9 170 330 226 337.5 244 C342.5 256 363.5 252 384 234 C412.5 216 450.9 150 471.4 100
         C486.4 64 484.4 40 469.9 42 C455.4 44 442.4 80 432.4 120 C419.9 170 404 226 411.5 244 C416.5 256 437.5 252 456 234
         C469.2 221 498.5 176 522.9 166 C507.9 170 491 190 485.5 212 C479 238 491.5 252 508 250 C526.5 248 541.5 228 545.5 204
         C548.5 184 540.9 166 524.9 166 C514.9 166 512 178 526 178 C548 178 580.4 168 614.9 150"></path>
    <path class="handwrite__stroke handwrite__stroke--accent" data-ease="power2.inOut" data-gap="-0.1"
      d="M55.5 284 C174.5 272 376 266 515.5 268 C555.3 269 589 262 614.5 248"></path>
    <path class="handwrite__stroke handwrite__stroke--accent" data-ease="power2.out" data-gap="-0.13" d="M622 108 L616 84"></path>
    <path class="handwrite__stroke handwrite__stroke--accent" data-ease="power2.out" data-gap="-0.13" d="M632 116 L650 98"></path>
    <path class="handwrite__stroke handwrite__stroke--accent" data-ease="power2.out" data-gap="-0.13" d="M638 130 L662 128"></path>
  </svg>
  <h2 class="handwrite__title" id="handwrite-title">静かで、<wbr>使いやすいWebサイトを。</h2>
</section>

CSS
.handwrite {
  --bg: #f2f1ed;
  --ink: #262626;
  --accent: #b27771; /* 下線ときらめきの色(ローズカラー) */
  display: grid;
  place-items: center;
  align-content: center; /* これが無いと、2 段の行が高さいっぱいに伸びて間に大きな隙間ができる */
  gap: 24px;
  min-height: 100vh;
  padding: 96px 6vw;
  background: var(--bg);
  color: var(--ink);
  text-align: center;
}

.handwrite__title {
  margin: 0;
  font-size: clamp(24px, 4vw, 32px);
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.02em;
  word-break: keep-all; /* 狭い画面では <wbr> の所(文節の切れ目)で折り返す */
}

.handwrite__ink {
  width: min(560px, 100%);
  height: auto;
  overflow: visible;
}

.handwrite__stroke {
  fill: none;
  stroke: var(--ink);
  stroke-width: 9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.handwrite__stroke--accent {
  stroke: var(--accent);
}

JavaScript
import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";

gsap.registerPlugin(DrawSVGPlugin);

const SPEED = 1150; // ペンの速さ(1 秒に描く長さ。viewBox の単位)
const MIN = 0.2; // 短い線でも、これより短い時間では描かない
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".handwrite__ink").forEach((svg) => {
  const paths = [...svg.querySelectorAll(".handwrite__stroke")];

  // 動きを減らす設定では、描き終えた状態のまま置く
  if (reduceMotion) {
    gsap.set(paths, { drawSVG: "0% 100%" });
    return;
  }

  // 線の長さはページに入ってから測る。描く時間 = 長さ ÷ ペンの速さ
  const tl = gsap.timeline({ paused: true });
  let at = 0;
  for (const path of paths) {
    const duration = Math.max(MIN, path.getTotalLength() / SPEED);
    tl.fromTo(path, { drawSVG: "0% 0%" }, { drawSVG: "0% 100%", duration, ease: path.dataset.ease }, at);
    at += duration + Number(path.dataset.gap);
  }

  // 画面に入ったら 1 回だけ描く
  const io = new IntersectionObserver(
    (entries, self) => {
      for (const entry of entries) {
        if (!entry.isIntersecting) continue;
        self.unobserve(entry.target);
        tl.play();
      }
    },
    { rootMargin: "0px 0px -15%" }
  );
  io.observe(svg);
});

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

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