I was inspired by Fractional Slider by Rauno Freiberg and recreated it using Framer Motion and React. My focus was primarily on the motion and the sound design of these components.
<TabContent tabId="overview" activeTab={activeTab}>
<motion.div
className="flex gap-2 overflow-x-auto mx-auto h-[500px] max-w-[800px] "
layoutId="images"
transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }}
>
{images.map((image) => {
return (
<motion.div
className="shadow-lg"
key={image.link}
whileHover={{
width: '350px',
transition: { duration: 0.2, easings: 'spring' },
}}
>
<Image
src={image.link}
width={400}
height={600}
onClick={() => clickHandler(image.id)}
alt="placeholder"
className="rounded-md h-full object-cover cursor-pointer "
></Image>
</motion.div>
)
})}
</motion.div>
</TabContent>
I started by creating my state variables as well as the refs and the individual slider-range. I used the useMotionValue hook from Framer Motion to track the drag position.
const range = [-20, 20];
const sliderRef = useRef<HTMLDivElement>(null);
const [value, setValue] = useState<number>(0);
const items = Array.from({
length: range[1] - range[0] + 1 }(_, i) => i + range[0]
);
const [maxWidth, setMaxWidth] = useState(0);
const x = useMotionValue(0);
TSX elements and Framer-motion setup
I created the individual slider elements and passed the value. I also passed an active prop to track if the element is currently within the selected range.
I used Framer Motion's "drag" option to implement the slider-dragging logic. To create a seamless experience, I used the dragConstraints option to limit the dragging to the slider-range.
To create the selecting logic, I used React state. I used some math to calculate the current value and set the state on every change. Then I also added a click sound effect whenever the value changes.
useEffect(() => {
const updateMaxWidth = () => {
const width = sliderRef.current?.offsetWidth
if (width) {
setMaxWidth(width / 2)
}
}
updateMaxWidth()
window.addEventListener('resize', updateMaxWidth)
return () => window.removeEventListener('resize', updateMaxWidth)
}, [])