UNPKG

lightswind

Version:

A professionally designed animate react component library & templates market that brings together functionality, accessibility, and beautiful aesthetics for modern applications.

112 lines (111 loc) 5.59 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useRef, useEffect, useState } from "react"; import { motion, useScroll } from "framer-motion"; const ScrollList = ({ data, renderItem, itemHeight = 155, // Default item height }) => { // useRef to get a reference to the scrollable div element const listRef = useRef(null); // useState to keep track of the index of the currently focused item const [focusedIndex, setFocusedIndex] = useState(0); // useScroll hook from Framer Motion to track scroll progress (can be used for additional animations) const { scrollYProgress } = useScroll({ container: listRef }); useEffect(() => { const updateFocusedItem = () => { if (!listRef.current) return; const container = listRef.current; // Get all direct children (the motion.div items) const children = Array.from(container.children); const scrollTop = container.scrollTop; // Current vertical scroll position const containerCenter = container.clientHeight / 2; // Vertical center of the container let closestItemIndex = 0; let minDistanceToCenter = Infinity; // Initialize with a very large number // Iterate over each child item to find the one closest to the center children.forEach((child, index) => { const itemTop = child.offsetTop; // Top position of the item relative to its parent const actualItemHeight = child.offsetHeight; // Actual rendered height of the item const itemCenter = itemTop + actualItemHeight / 2; // Vertical center of the item // Calculate the distance from the item's center to the container's center, adjusted for scroll const distanceToCenter = Math.abs(itemCenter - scrollTop - containerCenter); // If this item is closer to the center than the previous closest if (distanceToCenter < minDistanceToCenter) { minDistanceToCenter = distanceToCenter; closestItemIndex = index; } }); // Update the focused index state setFocusedIndex(closestItemIndex); }; // Call immediately on mount to set initial focused item updateFocusedItem(); // Add scroll event listener to update focused item on scroll const listElement = listRef.current; if (listElement) { listElement.addEventListener("scroll", updateFocusedItem); } // Cleanup function: remove the event listener when the component unmounts return () => { if (listElement) { listElement.removeEventListener("scroll", updateFocusedItem); } }; }, [data, itemHeight]); // Dependencies: Re-run effect if data or itemHeight changes // Framer Motion Variants for defining animation states for each item const itemVariants = { hidden: { opacity: 0, scale: 0.7, transition: { duration: 0.35, ease: "easeOut" }, }, focused: { opacity: 1, scale: 1, zIndex: 10, transition: { duration: 0.35, ease: "easeOut" }, }, next: { opacity: 1, scale: 0.95, zIndex: 5, transition: { duration: 0.35, ease: "easeOut" }, }, visible: { opacity: 1, scale: 1, transition: { duration: 0.35, ease: "easeOut" }, }, }; return (_jsx("div", { ref: listRef, // Tailwind CSS classes for styling: hidden scrollbar, centered horizontally, full width className: "scroll-list__wrp scrollbar-hidden mx-auto w-full", // Inline style for fixed height and scrollability of the main container style: { height: "600px", overflowY: "auto" }, children: data.map((item, index) => { let variant = "hidden"; // Default variant // Determine the animation variant based on the item's position relative to the focused item if (index === focusedIndex) { variant = "focused"; // The currently focused item } else if (index === focusedIndex + 1) { variant = "next"; // The item immediately following the focused one } else { // Items within a certain range (2 items above/below) of the focused item are visible const isWithinVisibleRange = Math.abs(index - focusedIndex) <= 2; if (isWithinVisibleRange) { variant = "visible"; } } return (_jsx(motion.div, { // !!! IMPORTANT CHANGE HERE !!! // Use a fixed width Tailwind class, e.g., 'w-96' (384px) or 'w-[350px]' for a custom pixel value. // Using 'w-full' will make them take the full width of the parent, which is 'w-full' from scroll-list__wrp. className: "scroll-list__item mx-auto max-w-3xl" // Example: fixed width of 384px. Adjust as needed. , variants: itemVariants, initial: "hidden" // Initial animation state , animate: variant, // Set the height of each individual item. style: { height: itemHeight ? `${itemHeight}px` : "auto", }, children: renderItem(item, index) }, index)); }) })); }; export default ScrollList;