UNPKG

react-light-marquee

Version:

What goes around comes around! An ode to the HTML marquee element.

208 lines (206 loc) 7.38 kB
// src/Marquee/index.tsx import { useEffect, useRef, useState } from "react"; // src/Marquee/utils.ts var getWrapperClassName = (marqueeId) => { return `${marqueeId}_wrapper`; }; var getPlayStateVariable = (marqueeId) => { return `--${marqueeId}_play_state`; }; var getWrapperStyles = (direction, rotateInDeg) => { return { width: "100%", height: "100%", display: "flex", flexDirection: direction === "left" || direction === "right" ? "row" : "column", overflow: "hidden", listStyle: "none", ...rotateInDeg && { transform: rotateInDeg } }; }; var handleInitialSlide = (initialSlideIndex, slides) => { if (!initialSlideIndex) return slides; return slides.slice(initialSlideIndex).concat(slides.slice(0, initialSlideIndex)); }; var generateSlideStyles = (wrapperClassName, marqueeId, slideIndex, translateProp, rotateInDeg, animationConfigs) => { const animations = []; const styles = []; animationConfigs.forEach((animation, index) => { const keyFrameId = `${marqueeId}_keyframe_slide${slideIndex}_${index}`; styles.push(`@keyframes ${keyFrameId} { 0% { transform: ${translateProp}(${animation.from}) ${rotateInDeg}; } 100% { transform: ${translateProp}(${animation.to}) ${rotateInDeg}; } } `); animations.push( `${animation.duration}ms linear ${animation.delay}ms ${animation.count} ${keyFrameId}` ); }); styles.push(`.${wrapperClassName} > :nth-child(${slideIndex + 1}) { animation: ${animations.join(",")}; animation-play-state: var(${getPlayStateVariable(marqueeId)}); }`); return styles; }; var getTranslateProp = (direction) => { return direction === "left" || direction === "right" ? "translateX" : "translateY"; }; var getRotateInDeg = (direction) => { let deg = 0; let rotateStyle = ""; if (direction === "right") { deg = 180; rotateStyle = "rotateY"; } if (direction === "down") { deg = 180; rotateStyle = "rotateX"; } return deg ? `${rotateStyle}(${deg}deg)` : ""; }; var getDistanceBetweenEdges = (direction, startRect, endRect) => { let distance = startRect.bottom - endRect.top; if (direction === "left") distance = endRect.right - startRect.left; else if (direction === "right") distance = startRect.right - endRect.left; else if (direction === "up") distance = endRect.bottom - startRect.top; return distance; }; var getContentSize = (children, direction) => { const firstChildRect = children[0].getBoundingClientRect(); const lastChildRect = children[children.length - 1].getBoundingClientRect(); return getDistanceBetweenEdges(direction, firstChildRect, lastChildRect); }; var getSlideEndingEdge = (parentElement, slideElement, direction) => { const parentRect = parentElement.getBoundingClientRect(); const slideRect = slideElement.getBoundingClientRect(); return getDistanceBetweenEdges(direction, parentRect, slideRect); }; var getNodeSize = (node, direction) => { const rect = node.getBoundingClientRect(); return direction === "left" || direction === "right" ? rect.right - rect.left : rect.bottom - rect.top; }; var handleReplication = (wrapper, direction, children) => { const wrapperSize = getNodeSize(wrapper, direction); const finalSlides = [...children]; const slideEdges = []; const childElements = Array.from(wrapper.children); const initialSlidesCount = childElements.length; let contentSize = getContentSize(childElements, direction); let needsReplication = false; childElements.forEach((slide) => { slideEdges.push(getSlideEndingEdge(wrapper, slide, direction)); needsReplication || (needsReplication = contentSize - getNodeSize(slide, direction) < wrapperSize); }); if (needsReplication) { let replicationCount = 1; if (contentSize <= wrapperSize) { replicationCount = Math.floor(wrapperSize / contentSize + 1); } for (let i = 1; i <= replicationCount; i++) { finalSlides.push(...children); const offset = contentSize * i; for (let j = 0; j < initialSlidesCount; j++) { slideEdges.push(slideEdges[j] + offset); } } contentSize = (replicationCount + 1) * contentSize; } return { finalSlides, slideEdges, contentSize }; }; // src/Marquee/index.tsx import { jsx } from "react/jsx-runtime"; var Marquee = ({ id, speed = 50, play = true, children = [], direction = "left", initialSlideIndex = 0, pauseOnHover = false }) => { const wrapperRef = useRef(null); const [marqueeId] = useState(`marquee_${id || (Math.random() + "").slice(2)}`); const [rotateInDeg] = useState(getRotateInDeg(direction)); const [slides, setSlides] = useState( () => handleInitialSlide(initialSlideIndex, children) ); useEffect(() => { const wrapper = wrapperRef?.current; if (!wrapper || !marqueeId) return; var styleElement = document.createElement("style"); styleElement.setAttribute("type", "text/css"); styleElement.setAttribute("id", marqueeId); document.head.appendChild(styleElement); const styleSheet = styleElement.sheet; const playStateVariable = getPlayStateVariable(marqueeId); const wrapperClassName = getWrapperClassName(marqueeId); const insertRule = (rule) => !!rule && styleSheet?.insertRule?.(rule, 0); [ `:root { ${playStateVariable}: ${play ? "running" : "paused"}; }`, pauseOnHover ? `.${wrapperClassName}:hover { ${playStateVariable}: paused; }` : "" ].forEach(insertRule); const translateProp = getTranslateProp(direction); const { slideEdges, finalSlides, contentSize } = handleReplication( wrapper, direction, slides ); const totalAnimationDuration = contentSize / speed * 1e3 | 0; setSlides(finalSlides); slideEdges.forEach((slideEndingEdge, index) => { const firstAnimationDuration = slideEndingEdge / speed * 1e3 | 0; const animationConfigs = [ { from: "0px", to: `${-slideEndingEdge}px`, delay: 0, duration: firstAnimationDuration, count: 1 }, { from: `${contentSize - slideEndingEdge}px`, to: `${-slideEndingEdge}px`, delay: firstAnimationDuration, duration: totalAnimationDuration, count: "infinite" } ]; generateSlideStyles( wrapperClassName, marqueeId, index, translateProp, rotateInDeg, animationConfigs ).forEach(insertRule); }); return () => { document.documentElement.style.removeProperty(playStateVariable); document.head.removeChild(styleElement); }; }, []); useEffect(() => { const wrapper = wrapperRef?.current; if (!wrapper || !marqueeId) return; const playStateVariable = getPlayStateVariable(marqueeId); document.documentElement.style.setProperty(playStateVariable, play ? "running" : "paused"); }, [play]); if (!children.length) { return null; } return /* @__PURE__ */ jsx("ul", { ref: wrapperRef, className: getWrapperClassName(marqueeId), style: getWrapperStyles(direction, rotateInDeg), children: slides.map((slide, slideIndex) => /* @__PURE__ */ jsx("li", { children: slide }, slideIndex)) }); }; Marquee.displayName = "Marquee"; var Marquee_default = Marquee; export { Marquee_default as default }; //# sourceMappingURL=index.mjs.map