"use client";
import React, { useState, useRef, useEffect } from "react";
import { useInView } from "framer-motion";
import { cn } from "@/lib/utils";
interface ImageSplitProps extends React.HTMLAttributes<HTMLDivElement> {
src: string;
sections?: number;
offsetStep?: number;
duration?: number;
className?: string;
}
export default function ImageSplit({
src,
sections = 9,
offsetStep = 20,
duration = 0.6,
className,
...props
}: ImageSplitProps) {
const [imagePieces, setImagePieces] = useState<string[]>([]);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const parentRef = useRef<HTMLDivElement | null>(null);
const imgRefs = useRef<(HTMLImageElement | null)[]>([]);
const isInView = useInView(parentRef, { once: false });
useEffect(() => {
const image = new Image();
image.src = src;
image.onload = () => cutImageUp(image);
}, [src, sections]);
useEffect(() => {
if (isInView) {
animateSlices();
} else {
reverseSlices();
}
}, [isInView]);
const cutImageUp = (image: HTMLImageElement) => {
const canvas = canvasRef.current;
if (!canvas) return;
const pieceWidth = Math.floor(image.width / sections);
const pieceHeight = image.height;
const context = canvas.getContext("2d");
if (!context) return;
const newImagePieces: string[] = [];
for (let i = 0; i < sections; i++) {
canvas.width = pieceWidth;
canvas.height = pieceHeight;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(
image,
i * pieceWidth,
0,
pieceWidth,
pieceHeight,
0,
0,
pieceWidth,
pieceHeight
);
newImagePieces.push(canvas.toDataURL());
}
setImagePieces(newImagePieces);
};
const getOffset = (index: number) => {
if (index === 0 || index === sections - 1) return 0;
const distanceFromEdge = Math.min(index, sections - 1 - index);
return distanceFromEdge * offsetStep;
};
const animateSlices = () => {
const startTime = performance.now();
const animate = (time: number) => {
const elapsed = time - startTime;
const progress = Math.min(elapsed / (duration * 1000), 1);
imgRefs.current.forEach((img, index) => {
if (img) {
const initialOffset = getOffset(index);
img.style.top = `${initialOffset * (1 - progress)}px`;
}
});
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
};
const reverseSlices = () => {
const startTime = performance.now();
const animate = (time: number) => {
const elapsed = time - startTime;
const progress = Math.min(elapsed / (duration * 1000), 1);
imgRefs.current.forEach((img, index) => {
if (img) {
const initialOffset = getOffset(index);
img.style.top = `${initialOffset * progress}px`;
}
});
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
};
return (
<div
ref={parentRef}
className={cn("flex relative w-full rounded-[inherit]", className)}
{...props}
>
<canvas ref={canvasRef} className="hidden" />
{imagePieces.map((piece, index) => (
<img
key={index}
src={piece}
alt={`section-${index}`}
ref={(el) => {
if (el) {
imgRefs.current[index] = el;
}
}}
className={cn("object-contain", {
"rounded-l-[inherit]": index === 0,
"rounded-r-[inherit]": index === imagePieces.length - 1,
})}
style={{
position: "relative",
top: `${getOffset(index)}px`,
width: `${100 / sections}%`,
}}
/>
))}
</div>
);
}