Component Preview
0.0%
'use client';
import {useEffect, useRef} from 'react';
import {motion, useMotionValue, useSpring, useTransform, type MotionValue} from 'framer-motion';
import {cn} from '@/lib/utils';
const SPRING_CONFIG = {
stiffness: 180,
damping: 40,
};
function Bar({height, color, width}: {height: string; color: string; width: any}) {
return <motion.div style={{width}} className={cn(height, color)} />;
}
function Bars({mouseY, TOTAL}: {mouseY: MotionValue<number>; TOTAL: number}) {
return (
<div
className="absolute inset-0 grid"
style={{
gridTemplateRows: `repeat(${TOTAL}, 1fr)`,
}}
>
{Array.from({length: TOTAL}).map((_, idx) => {
const isBig = idx % 5 === 0;
const barPosition = idx / TOTAL;
const distance = useTransform(mouseY, y => Math.abs(y - barPosition));
const width = useTransform(distance, d => {
const max = isBig ? 120 : 80;
const min = isBig ? 40 : 20;
const spread = 0.35;
const normalized = Math.min(d / spread, 1);
const eased = Math.pow(1 - normalized, 2.5);
return min + (max - min) * eased;
});
const smoothWidth = useSpring(width, SPRING_CONFIG);
return (
<Bar
key={idx}
height="h-px"
width={smoothWidth}
color={isBig ? 'bg-black' : 'bg-neutral-500'}
/>
);
})}
</div>
);
}
function CursorLine({
mouseY,
containerHeight,
}: {
mouseY: MotionValue<number>;
containerHeight: MotionValue<number>;
}) {
const yPx = useTransform([mouseY, containerHeight], ([y, h]: any) => y * h);
const smoothY = useSpring(yPx, {
stiffness: 200,
damping: 30,
});
const percent = useTransform(mouseY, y => `${(y * 100).toFixed(1)}%`);
return (
<>
<motion.div
style={{y: smoothY}}
className="absolute left-0 w-full h-px bg-red-500 pointer-events-none"
/>
<motion.div
style={{y: smoothY}}
className="absolute right-4 top-2 -translate-y-1/2 text-[10px] font-mono text-red-400 pointer-events-none"
>
<motion.span>{percent}</motion.span>
</motion.div>
</>
);
}
function StrechedBars() {
const mouseY = useMotionValue(0);
const containerHeight = useMotionValue(0);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const updateSize = () => {
containerHeight.set(el.getBoundingClientRect().height);
};
updateSize();
window.addEventListener('resize', updateSize);
const handleMouseMove = (e: MouseEvent) => {
const rect = el.getBoundingClientRect();
const y = (e.clientY - rect.top) / rect.height;
mouseY.set(Math.max(0, Math.min(1, y)));
};
el.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('resize', updateSize);
el.removeEventListener('mousemove', handleMouseMove);
};
}, [mouseY, containerHeight]);
const TOTAL = 30;
return (
<div ref={containerRef} className="relative size-full">
<Bars mouseY={mouseY} TOTAL={TOTAL} />
<CursorLine mouseY={mouseY} containerHeight={containerHeight} />
</div>
);
}
export default StrechedBars;
Streched Bars
An interactive bar field that dynamically responds to cursor movement, using distance-based interpolation and spring physics to create a fluid, wave-like motion effect.
Framer MotionInteractive UIMotion ValuesSpring AnimationCursor Tracking