react-light-marquee
Version:
What goes around comes around! An ode to the HTML marquee element.
208 lines (206 loc) • 7.38 kB
JavaScript
// 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