react-light-marquee
Version:
What goes around comes around! An ode to the HTML marquee element.
231 lines (227 loc) • 8.44 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
default: () => Marquee_default
});
module.exports = __toCommonJS(src_exports);
// src/Marquee/index.tsx
var import_react = require("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
var import_jsx_runtime = require("react/jsx-runtime");
var Marquee = ({
id,
speed = 50,
play = true,
children = [],
direction = "left",
initialSlideIndex = 0,
pauseOnHover = false
}) => {
const wrapperRef = (0, import_react.useRef)(null);
const [marqueeId] = (0, import_react.useState)(`marquee_${id || (Math.random() + "").slice(2)}`);
const [rotateInDeg] = (0, import_react.useState)(getRotateInDeg(direction));
const [slides, setSlides] = (0, import_react.useState)(
() => handleInitialSlide(initialSlideIndex, children)
);
(0, import_react.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);
};
}, []);
(0, import_react.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__ */ (0, import_jsx_runtime.jsx)("ul", { ref: wrapperRef, className: getWrapperClassName(marqueeId), style: getWrapperStyles(direction, rotateInDeg), children: slides.map((slide, slideIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: slide }, slideIndex)) });
};
Marquee.displayName = "Marquee";
var Marquee_default = Marquee;
//# sourceMappingURL=index.js.map