forked from Weaverse/pilot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
marquee.tsx
110 lines (107 loc) · 2.84 KB
/
marquee.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {
type CSSProperties,
useCallback,
useEffect,
useRef,
useState,
} from "react";
interface MarqueeProps {
children: React.ReactNode;
gap?: number;
speed?: number;
rollAsNeeded?: boolean;
}
const ANIMATION_SPEED = 100;
export function Marquee({
children,
gap = 0,
speed = ANIMATION_SPEED,
rollAsNeeded,
}: MarqueeProps) {
const contentRef = useRef<HTMLDivElement>(null);
const [contentWidth, setContentWidth] = useState(0);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if (contentRef.current) {
const { width } = contentRef.current.getBoundingClientRect();
if (rollAsNeeded) {
if (width < window.innerWidth) {
setContentWidth(0);
} else {
setContentWidth(width);
}
} else {
setContentWidth(width);
}
}
}, []);
const maxAnimationTime = 400000; // 100 seconds - slowest speed 0% - 0 speed
const minAnimationTime = 1000; // 1 second - fastest speed 100% - 100 speed
const animationTime =
((100 - speed) * (maxAnimationTime - minAnimationTime)) / 100 +
minAnimationTime;
return (
<div
className="flex items-center w-full"
style={
{
"--animation-speed": `${animationTime}ms`,
"--gap": `${gap}px`,
} as CSSProperties
}
>
{contentWidth === 0 ? (
<div ref={contentRef} className="mx-auto">
{children}
</div>
) : (
<OneView contentWidth={contentWidth} gap={gap}>
{children}
</OneView>
)}
</div>
);
}
function OneView({
children,
contentWidth,
gap,
}: {
children: React.ReactNode;
contentWidth: number;
gap: number;
}) {
const [contentRepeat, setContentRepeat] = useState(0);
const calculateRepeat = useCallback(() => {
if (contentWidth < window.innerWidth) {
// if it is, set the contentRepeat to the number of times the text can fit on the screen
const repeat = Math.ceil(window.innerWidth / (contentWidth + gap));
setContentRepeat(repeat);
} else {
// setContentRepeat(3);
}
}, [contentWidth, gap]);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
calculateRepeat();
window.addEventListener("resize", calculateRepeat);
return () => {
window.removeEventListener("resize", calculateRepeat);
};
}, []);
let oneView = (
<div className="flex" style={{ paddingRight: gap, gap }}>
{Array.from({ length: contentRepeat || 1 }).map((_, index) => (
<div key={index} className="shrink-0">
{children}
</div>
))}
</div>
);
return (
<div className="flex items-center">
<div className="shrink-0 animate-marquee">{oneView}</div>
<div className="shrink-0 animate-marquee">{oneView}</div>
</div>
);
}