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

見出しの語の下に手描きの下線が引かれて消える

デモ

仕様

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

しくみと調整

見出しの強調したい語の下に、手描き風の下線がすっと引かれます。語の幅を測り、線の太さが伸び縮みしないようその幅ぴったりのSVGを作って下に重ねています。線は2本の曲線で、1本目が長く弧を描き、2本目が短く下をなぞって重なります。

下線の上端は、語のベースラインを測ってその0.22倍下に置いています。和文でも欧文でも、書体や行の高さを替えても、字の下がり(欧文の p・y など)との間が変わりません。1本目は0.8秒、2本目は少し遅れて0.55秒かけて描きます。

線の端は丸くしているので、描き終わったところに小さな点が残りやすくなります。消すときは、線を縮めきる直前に透明にして点を隠しています。

つまみは線の色・太さ・ベースラインからの間隔・反り方です。色を変えるだけでも印象が変わります。見出しの一部や、LP の強調したい語に向いています。

コード

HTML
<section class="underline-draw">
  <h2 class="underline-draw__heading">
    伝えたい言葉を、<br>
    <span class="underline-draw__word">まっすぐ届ける</span>。
  </h2>
</section>

CSS
.underline-draw {
  --accent: #688297; /* 下線の色 */
  display: grid;
  place-items: center;
  min-height: 60vh;
  padding: 96px 6vw;
  background: #f2f1ed;
  color: #262626;
  text-align: center;
}

.underline-draw__heading {
  margin: 0;
  font-size: clamp(28px, 6vw, 64px);
  font-weight: 800;
  line-height: 1.3;
  letter-spacing: -0.02em;
}

.underline-draw__word {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

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

gsap.registerPlugin(DrawSVGPlugin);

const GAP = 0.22; // ベースラインから線までの間隔(字の大きさに対する比)
const SVG_NS = "http://www.w3.org/2000/svg";
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// 語の箱の上端からベースラインまで(px)。高さ 0 の inline-block は下端をベースラインにそろえて置かれる
function baselineOf(word) {
  const probe = document.createElement("span");
  probe.style.cssText = "display:inline-block;width:0;height:0;";
  word.prepend(probe);
  const y = probe.offsetTop;
  probe.remove();
  return y;
}

// 語の幅に合わせた下線の SVG を作る。1 本目は右上がりにゆるく反らせ、2 本目は短く下をなぞる
function underline(width, size, top) {
  const height = size * 0.34;
  const x = (f) => (f * width).toFixed(1);
  const y = (f) => (f * height).toFixed(1);
  const svg = document.createElementNS(SVG_NS, "svg");
  svg.setAttribute("viewBox", `0 0 ${width} ${height.toFixed(1)}`);
  svg.setAttribute("width", String(width));
  svg.setAttribute("height", height.toFixed(1));
  svg.setAttribute("aria-hidden", "true");
  svg.style.cssText = `position:absolute;left:0;top:${Math.round(top)}px;overflow:visible;`;
  const paths = [
    `M ${x(0.01)} ${y(0.7)} C ${x(0.3)} ${y(0.25)}, ${x(0.68)} ${y(0.12)}, ${x(0.99)} ${y(0.42)}`,
    `M ${x(0.1)} ${y(1)} C ${x(0.38)} ${y(0.72)}, ${x(0.7)} ${y(0.68)}, ${x(0.93)} ${y(0.86)}`,
  ].map((d) => {
    const path = document.createElementNS(SVG_NS, "path");
    path.setAttribute("d", d);
    path.setAttribute("fill", "none");
    path.style.stroke = "var(--accent)"; // 色は CSS の --accent で変える
    path.setAttribute("stroke-width", (size * 0.075).toFixed(1));
    path.setAttribute("stroke-linecap", "round");
    svg.append(path);
    return path;
  });
  return { svg, paths };
}

// 語の幅とベースラインは字形で決まるので、フォントが確定してから測って線を作る
document.fonts.ready.then(() => {
  document.querySelectorAll(".underline-draw__word").forEach((word) => {
    const size = Number.parseFloat(getComputedStyle(word).fontSize);
    const { svg, paths } = underline(word.offsetWidth, size, baselineOf(word) + size * GAP);
    word.append(svg);

    if (reduceMotion) {
      gsap.set(paths, { drawSVG: "0% 100%" });
      return;
    }

    const draw = gsap.fromTo(
      paths,
      { drawSVG: "0% 0%" },
      { drawSVG: "0% 100%", duration: 0.8, ease: "power2.inOut", stagger: 0.55, paused: true }
    );

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

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

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