isirmtブログ
タグシリーズ投稿
お気に入りのタグ

ブログを書いている人

isirmt

ホームページ

GitHub
github_user_icon
isirmt
Software & Web
@isirmt

(管理者用)ダッシュボード

© isirmtBuild with NextjsBlogWithGitPAT  (21df4dc)
  1. 枠外のテキストもスライドアニメーションで表示するReactコンポーネント
  2. コンポーネント
  3. 呼び出し例
  4. 解説
共有
2026
1
15

枠外のテキストもスライドアニメーションで表示するReactコンポーネント

コメント: 0件
Next.js
React
Tailwind CSS
Web Animation API
isirmt.com v2 制作メモ (2件)

前の投稿

2026
1
15

シリーズisirmt.com v2ができました

↑ タグの付けすぎ みなさん あけましておめでとうございます 今年もよろしくお願いします サイトのアップデートだけされて...

読み込み中
シリーズを表示
Next.js
React
Tailwind CSS
Lightsail
PostgreSQL
Go
Docker
Docker Compose
Echo
TypeScript
Gorm
Gorm Gen
Three.js
WebGL
Auth.js
WebSocket

次の投稿

最新の投稿です

テキストが長いときに、画面からはみ出したり完全に隠すのではなく、アニメーションを使って、テキストの左端と右端がスライドアニメーションで映るようにするためのReactコンポーネントです。

コンポーネント

marqueeText.tsx
"use client";
import React, { useCallback, useEffect, useRef } from "react";

const clamp = (value: number, min: number, max: number) =>
  Math.min(Math.max(value, min), max);

type Props = {
  text: string;
  containerClassName?: string;
  innerClassName?: string;
  containerStyle?: React.CSSProperties;
  speedFactor?: number;
  pauseSeconds?: number;
};

export default function MarqueeText({
  text,
  containerClassName = "",
  innerClassName = "",
  containerStyle,
  speedFactor = 40,
  pauseSeconds = 1.5,
}: Props) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const innerRef = useRef<HTMLDivElement | null>(null);
  const animationRef = useRef<Animation | null>(null);

  const calcAnimation = useCallback(() => {
    const c = containerRef.current;
    const i = innerRef.current;
    if (!c || !i) return;

    const cw = c.clientWidth;
    const iw = i.scrollWidth;
    const distance = iw - cw;
    if (distance <= 2) {
      animationRef.current?.cancel();
      animationRef.current = null;
      i.style.transform = "translateX(0)";
      return;
    }

    const moveTime = clamp(distance / speedFactor, 1.2, 8);
    const total = pauseSeconds + moveTime + pauseSeconds;
    const startPauseOffset = pauseSeconds / total;
    const moveEndOffset = (pauseSeconds + moveTime) / total;
    const almostEndOffset = 0.99999;
    const endTranslate = `-${distance}px`;

    animationRef.current?.cancel();
    animationRef.current = i.animate(
      [
        { transform: "translateX(0)", offset: 0 },
        { transform: "translateX(0)", offset: startPauseOffset },
        { transform: `translateX(${endTranslate})`, offset: moveEndOffset },
        { transform: `translateX(${endTranslate})`, offset: almostEndOffset },
        { transform: "translateX(0)", offset: 1 },
      ],
      {
        duration: total * 1000,
        easing: "linear",
        iterations: Infinity,
      },
    );
  }, [pauseSeconds, speedFactor]);

  useEffect(() => {
    const onResize = () => calcAnimation();
    calcAnimation();
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
      animationRef.current?.cancel();
      animationRef.current = null;
    };
  }, [calcAnimation, text]);

  return (
    <div
      ref={containerRef}
      className={containerClassName}
      style={{ overflow: "hidden", display: "block", ...containerStyle }}
      title={text}
    >
      <div ref={innerRef} className={`marquee-inner ${innerClassName}`}>
        {text}
      </div>
    </div>
  );
}

呼び出し例

return <div className="max-w-full whitespace-nowrap">
  <MarqueeText text={title} />
</div>

解説

Web Animation APIを使った実装になります。refでDOMを取得し、animationの定義をuseEffectで画面幅が変わるたびにセットしなおすことにより実現しています。

containerRefとinnerRefの幅を取得して、どれだけテキストがはみ出ているかを計算後、speedFactorに基づいて移動時間の総計を求めます。

アニメーションの前後にはポーズ期間があるので、端のテキストもしっかり読むことが可能です。

コメント

自動更新
コメントはまだありません
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.