체크가 튀어나오며 그려지는 결제 완료 화면
기타 블록파란 원이 통통 튀며 나타나고 흰 체크가 그려진 뒤, 결제 정보와 두 버튼이 차례로 올라오는 토스 느낌의 완료 화면입니다.
Claude Code · claude-opus-5-5 · React (TSX)
결과 코드펼치기
import { useEffect, useRef, useState } from "react";
const EASE_POP = "cubic-bezier(0.2, 0.9, 0.3, 1.3)";
export default function PaymentComplete() {
const circle = useRef<SVGSVGElement>(null);
const ring = useRef<HTMLSpanElement>(null);
const check = useRef<SVGPathElement>(null);
const texts = useRef<HTMLDivElement>(null);
const [round, setRound] = useState(0);
useEffect(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const anims = [
circle.current?.animate(
[
{ opacity: 0, transform: "scale(0.4)" },
{ opacity: 1, transform: "scale(1.1)", offset: 0.55 },
{ transform: "scale(0.96)", offset: 0.75 },
{ transform: "scale(1)" },
],
{ duration: 560, easing: EASE_POP, fill: "both" },
),
ring.current?.animate(
[
{ opacity: 0.6, transform: "scale(1)" },
{ opacity: 0, transform: "scale(1.9)" },
],
{ duration: 900, delay: 450, easing: "ease-out", fill: "both" },
),
check.current?.animate([{ strokeDashoffset: 60 }, { strokeDashoffset: 0 }], {
duration: 380,
delay: 420,
easing: "ease-out",
fill: "both",
}),
...Array.from(texts.current?.children ?? []).map((el, i) =>
el.animate(
[
{ opacity: 0, transform: "translateY(10px)" },
{ opacity: 1, transform: "none" },
],
{ duration: 420, delay: 520 + i * 90, easing: "ease-out", fill: "both" },
),
),
];
return () => anims.forEach((a) => a?.cancel());
}, [round]);
return (
<section className="w-full bg-white px-5 py-20 text-zinc-900 sm:px-8">
<div role="status" className="mx-auto flex max-w-md flex-col items-center text-center">
<div className="relative h-24 w-24">
<span ref={ring} aria-hidden className="absolute inset-0 rounded-full bg-[#3182f6]/40 opacity-0" />
<svg ref={circle} aria-hidden viewBox="0 0 96 96" className="relative h-24 w-24">
<circle cx="48" cy="48" r="44" fill="#3182f6" />
<path
ref={check}
d="M30 49.5 42 61.5 66 35"
fill="none"
stroke="white"
strokeWidth="7"
strokeLinecap="round"
strokeLinejoin="round"
strokeDasharray="60"
/>
</svg>
</div>
<div ref={texts} className="w-full">
<h2 className="mt-7 text-2xl font-bold tracking-tight">결제를 마쳤어요</h2>
<p className="mt-2 text-[15px] text-zinc-500">주문하신 상품은 내일 도착 예정이에요.</p>
<dl className="mt-6 divide-y divide-zinc-100 rounded-2xl bg-zinc-50 px-5 text-left text-sm">
<div className="flex justify-between py-3">
<dt className="text-zinc-500">결제 금액</dt>
<dd className="font-semibold">38,900원</dd>
</div>
<div className="flex justify-between py-3">
<dt className="text-zinc-500">결제 수단</dt>
<dd>체크카드 (1234)</dd>
</div>
</dl>
<div className="mt-8 flex flex-col gap-2 sm:flex-row">
<a
href="#"
className="flex h-12 flex-1 items-center justify-center rounded-xl bg-[#3182f6] text-[15px] font-semibold text-white transition hover:bg-[#1b64da] active:scale-[0.98]"
>
주문 내역 보기
</a>
<a
href="#"
className="flex h-12 flex-1 items-center justify-center rounded-xl bg-zinc-100 text-[15px] font-semibold text-zinc-700 transition hover:bg-zinc-200 active:scale-[0.98]"
>
쇼핑 계속하기
</a>
</div>
<button
type="button"
onClick={() => setRound((r) => r + 1)}
className="mt-5 text-xs text-zinc-400 underline-offset-4 hover:text-zinc-600 hover:underline"
>
애니메이션 다시 보기
</button>
</div>
</div>
</section>
);
}
적용 사이트 0
이 블록이 실제로 쓰인 사이트입니다.
아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.
재현 후기
v1 · 0개아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.