gridloc / src /components /ui /wave-path.tsx
lassanmonster's picture
GridLoc: Bengaluru parking congestion digital twin
db3dbe1
Raw
History Blame Contribute Delete
1.98 kB
'use client';
import React from 'react';
import { cn } from '../../lib/utils';
import { useRef, useEffect } from 'react';
type WWavePathProps = React.ComponentProps<'div'>;
export function WavePath({ className, ...props }: WWavePathProps) {
const path = useRef<SVGPathElement>(null);
let progress = 0;
let x = 0.2;
let time = Math.PI / 2;
let reqId: number | null = null;
useEffect(() => {
setPath(progress);
}, []);
const setPath = (progress: number) => {
const width = window.innerWidth;
if (path.current) {
path.current.setAttributeNS(
null,
'd',
`M0 100 Q${width * x} ${100 + progress * 0.6}, ${width} 100`,
);
}
};
const lerp = (x: number, y: number, a: number) => x * (1 - a) + y * a;
const manageMouseEnter = () => {
if (reqId) {
cancelAnimationFrame(reqId);
resetAnimation();
}
};
const manageMouseMove = (e: React.MouseEvent) => {
const { movementY, clientX } = e;
if (path.current) {
const pathBound = path.current.getBoundingClientRect();
x = (clientX - pathBound.left) / pathBound.width;
progress += movementY;
setPath(progress);
}
};
const manageMouseLeave = () => {
animateOut();
};
const animateOut = () => {
const newProgress = progress * Math.sin(time);
progress = lerp(progress, 0, 0.025);
time += 0.2;
setPath(newProgress);
if (Math.abs(progress) > 0.75) {
reqId = requestAnimationFrame(animateOut);
} else {
resetAnimation();
}
};
const resetAnimation = () => {
time = Math.PI / 2;
progress = 0;
};
return (
<div className={cn('relative h-px w-full', className)} {...props}>
<div
onMouseEnter={manageMouseEnter}
onMouseMove={manageMouseMove}
onMouseLeave={manageMouseLeave}
className="relative -top-5 z-10 h-10 w-full hover:-top-[150px] hover:h-[300px]"
/>
<svg className="absolute -top-[100px] h-[300px] w-full">
<path ref={path} className="fill-none stroke-[var(--lp-line)]" strokeWidth={1} />
</svg>
</div>
);
}