Baktiyar Ablaikhan

Image shimmer

Source
After bencho.dev ↗
Author
Interaction after bencho.dev
Licence
Original code — interaction after bencho.dev

Loading…

A skeleton shimmers until the image develops in; click to load again.

Code

"use client";

import { AnimatePresence, motion } from "motion/react";
import { RotateCw } from "lucide-react";
import { useEffect, useState } from "react";
import { Surface } from "../ui";

const ART =
  "radial-gradient(40% 50% at 30% 70%, #ff8a5b 0, transparent 70%), radial-gradient(50% 45% at 75% 35%, #8fb5ff 0, transparent 70%), radial-gradient(35% 35% at 55% 80%, #ffd36b 0, transparent 70%), linear-gradient(160deg, #2B46B8, #9a7bff)";

function Sheen() {
  return (
    <span className="absolute inset-0 overflow-hidden">
      <span className="absolute inset-0 animate-[bencho-skeleton_1.4s_ease-in-out_infinite] bg-[linear-gradient(100deg,transparent_20%,rgba(255,255,255,0.75)_50%,transparent_80%)]" />
    </span>
  );
}

export default function ImageShimmer() {
  const [round, setRound] = useState(0);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    const t = window.setTimeout(() => setLoaded(true), 1800);
    return () => window.clearTimeout(t);
  }, [round]);

  const reload = () => {
    setLoaded(false);
    setRound((r) => r + 1);
  };

  return (
    <Surface>
      <div className="flex flex-col items-center gap-[1em]">
        <motion.button
          type="button"
          onClick={reload}
          whileHover={{ y: -3 }}
          className="w-[15em] overflow-hidden rounded-[1.1em] bg-white p-[0.5em] text-left shadow-[0_12px_32px_-18px_rgba(0,0,0,0.35)]"
        >
          <div className="relative h-[9em] overflow-hidden rounded-[0.7em] bg-[#ecebe6]">
            <AnimatePresence>
              {loaded ? (
                <motion.div
                  key={`img-${round}`}
                  className="absolute inset-0"
                  style={{ background: ART }}
                  initial={{ opacity: 0, scale: 1.12, filter: "blur(14px) saturate(0.4)" }}
                  animate={{ opacity: 1, scale: 1, filter: "blur(0px) saturate(1)" }}
                  transition={{ duration: 0.9, ease: [0.2, 0.7, 0.2, 1] }}
                />
              ) : (
                <motion.div key="sk" className="absolute inset-0" exit={{ opacity: 0 }}>
                  <Sheen />
                </motion.div>
              )}
            </AnimatePresence>
          </div>
          <div className="px-[0.3em] pb-[0.3em] pt-[0.8em]">
            {loaded ? (
              <motion.div key={`t-${round}`} initial={{ opacity: 0, y: 4 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }}>
                <p className="text-[0.9em] font-medium">Afterglow</p>
                <p className="text-[0.72em] text-[#8a8984]">Generated · 1024 × 640</p>
              </motion.div>
            ) : (
              <div className="space-y-[0.45em] py-[0.15em]">
                <div className="relative h-[0.75em] w-[60%] overflow-hidden rounded-full bg-[#ecebe6]">
                  <Sheen />
                </div>
                <div className="relative h-[0.6em] w-[40%] overflow-hidden rounded-full bg-[#ecebe6]">
                  <Sheen />
                </div>
              </div>
            )}
          </div>
        </motion.button>
        <p className="flex items-center gap-[0.4em] font-mono text-[0.7em] text-[#8a8984]">
          <RotateCw className="h-[1em] w-[1em]" /> click the card to load again
        </p>
      </div>
    </Surface>
  );
}
Prompt

Build "Image shimmer", a loader animation. What it does: A skeleton shimmers until the image develops in; click to load again. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using lucide-react, 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).