Created
October 23, 2022 16:34
-
-
Save maccman/3db4b461903d23c84de302aba396c9d4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.marquee { | |
@apply flex-1 flex overflow-hidden; | |
} | |
.marquee .marquee-inner { | |
@apply truncate flex-1; | |
} | |
.marquee.marquee-active .marquee-inner { | |
@apply flex-none; | |
animation: marquee-horizontal var(--marquee-duration, 10s) linear forwards; | |
animation-delay: 700ms; | |
animation-play-state: var(--marquee-play); | |
} | |
.marquee.marquee-hover-pause.marquee-active:hover .marquee-inner { | |
animation-play-state: paused; | |
} | |
@keyframes marquee-horizontal { | |
0% { | |
transform: translateX(0); | |
} | |
100% { | |
transform: translateX(var(--marquee-offset)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import clsx from 'clsx' | |
import React from 'react' | |
import {useDimensions} from '../hooks/use-dimensions' | |
interface MarqueeProps { | |
active?: boolean | |
speed?: number | |
hoverToPause?: boolean | |
} | |
export const Marquee: React.FC<MarqueeProps> = ({ | |
children, | |
active = true, | |
speed = 20, | |
hoverToPause = true, | |
}) => { | |
const [outerRef, outerDimensions] = useDimensions([active]) | |
const [innerRef, innerDimensions] = useDimensions([active]) | |
const offset = Math.ceil(innerDimensions.width - outerDimensions.width) | |
const duration = offset / speed | |
return ( | |
<div | |
ref={outerRef} | |
className={clsx( | |
'marquee', | |
active && 'marquee-active', | |
hoverToPause && 'marquee-hover-pause', | |
)} | |
> | |
<div | |
ref={innerRef} | |
className="marquee-inner" | |
style={{ | |
['--marquee-play' as string]: active ? 'running' : 'paused', | |
['--marquee-duration' as string]: `${duration}s`, | |
['--marquee-offset' as string]: `-${offset}px`, | |
}} | |
> | |
{children} | |
</div> | |
</div> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {isEqual} from 'lodash-es' | |
import {DependencyList, RefObject, useEffect, useRef, useState} from 'react' | |
type Dimensions = {width: number; height: number} | |
export const useDimensions = ( | |
deps?: DependencyList, | |
): [ref: RefObject<HTMLDivElement>, dimensions: Dimensions] => { | |
const [dimensions, setDimensions] = useState<Dimensions>({width: 0, height: 0}) | |
const ref = useObserveDimensions(setDimensions, deps) | |
return [ref, dimensions] | |
} | |
export const useObserveDimensions = ( | |
callback: (dimensions: Dimensions) => void, | |
deps?: DependencyList, | |
) => { | |
const ref = useRef<HTMLDivElement>(null) | |
const dimensionsRef = useRef({width: 0, height: 0}) | |
const element = ref.current | |
useEffect(() => { | |
if (!element) return | |
const updateDimensions = ({width, height}: Dimensions) => { | |
const dimensions: Dimensions = {width, height} | |
if (!isEqual(dimensions, dimensionsRef.current)) { | |
callback?.(dimensions) | |
} | |
} | |
if (typeof ResizeObserver === 'undefined') { | |
updateDimensions(element.getBoundingClientRect()) | |
return | |
} | |
const observer = new ResizeObserver(([entry]) => { | |
updateDimensions(entry.contentRect) | |
}) | |
observer.observe(element) | |
return () => { | |
observer.disconnect() | |
} | |
}, [element, callback, ...(deps ?? [])]) | |
return ref | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment