Baktiyar Ablaikhan

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).