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