썸네일 위를 빛이 훑고 지나가다 카드마다 차례로 걷히는 로딩
로딩 인터랙션카드 썸네일 자리에 옅은 빛줄기가 반복해서 훑고 지나가다가, 이미지가 준비되면 카드마다 110ms씩 늦게 0.5초에 걸쳐 걷힙니다. 다시 보기로 반복할 수 있습니다.
Claude Code · Claude Opus 5.5 · React (TSX)
결과 코드펼치기
import { useEffect, useRef, useState } from "react";
const ART = "https://nnuer.site/brand/landing";
const CARDS = [
{ img: "kind-block.webp", title: "유리 질감 가격표 섹션", bg: "from-[#ede9fe] to-[#f5f3ff]" },
{ img: "kind-interaction.webp", title: "눌리면 튀어 오르는 버튼", bg: "from-[#fce7f3] to-[#fdf2f8]" },
{ img: "kind-graphic.webp", title: "광택 있는 3D 폴더 아이콘", bg: "from-[#cffafe] to-[#ecfeff]" },
];
/** A sweeping shimmer covers each thumbnail while it loads, then fades away card by card */
function Card({ img, title, bg, index, run }: { img: string; title: string; bg: string; index: number; run: number }) {
const sheen = useRef<HTMLSpanElement>(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(false);
// Hold the placeholder a little so the effect can be seen even when the image is cached
const t = setTimeout(() => setLoaded(true), 900 + index * 110);
return () => clearTimeout(t);
}, [run, index]);
useEffect(() => {
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const sweep = sheen.current?.animate([{ backgroundPosition: "100% 0" }, { backgroundPosition: "-150% 0" }], {
duration: 1100,
iterations: Infinity,
easing: "linear",
});
return () => sweep?.cancel();
}, []);
return (
<div className="overflow-hidden rounded-[18px] border border-black/[0.07] bg-white shadow-[0_24px_50px_-34px_rgba(60,40,120,0.35)]">
<span className={`relative grid aspect-video place-items-center overflow-hidden bg-gradient-to-br ${bg}`}>
<img src={`${ART}/${img}`} alt="" className="h-3/5 w-auto object-contain" />
<span
ref={sheen}
aria-hidden
className={`absolute inset-0 bg-[linear-gradient(100deg,#eceaf3_30%,#f7f6fb_50%,#eceaf3_70%)] bg-[length:300%_100%] transition-opacity duration-500 ${
loaded ? "opacity-0" : "opacity-100"
}`}
/>
</span>
<span className="block truncate px-3.5 pb-3.5 pt-3 text-[14px] font-semibold text-[#111114]">{title}</span>
</div>
);
}
export default function ShimmerThumbnails() {
const [run, setRun] = useState(0);
return (
<section className="flex w-full flex-col items-center bg-[#fbfbfc] px-4 py-20">
<div className="grid w-full max-w-[900px] grid-cols-1 gap-4 sm:grid-cols-3">
{CARDS.map((c, i) => (
<Card key={c.title} {...c} index={i} run={run} />
))}
</div>
<button
type="button"
onClick={() => setRun((n) => n + 1)}
className="mt-10 rounded-full border border-black/10 bg-white px-4 py-2 text-[13px] font-semibold text-[#5c5c68] hover:text-[#111114] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed]"
>
다시 보기
</button>
</section>
);
}
적용 사이트 0
이 블록이 실제로 쓰인 사이트입니다.
아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.
재현 후기
v1 · 0개아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.