M-022 Text Animation moderate

Scroll Text Highlighter Reveal スクロールハイライトテキストリビール

Words illuminate progressively line by line in response to scroll progression. ユーザーのスクロールリズムに合わせて単語が次々と光を帯びて浮かび上がる読書体験向上演出。

ライブデモ Live Demo

Great software experiences should feel as fluid and effortless as natural thought. As you scroll down this paragraph, each word progressively catches light and steps forward into conscious focus.

Instead of dumping blocks of dense text all at once, interactive scroll highlighting guides the reader's eye line by line. Every phrase gains momentum, turning mundane documentation into a captivating storytelling journey.

Hardware-accelerated opacity transitions combined with soft luminous text shadows guarantee flawless 60fps rendering across both mobile and desktop screens.

概要・用途・特徴Overview, Usage & Features

何ができるかWhat it does

An editorial text experience where paragraphs illuminate progressively word-by-word as visitors scroll down.

スクロールに合わせて薄暗い単語が順次ホワイトやアクセントカラーで発光し、読み進めるリズムを作るタイポグラフィ演出です。

どこで使うかWhere to use

Commonly featured in Apple/Linear-style story pages, landing page manifestos, and key company statements.

AppleやLinearのような高品質なブランド紹介ページ、理念表明、プロダクトの核となるメッセージ部分に最適です。

特徴Key features

Tokenized word spans, relative scroll offset detection, luminous text-shadow glow, responsive typography.

単語ごとのスパン分割、相対スクロール位置判定、柔らかな文字グロー光彩、レスポンシブな文字サイズ設計。

調整可能パラメータ Adjustable Parameters

Parameter Default Description
highlight-color#38bdf8Active word focal glow color

実装コード Implementation Code

<div class="m022-viewport" id="scrollArea">
  <p class="m022-text-flow">
    <span class="m022-word is-lit">Great</span>
    <span class="m022-word is-focus">software</span>
    <span class="m022-word">experiences...</span>
  </p>
</div>
.m022-text-flow {
  font-size: 1.2rem;
  line-height: 1.8;
  font-weight: 600;
}

.m022-word {
  display: inline-block;
  opacity: 0.3;
  color: rgba(148, 163, 184, 0.25);
  transition: all 0.25s ease;
}

.m022-word.is-lit {
  color: #f8fafc;
  opacity: 1;
}

.m022-word.is-focus {
  color: var(--m022-highlight-color, #38bdf8);
  opacity: 1;
  text-shadow: 0 0 16px var(--m022-highlight-color, #38bdf8);
}
import React, { useRef, useEffect } from 'react';
import './M-022.css';

export default function ScrollTextHighlighter({
  text = 'Great software experiences should feel as fluid and effortless as natural thought.',
  highlightColor = '#38bdf8'
}) {
  const viewportRef = useRef(null);
  const words = text.split(/\s+/);

  useEffect(() => {
    const vp = viewportRef.current;
    if (!vp) return;

    const onScroll = () => {
      const trigger = vp.scrollTop + vp.clientHeight * 0.55;
      const elements = vp.querySelectorAll('.m022-word');
      elements.forEach(el => {
        const top = el.offsetTop;
        if (top < trigger - 35) {
          el.className = 'm022-word is-lit';
        } else if (top < trigger) {
          el.className = 'm022-word is-focus';
        } else {
          el.className = 'm022-word';
        }
      });
    };

    vp.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => vp.removeEventListener('scroll', onScroll);
  }, [text]);

  return (
    <div className="m022-stage" style={{ '--m022-highlight-color': highlightColor }}>
      <div className="m022-viewport" ref={viewportRef}>
        <p className="m022-text-flow">
          {words.map((w, i) => (
            <span key={i} className="m022-word">{w} </span>
          ))}
        </p>
      </div>
    </div>
  );
}
.m022-stage {
  position: relative;
  width: 100%;
  max-width: 620px;
  border-radius: 16px;
  background: #090d16;
  border: 1px solid rgba(148, 163, 184, 0.2);
  overflow: hidden;
}

.m022-viewport {
  height: 300px;
  overflow-y: auto;
  padding: 36px 28px;
}

.m022-text-flow {
  font-size: 1.2rem;
  line-height: 1.8;
  font-weight: 600;
}

.m022-word {
  display: inline-block;
  opacity: 0.3;
  color: rgba(148, 163, 184, 0.25);
  transition: all 0.25s ease;
}

.m022-word.is-lit {
  color: #f8fafc;
  opacity: 1;
}

.m022-word.is-focus {
  color: var(--m022-highlight-color, #38bdf8);
  opacity: 1;
  text-shadow: 0 0 16px var(--m022-highlight-color, #38bdf8);
}

仕組みとカスタマイズHow It Works & Customization

仕組みHow it works

Splits body copy into inline words, calculating each word offset relative to a scroll trigger line (55% viewport height) to toggle lighting states.

文章を単語単位のインラインスパンに分解し、ビューポート中央(約55%位置)のトリガー線との交差判定で .is-lit や .is-focus クラスを動的に制御します。

カスタマイズ方法Customization

Customize highlight color using CSS custom property --m022-highlight-color and adjust trigger thresholds in script.

CSSカスタムプロパティ(--m022-highlight-color)で現在行のハイライト発光色を変更できます。

注意点Caveats

For large paragraphs, ensure words are batched or throttled to avoid excessive layout reads during rapid touch flings.

大量の長文に適用する場合は、スクロールハンドラーの間引き(throttle)や単語のoffsetTopキャッシュを行ってください。

よくある質問 Frequently Asked Questions

How can I support Japanese text without spaces? 日本語など空白で区切られていないテキストに対応できますか?

Tokenize by punctuation marks (、。) or use a client-side phrase segmentation library like Intl.Segmenter.

句読点での分割や、モダンブラウザ標準の Intl.Segmenter API を使用して文節単位にスパン分割することで綺麗に対応できます。

Can I style past words differently from unread words? 読了済みの単語と未読の単語でスタイルを変えられますか?

Yes, .is-lit targets fully illuminated past words, while unread words retain the default muted opacity.

はい、通過済みの単語には .is-lit が付与され、未読単語は薄暗い透過色のまま維持されます。

AIへの指示テンプレート AI Prompt Template

以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.