Momentum Lines
December 2024
"use client";
import gsap from "gsap";
import React, {MouseEvent, useRef} from "react";
const THRESHOLD = 200;
const ARRAY_LENGTH = 17;
export default function MomentumLines() {
const containerRef = useRef<HTMLDivElement>(null);
const transformX = (el: HTMLDivElement, value: number) => {
const xTo = gsap.quickTo(el, "x", {
duration: 0.8,
ease: "power3.out",
});
xTo(value);
};
const toggleOpacity = (el: HTMLDivElement, value: number) => {
const opacityTo = gsap.quickTo(el, "opacity", {
duration: 0.8,
ease: "power3.out",
});
opacityTo(value);
};
const followMouse = (mouseX: number) => {
if (!containerRef.current) return;
const lines = containerRef.current?.querySelectorAll(".line");
const containerWidth = containerRef.current.offsetWidth;
lines.forEach((line, index) => {
const lineDiv = line as HTMLDivElement;
const relativeLeft =
lineDiv.offsetLeft - containerRef.current!.offsetLeft;
const xPercentage = mouseX / containerWidth;
const linePercentage = relativeLeft / containerWidth;
const gapPercentage = Math.abs(xPercentage - linePercentage);
const isLastOrFirst = !index || index === lines.length - 1;
const threshold = isLastOrFirst ? THRESHOLD / 2 : THRESHOLD;
const gapDiff = Math.min(
Math.max(mouseX - relativeLeft, -threshold),
threshold
);
transformX(lineDiv, gapDiff * (1 - gapPercentage));
toggleOpacity(lineDiv, 1 - gapPercentage);
});
};
const onMouseMove = (e: MouseEvent) => {
const mouseX = e.nativeEvent.offsetX;
followMouse(mouseX);
};
const onMouseOut = () => {
const lines = containerRef.current?.querySelectorAll(".line");
lines?.forEach((line) => {
transformX(line as HTMLDivElement, 0);
toggleOpacity(line as HTMLDivElement, 1);
});
};
return (
<article className="grid aspect-video place-items-center rounded-xl border border-white/10 bg-white/5">
<div
onMouseMove={onMouseMove}
onMouseOut={onMouseOut}
className="flex h-full w-full justify-between p-10"
ref={containerRef}>
{[...new Array(ARRAY_LENGTH).fill(0)].map((_, index) => {
return (
<div
key={index}
className="line pointer-events-none h-full w-px bg-white"></div>
);
})}
</div>
</article>
);
}