✏️
Reactで一定の横幅を超えると自動横スクロールするテキストを作ってみた(Marquee)
これです。
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: hidden
とposition: absolute
で、画面から隠しつつも、横幅を取れるように工夫しています。
.MarqueeWidthExtract {
visibility: hidden;
white-space: nowrap;
position: absolute;
}
Discussion