Docs
GitHub Card - SkewGitHub Card - Skew
A card component which skews when hovered, as seen in GitHub's homepage
requires interactionhover
Loading...
Installation
CLI
pnpm dlx shadcn@latest add https://animata.design/r/card/github-card-skew.json
Manual
Copy useMousePosition hook
import { useEffect, useRef } from "react";
type Point = {
x: number;
y: number;
};
export function useMousePosition(
ref: React.RefObject<HTMLElement | null>,
callback?: (point: Point) => void,
) {
const callbackRef = useRef(callback);
const rectRef = useRef<DOMRect | null>(null);
const frameRef = useRef<number | null>(null);
const latestPointRef = useRef<Point | null>(null);
callbackRef.current = callback;
useEffect(() => {
const node = ref.current;
if (!node) return;
const updateRect = () => {
rectRef.current = node.getBoundingClientRect();
};
const flush = () => {
frameRef.current = null;
if (latestPointRef.current) {
callbackRef.current?.(latestPointRef.current);
}
};
const handlePointerMove = (event: PointerEvent) => {
if (!rectRef.current) {
updateRect();
}
const currentRect = rectRef.current;
if (!currentRect) return;
latestPointRef.current = {
x: event.clientX - currentRect.left,
y: event.clientY - currentRect.top,
};
if (frameRef.current === null) {
frameRef.current = requestAnimationFrame(flush);
}
};
updateRect();
node.addEventListener("pointerenter", updateRect);
node.addEventListener("pointermove", handlePointerMove, {
passive: true,
});
window.addEventListener("resize", updateRect);
window.addEventListener("scroll", updateRect, {
passive: true,
capture: true,
});
return () => {
node.removeEventListener("pointerenter", updateRect);
node.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("resize", updateRect);
window.removeEventListener("scroll", updateRect, true);
if (frameRef.current !== null) {
cancelAnimationFrame(frameRef.current);
}
};
}, [ref]);
}Run the following command
It will create a new file github-card-skew.tsx inside the components/animata/card directory.
mkdir -p components/animata/card && touch components/animata/card/github-card-skew.tsxPaste the code
Open the newly created file and paste the following code:
import { useCallback, useRef } from "react";
import { useMousePosition } from "@/hooks/use-mouse-position";
import { cn } from "@/lib/utils";
function calculateCardRotation({
currentX,
currentY,
centerX,
centerY,
maxRotationX,
maxRotationY,
}: {
currentX: number;
currentY: number;
centerX: number;
centerY: number;
maxRotationX: number;
maxRotationY: number;
}) {
// Calculate the distance from the center
const deltaX = currentX - centerX;
const deltaY = currentY - centerY;
// Calculate the maximum distance (assuming a rectangular area)
const maxDistance = Math.sqrt(centerX ** 2 + centerY ** 2);
// Calculate the actual distance
const distance = Math.sqrt(deltaX ** 2 + deltaY ** 2);
// Calculate the rotation factor (0 to 1)
const rotationFactor = distance / maxDistance;
// Calculate rotations (inverted for natural tilt effect)
const rotationY = ((-deltaX / centerX) * maxRotationY * rotationFactor).toFixed(2);
const rotationX = ((deltaY / centerY) * maxRotationX * rotationFactor).toFixed(2);
return { rotationX, rotationY };
}
export default function GithubCardSkew({ className }: { className?: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const resetRef = useRef<NodeJS.Timeout>(undefined);
const update = useCallback(({ x, y }: { x: number; y: number }) => {
if (!containerRef.current) {
return;
}
const { width, height } = containerRef.current.getBoundingClientRect();
const { rotationX, rotationY } = calculateCardRotation({
centerX: width / 2,
centerY: height / 2,
currentX: x,
currentY: y,
maxRotationX: 4,
maxRotationY: 6,
});
containerRef.current.style.setProperty("--x", `${rotationX}deg`);
containerRef.current.style.setProperty("--y", `${rotationY}deg`);
}, []);
useMousePosition(containerRef, update);
return (
<div
ref={containerRef}
className={cn(
"flex max-w-80 flex-col gap-4 rounded-3xl border border-border bg-zinc-700 p-10 text-zinc-200 shadow-lg transition-transform ease-linear will-change-transform",
className,
)}
style={{
transform: "perspective(400px) rotateX(var(--x)) rotateY(var(--y))",
transitionDuration: "50ms",
}}
onMouseEnter={() => {
resetRef.current = setTimeout(() => {
if (!containerRef.current) {
return;
}
// Reset the transition duration to 0 so that the mouse movement is smooth
containerRef.current.style.transitionDuration = "0ms";
}, 300);
}}
onMouseLeave={() => {
clearTimeout(resetRef.current);
if (!containerRef.current) {
return;
}
containerRef.current.style.transitionDuration = "50ms";
containerRef.current.style.setProperty("--x", "0deg");
containerRef.current.style.setProperty("--y", "0deg");
}}
>
<h1 className="font-mono text-6xl tracking-tight">55%</h1>
<p className="text-xl font-medium text-zinc-400">Developer preference for GitHub Copilot</p>
<span className="mt-4 text-sm text-zinc-400">Stack Overflow 2023 Survey</span>
</div>
);
}Credits
Built by hari
Inspired by GitHub's homepage