Skip to content

Instantly share code, notes, and snippets.

@albertogalca
Created August 7, 2025 11:50
Show Gist options
  • Save albertogalca/a46e9f8eb23286f5eca2559c88de0f34 to your computer and use it in GitHub Desktop.
Save albertogalca/a46e9f8eb23286f5eca2559c88de0f34 to your computer and use it in GitHub Desktop.
Hexagonal animation for Wanda
'use client';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
export default function Hexagon() {
const hexagonRef = useRef<SVGSVGElement>(null);
const pathRef = useRef<SVGPathElement>(null);
const secondaryLinesRef = useRef<(SVGPathElement | null)[]>([]);
const cornerLabels = ['Top', 'Top Right', 'Bottom Right', 'Bottom', 'Bottom Left', 'Top Left'];
useEffect(() => {
if (!pathRef.current) return;
const path = pathRef.current;
const pathLength = path.getTotalLength();
gsap.set(path, {
strokeDasharray: pathLength,
strokeDashoffset: pathLength
});
secondaryLinesRef.current.forEach((path) => {
if (path) {
const pathLength = path.getTotalLength();
gsap.set(path, {
strokeDasharray: pathLength,
strokeDashoffset: pathLength
});
}
});
}, []);
const getPathToCorner = (cornerIndex: number) => {
const startPoint = hexagonPoints[0]; // Start from top
let pathString = `M ${startPoint.x} ${startPoint.y}`;
// For top corner (index 0), complete the entire hexagon clockwise
if (cornerIndex === 0) {
// Complete full hexagon: top -> top right -> bottom right -> bottom -> bottom left -> top left -> back to top
for (let i = 1; i < hexagonPoints.length; i++) {
pathString += ` L ${hexagonPoints[i].x} ${hexagonPoints[i].y}`;
}
pathString += ` L ${hexagonPoints[0].x} ${hexagonPoints[0].y}`; // Close the loop
return pathString;
}
// For other corners, go clockwise around hexagon to reach them
for (let i = 1; i <= cornerIndex; i++) {
const point = hexagonPoints[i];
pathString += ` L ${point.x} ${point.y}`;
}
return pathString;
};
const handleTextHover = (index: number, isEntering: boolean) => {
const secondaryLine = secondaryLinesRef.current[index];
if (!secondaryLine) return;
// Kill any existing animations on this element to prevent conflicts
gsap.killTweensOf(secondaryLine);
if (isEntering) {
// Draw animation: from current position to 0 (draws the line)
gsap.to(secondaryLine, {
strokeDashoffset: 0,
opacity: 1,
duration: 0.8,
ease: "power2.out"
});
} else {
// Reverse animation: from current position to full offset (undoes/erases the line)
gsap.to(secondaryLine, {
strokeDashoffset: secondaryLine.getTotalLength(),
duration: 0.6,
ease: "power2.in"
});
}
};
const hexagonPoints = [
{ x: 200, y: 50 }, // Top
{ x: 300, y: 100 }, // Top Right
{ x: 300, y: 200 }, // Bottom Right
{ x: 200, y: 250 }, // Bottom
{ x: 100, y: 200 }, // Bottom Left
{ x: 100, y: 100 } // Top Left
];
const textPositions = [
{ x: 200, y: 15 }, // Top - more margin
{ x: 350, y: 100 }, // Top Right - more margin
{ x: 350, y: 200 }, // Bottom Right - more margin
{ x: 200, y: 295 }, // Bottom - more margin
{ x: 50, y: 200 }, // Bottom Left - more margin
{ x: 50, y: 100 } // Top Left - more margin
];
const hexagonCenter = { x: 200, y: 150 };
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<svg
ref={hexagonRef}
width="400"
height="300"
viewBox="0 0 400 300"
>
<path
ref={pathRef}
d={`M ${hexagonPoints[0].x} ${hexagonPoints[0].y}
L ${hexagonPoints[1].x} ${hexagonPoints[1].y}
L ${hexagonPoints[2].x} ${hexagonPoints[2].y}
L ${hexagonPoints[3].x} ${hexagonPoints[3].y}
L ${hexagonPoints[4].x} ${hexagonPoints[4].y}
L ${hexagonPoints[5].x} ${hexagonPoints[5].y} Z`}
fill="none"
stroke="#3b82f6"
strokeWidth="3"
/>
{textPositions.map((pos, index) => (
<path
key={`default-line-${index}`}
d={getPathToCorner(index)}
fill="none"
stroke="#000000"
strokeWidth="1"
opacity="0.8"
/>
))}
{textPositions.map((pos, index) => (
<path
key={`secondary-line-${index}`}
ref={(el) => (secondaryLinesRef.current[index] = el)}
d={getPathToCorner(index)}
fill="none"
stroke="#ef4444"
strokeWidth="3"
strokeDasharray="100%"
strokeDashoffset="100%"
opacity="0"
/>
))}
{textPositions.map((pos, index) => (
<text
key={`text-${index}`}
x={pos.x}
y={pos.y}
textAnchor="middle"
dominantBaseline="middle"
className="fill-gray-700 text-sm font-semibold cursor-default hover:fill-blue-600 select-none"
onMouseEnter={() => handleTextHover(index, true)}
onMouseLeave={() => handleTextHover(index, false)}
>
{cornerLabels[index]}
</text>
))}
</svg>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment