93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
"use client"
|
|
|
|
import { cx } from "class-variance-authority"
|
|
import { useEffect, useRef, useState } from "react"
|
|
|
|
import styles from "./marqueeText.module.css"
|
|
|
|
interface MarqueeTextProps
|
|
extends React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> {
|
|
backgroundColor: string
|
|
textWrapperClassName?: string
|
|
}
|
|
|
|
export function MarqueeText({
|
|
backgroundColor,
|
|
children,
|
|
className,
|
|
textWrapperClassName,
|
|
...props
|
|
}: MarqueeTextProps) {
|
|
const textContainerRef = useRef<HTMLDivElement>(null)
|
|
const [dimensions, setDimensions] = useState({
|
|
containerWidth: 0,
|
|
contentWidth: 0,
|
|
isOverflowing: false,
|
|
})
|
|
|
|
useEffect(() => {
|
|
const element = textContainerRef.current
|
|
const parentElement = element?.parentElement
|
|
if (!parentElement) {
|
|
return
|
|
}
|
|
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
const containerWidth = element.clientWidth
|
|
const contentWidth = element.scrollWidth
|
|
const isOverflowing = contentWidth > containerWidth
|
|
|
|
setDimensions({
|
|
containerWidth,
|
|
contentWidth,
|
|
isOverflowing,
|
|
})
|
|
|
|
if (isOverflowing && containerWidth > 0) {
|
|
const scrollDistance = contentWidth - containerWidth
|
|
parentElement.style.setProperty(
|
|
"--scroll-distance",
|
|
`${scrollDistance}px`
|
|
)
|
|
|
|
// Calculate dynamic animation duration based on scroll distance
|
|
// This is done to avoid long scrolling durations for small distances and vice versa
|
|
// Base formula: minimum 4000ms, add 60ms per pixel of scroll distance. The duration
|
|
// includes start and end pauses.
|
|
const baseDuration = 4000
|
|
const durationPerPixel = 60
|
|
const calculatedDuration =
|
|
baseDuration + scrollDistance * durationPerPixel
|
|
|
|
parentElement.style.setProperty(
|
|
"--animation-duration",
|
|
`${calculatedDuration}ms`
|
|
)
|
|
}
|
|
})
|
|
|
|
resizeObserver.observe(element)
|
|
|
|
return () => resizeObserver.disconnect()
|
|
}, [])
|
|
|
|
return (
|
|
<div
|
|
className={cx(styles.marqueeText, className)}
|
|
style={
|
|
{ "--marquee-background-color": backgroundColor } as React.CSSProperties
|
|
}
|
|
{...props}
|
|
>
|
|
<div
|
|
ref={textContainerRef}
|
|
className={cx(styles.textWrapper, textWrapperClassName, {
|
|
[styles.overflowing]: dimensions.isOverflowing,
|
|
})}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|