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

セクション

セクションの本文の語にマーカーが引かれる

デモ

仕様

動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

段落が画面に入ると、強調した語に左から蛍光ペンのマーカーが引かれていき、行をまたぐ語では、行末まで引き終えたところから次の行の頭へそのまま続きます。語を <mark> で囲み、背景に単色のグラデーションを指定して、その横幅を伸ばしているだけです。折り返しても背景は行ごとの断片をつないだひと続きとして扱われるので、行の境目で継ぎ足す処理は要りません。

引く時間は字数ではなく、語の幅から決めています。語ごとに0.45秒、そこへ幅1emごとに0.04秒を足した長さです。同じ幅でも和文は欧文の半分ほどの字数しかないので、字数で決めると和文だけペンが速く走って見えます。次の語は0.55秒後に引き始め、イージングは power2.inOut にして、引き始めと引き終わりにペンの重さを残しました。

マーカーの縦の位置を <mark> 要素の高さに対する割合で決めると、和文ではマーカーが字の下へずれます。要素の高さは書体の指定で先頭に並ぶ欧文書体で決まり、その下のほうは g や p が下へはみ出すための余白だからです。和文の字はそこまで下りてこないので、下寄りに置いたマーカーは字から外れ、書体を替えるとずれ方まで変わります。

そこで、語の先頭に高さのない inline-block を差し込み、ベースラインの位置を測ることにしました。中身のない inline-block は、下端をベースラインにそろえて置かれます。マーカーの下端はベースラインの0.14em下に置き、和文の字の下端の少し下から、字の高さの半分ほどを覆うようにしています。測った値は字の大きさとの比にして CSS 変数へ入れてあるので、画面の幅で字の大きさが変わっても、マーカーはずれません。

コード

HTML
<section class="statement">
  <p class="statement__eyebrow">Approach</p>

  <!-- 引きたい語を <mark class="marker"> で囲む。行は文節の切れ目に <br> を入れて決める -->
  <p class="statement__text">私たちは、<mark class="marker">使いやすさ</mark>をいちばんに考えて、<br>
    Webサイトをつくっています。<br>
    <mark class="marker">はじめての人でも迷わない</mark>こと、<br>
    そして<mark class="marker">長く使い続けられる</mark>こと。</p>
</section>

CSS
.statement {
  --ink: #262626;
  --muted: #6b6b6b;
  --marker: #c8bf83; /* マーカーの色 */
  --band: 0.5em; /* マーカーの太さ */
  --drop: 0.14em; /* マーカーの下端を、ベースラインからどれだけ下げるか */
  padding: 120px 6vw;
  background-color: #f2f1ed;
  color: var(--ink);
}

.statement__eyebrow {
  margin: 0 0 32px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.statement__text {
  margin: 0;
  font-size: clamp(22px, 2.8vw, 36px);
  line-height: 1.8;
  letter-spacing: 0.04em;
  word-break: auto-phrase; /* 狭い画面で折り返すとき、文節の切れ目で折る(対応するブラウザのみ) */
}

/* マーカーは背景の単色のグラデーション。引いた長さ(--draw)を、スクリプトが 0% から 100% へ伸ばす。
   縦の位置は、スクリプトが測ったベースライン(--baseline。要素の上端からの距離)から決める */
.marker {
  --draw: 100%;
  padding: 0 0.08em; /* 字組みを変えずに、マーカーだけ左右へ少しはみ出させる */
  margin: 0 -0.08em;
  background: linear-gradient(var(--marker), var(--marker)) no-repeat;
  background-size: var(--draw) var(--band);
  background-position: 0 calc(var(--baseline, 0.95em) + var(--drop) - var(--band));
  color: inherit;
}

JavaScript
import { gsap } from "gsap";

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

// <mark> 要素の上端からベースラインまでを、字の大きさに対する比で返す。
// 高さ 0 の inline-block は、下端をベースラインにそろえて置かれる
function baselineOf(mark) {
  const probe = document.createElement("span");
  probe.style.cssText = "display:inline-block;width:0;height:0;";
  mark.prepend(probe);
  const y = probe.getBoundingClientRect().bottom - mark.getClientRects()[0].top;
  probe.remove();
  return y / parseFloat(getComputedStyle(mark).fontSize);
}

// ベースラインの位置と語の幅は書体で決まるので、読み込みが終わってから測る
document.fonts.ready.then(() => {
  document.querySelectorAll(".statement__text").forEach((text) => {
    const marks = [...text.querySelectorAll(".marker")];
    // 要素に対する %(下から 10% など)でマーカーを置くと、和文では字の下へずれるので、測ったベースラインから置く
    for (const mark of marks) mark.style.setProperty("--baseline", `${baselineOf(mark).toFixed(3)}em`);

    // 動きを減らす設定では、引き終えた状態のまま見せる
    if (reduceMotion) return;

    const size = parseFloat(getComputedStyle(text).fontSize);
    const draw = gsap.timeline({ paused: true });
    marks.forEach((mark, i) => {
      // 長い語ほど少しだけ長く引く(幅 1em ごとに 0.04 秒。和文でも欧文でも、同じ幅なら同じ速さ)
      const width = [...mark.getClientRects()].reduce((sum, rect) => sum + rect.width, 0);
      draw.fromTo(
        mark,
        { "--draw": "0%" },
        { "--draw": "100%", duration: 0.45 + (width / size) * 0.04, ease: "power2.inOut" },
        i * 0.55 // 前の語を引き終える少し前に、次の語を引き始める
      );
    });

    // 段落が画面の下から 4 分の 1 の所まで入ったら、1 回だけ引く
    const io = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return;
        io.disconnect();
        draw.play();
      },
      { rootMargin: "0px 0px -25%" }
    );
    io.observe(text);
  });
});

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

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