✏️

Reactで一定の横幅を超えると自動横スクロールするテキストを作ってみた(Marquee)

2022/12/17に公開

これです。

App.tsx
import * as React from 'react';
import './style.css';
import './Marquee.tsx';
import { Marquee } from './Marquee';

export default function App() {
  return (
    <div>
      <h1>Hello Marquee</h1>
      <Marquee limitWidth={200}>
        Hi! This text is scrolling............ :)
      </Marquee>
      <Marquee limitWidth={200}>This text is not scroll!</Marquee>
    </div>
  );
}
style.css
.Marquee {
  position: relative;
}

.MarqueeWidthExtract {
  visibility: hidden;
  white-space: nowrap;
  position: absolute;
}

.MarqueeText {
  white-space: nowrap;
}
Marquee.tsx
import * as React from 'react';
import { ReactNode, FC, useEffect, useState, useRef } from 'react';
import { useSpring, animated } from '@react-spring/web';

export const Marquee: FC<{ children: ReactNode; limitWidth: number }> = ({
  children,
  limitWidth,
}) => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [startAnimation, setStartAnimation] = useState(false);
  const { translateX } = useSpring({
    config: {
      duration: 10000,
    },
    from: {
      translateX: '0%',
    },
    to: {
      translateX: '-100%',
    },
    delay: 2000,
    onRest: () => {
      setStartAnimation(false);
    },
  });

  useEffect(() => {
    if (!containerRef.current) return;

    const { width } = containerRef.current.getBoundingClientRect();
    if (width >= limitWidth) setStartAnimation(true);
  }, [limitWidth]);

  return (
    <div className="Marquee">
      {!startAnimation && (
        <span ref={containerRef} className="MarqueeWidthExtract">
          {children}
        </span>
      )}
      {!startAnimation && <span className="MarqueeText">{children}</span>}
      {startAnimation && (
        <animated.div
          style={{
            translateX: translateX,
          }}
        >
          <span className="MarqueeText">{children}</span>
        </animated.div>
      )}
    </div>
  );
};

ポイントはMarquee.tsxで、一度、テキストを改行させずに描画して、useEffectで横幅を取得したのちに、指定幅(limitWidth)を超えていれば、startAnimationフラグをtrueにします。startAnimationがtrueになると、react-springをつかって、テキストをアニメーションさせています。

いかはMarqueeWidthExtractクラスのスタイルです。white-space: nowrapで改行しないようにしています。visibility: hiddenposition: absoluteで、画面から隠しつつも、横幅を取れるように工夫しています。

.MarqueeWidthExtract {
  visibility: hidden;
  white-space: nowrap;
  position: absolute;
}

Discussion