Like
- Source
- After bencho.dev ↗
- Author
- Interaction after bencho.dev
- Licence
- Original code — interaction after bencho.dev
Loading…
Tap the heart to like; it pops, sparks and rolls the count.
Code
"use client";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import { Surface } from "../ui";
const SPARKS = Array.from({ length: 8 }, (_, i) => (i / 8) * Math.PI * 2);
function Count({ value }: { value: number }) {
const s = value.toLocaleString("en-US");
return (
<span className="relative inline-flex tabular-nums">
{s.split("").map((ch, i) => (
<span key={i} className="relative inline-block h-[1.3em] overflow-hidden">
<AnimatePresence mode="popLayout" initial={false}>
<motion.span
key={ch + i}
className="inline-block"
initial={{ y: "100%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
exit={{ y: "-100%", opacity: 0 }}
transition={{ type: "spring", stiffness: 420, damping: 30 }}
>
{ch}
</motion.span>
</AnimatePresence>
</span>
))}
</span>
);
}
export default function Like() {
const [liked, setLiked] = useState(false);
const [pop, setPop] = useState(0);
const count = 1299 + (liked ? 1 : 0);
return (
<Surface>
<motion.button
type="button"
whileTap={{ scale: 0.92 }}
whileHover={{ scale: 1.03 }}
onClick={() => {
setLiked((l) => !l);
setPop((p) => p + 1);
}}
className="relative flex items-center gap-[0.55em] rounded-full bg-white px-[1.1em] py-[0.65em] text-[1.15em] shadow-[0_8px_24px_-14px_rgba(0,0,0,0.35),0_0_0_1px_rgba(0,0,0,0.04)]"
>
<span className="relative grid h-[1.2em] w-[1.2em] place-items-center">
<motion.svg
key={pop}
viewBox="0 0 24 24"
className="h-full w-full"
initial={pop ? { scale: liked ? 0.4 : 1.2 } : false}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 500, damping: 12 }}
>
<path
d="M12 20.5s-7.5-4.6-9.3-9.2C1.4 8 3.4 4.5 6.9 4.5c2 0 3.7 1.1 5.1 3 1.4-1.9 3.1-3 5.1-3 3.5 0 5.5 3.5 4.2 6.8-1.8 4.6-9.3 9.2-9.3 9.2Z"
fill={liked ? "#e5484d" : "none"}
stroke={liked ? "#e5484d" : "#151515"}
strokeWidth={1.8}
strokeLinejoin="round"
/>
</motion.svg>
<AnimatePresence>
{liked && pop > 0 && (
<motion.span key={`ring-${pop}`} className="pointer-events-none absolute inset-0">
<motion.span
className="absolute inset-[-40%] rounded-full border-2 border-[#e5484d]"
initial={{ scale: 0.3, opacity: 0.9 }}
animate={{ scale: 1.3, opacity: 0 }}
transition={{ duration: 0.5 }}
/>
{SPARKS.map((a, i) => (
<motion.span
key={i}
className="absolute left-1/2 top-1/2 h-[0.22em] w-[0.22em] rounded-full"
style={{ background: i % 2 ? "#2B46B8" : "#e5484d" }}
initial={{ x: "-50%", y: "-50%", scale: 1, opacity: 1 }}
animate={{ x: `calc(-50% + ${Math.cos(a) * 1.3}em)`, y: `calc(-50% + ${Math.sin(a) * 1.3}em)`, scale: 0, opacity: 0 }}
transition={{ duration: 0.55, ease: "easeOut" }}
/>
))}
</motion.span>
)}
</AnimatePresence>
</span>
<Count value={count} />
</motion.button>
</Surface>
);
}
Prompt
Build "Like", a button animation. What it does: Tap the heart to like; it pops, sparks and rolls the count. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using motion. Technique: an HTML canvas, the Motion library, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://bencho.dev (Interaction after bencho.dev, Original code — interaction after bencho.dev).