Skip to content

Instantly share code, notes, and snippets.

@mirshko
Last active May 5, 2021 13:12
Show Gist options
  • Save mirshko/988666680ca5a9ecd1c7a03d4d24f618 to your computer and use it in GitHub Desktop.
Save mirshko/988666680ca5a9ecd1c7a03d4d24f618 to your computer and use it in GitHub Desktop.
useMarquee6k
import { useRef } from "react";
import useMarquee from "../useMarquee";
export default function Marquee({ text, ...rest }) {
const ref = useRef<HTMLDivElement>();
useMarquee(ref);
return (
<div className="flex-1 truncate mt-0.5" {...rest}>
<div ref={ref}>
<div className="whitespace-nowrap leading-none pr-8">{text}</div>
</div>
</div>
);
}
import { useEffect, useRef } from "react";
export default function useAnimationFrame(
callback: (deltaTime: number) => void
) {
// Use useRef for mutable variables that we want to persist
// without triggering a re-render on their change
const requestRef = useRef<number>();
const previousTimeRef = useRef<number>();
const animate = (time: number) => {
if (previousTimeRef.current != undefined) {
const deltaTime = time - previousTimeRef.current;
callback(deltaTime);
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
};
useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []); // Make sure the effect runs only once
}
import { RefObject, useCallback, useEffect } from "react";
import useAnimationFrame from "./useAnimationFrame";
interface MarqueeOptions {
speed?: number;
reverse?: boolean;
}
export default function useMarquee(
ref: RefObject<Element> | Element | null,
options: MarqueeOptions = { speed: 0.25, reverse: false }
) {
const { speed, reverse } = options;
let element: Element;
let wrapper: HTMLDivElement;
let content: Element;
let parentProps: DOMRect;
let requiredReps: number;
let contentWidth: number;
let offset = 0;
const _createClone = useCallback(() => {
const clonedContentElement = content.cloneNode(true);
// @ts-ignore
clonedContentElement.style.display = "inline-block";
// @ts-ignore
clonedContentElement.classList.add(`6k__copy`);
wrapper.appendChild(clonedContentElement);
}, []);
const _setupWrapper = useCallback(() => {
const wrapperElement = document.createElement("div");
wrapperElement.classList.add("6k__wrapper");
wrapperElement.style.whiteSpace = "nowrap";
wrapper = wrapperElement;
}, []);
const _setupContent = useCallback(() => {
content.classList.add("6k__copy");
// @ts-ignore
content.style.display = "inline-block";
// @ts-ignore
contentWidth = content.offsetWidth;
requiredReps =
contentWidth > parentProps.width
? 2
: Math.ceil((parentProps.width - contentWidth) / contentWidth) + 1;
for (let i = 0; i < requiredReps; i++) {
_createClone();
}
if (reverse) {
offset = contentWidth * -1;
}
element.classList.add("6k__init");
}, [_createClone]);
const _animate = useCallback(() => {
const isScrolled = reverse ? offset < 0 : offset > contentWidth * -1;
const direction = reverse ? -1 : 1;
const reset = reverse ? contentWidth * -1 : 0;
if (isScrolled) {
offset -= speed * direction;
} else {
offset = reset;
}
wrapper.style.whiteSpace = "nowrap";
wrapper.style.transform = `translate(${offset}px, 0) translateZ(0)`;
}, []);
useEffect(() => {
if (ref === null) {
return;
}
element = ref instanceof Element ? ref : ref.current;
if (element === null) {
return;
}
parentProps = element.parentElement.getBoundingClientRect();
content = element.children[0];
_setupWrapper();
_setupContent();
wrapper.appendChild(content);
element.appendChild(wrapper);
}, [ref, _setupWrapper, _setupContent]);
useAnimationFrame(_animate);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment