Senden mit Papierflieger
Der Flieger holt Schwung, hebt vom schrumpfenden Button ab, wird zum gezeichneten Häkchen und gleitet dann zurück.
Einstellungen
Code
import { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { Send } from "lucide-react";
type SendState = "idle" | "flying" | "sent";
interface PaperPlaneSendProps {
label?: string;
sentLabel?: string;
color?: string;
}
const sendSpring = { type: "spring" as const, stiffness: 520, damping: 34 };
const sendText = {
initial: { opacity: 0, y: 10, filter: "blur(4px)" },
animate: { opacity: 1, y: 0, filter: "blur(0px)" },
exit: { opacity: 0, y: -10, filter: "blur(4px)" },
};
export default function PaperPlaneSend({
label = "Senden",
sentLabel = "Gesendet",
color = "#0a84ff",
}: PaperPlaneSendProps) {
const [state, setState] = useState<SendState>("idle");
const [trip, setTrip] = useState(0);
const timers = useRef<ReturnType<typeof setTimeout>[]>([]);
const reduce = useReducedMotion();
useEffect(() => () => timers.current.forEach(clearTimeout), []);
const send = () => {
if (state !== "idle") return;
setState("flying");
timers.current = [
setTimeout(() => setState("sent"), reduce ? 0 : 560),
setTimeout(() => {
setState("idle");
setTrip((t) => t + 1);
}, 2400),
];
};
return (
<motion.button
type="button"
layout
onClick={send}
aria-disabled={state !== "idle"}
whileTap={{ scale: state === "idle" ? 0.95 : 1 }}
transition={sendSpring}
className="relative flex h-12 items-center justify-center gap-2 px-6 text-[15px] font-medium text-white outline-none focus-visible:ring-2 focus-visible:ring-black/25 focus-visible:ring-offset-2 dark:focus-visible:ring-white/40 dark:focus-visible:ring-offset-black"
style={{
backgroundColor: color,
borderRadius: 999,
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.28), inset 0 -1px 0 rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.14), 0 10px 24px -10px " + color,
}}
>
<motion.span layout="position" className="relative grid h-[18px] w-[18px] place-items-center">
<AnimatePresence initial={false}>
{state === "sent" ? (
<motion.svg
key="check"
viewBox="0 0 24 24"
className="absolute h-[18px] w-[18px]"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
strokeLinecap="round"
strokeLinejoin="round"
exit={{ opacity: 0, scale: 0.6 }}
transition={{ duration: 0.15 }}
>
<motion.path
d="M5 12.5l4.5 4.5L19 7.5"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.32, ease: [0.65, 0, 0.35, 1] }}
/>
</motion.svg>
) : (
<motion.span
key={"plane-" + trip}
className="absolute grid place-items-center"
initial={trip === 0 ? false : { x: -26, y: 12, rotate: 18, opacity: 0 }}
animate={
state === "flying" && !reduce
? {
x: [0, -4, 110],
y: [0, 3, -80],
rotate: [0, 14, -10],
scale: [1, 0.92, 0.7],
opacity: [1, 1, 0],
filter: ["blur(0px)", "blur(0px)", "blur(1.5px)"],
}
: { x: 0, y: 0, rotate: 0, scale: 1, opacity: 1 }
}
exit={{ opacity: 0 }}
transition={
state === "flying"
? { duration: 0.56, times: [0, 0.28, 1], ease: [0.55, 0, 0.8, 0.2] }
: { type: "spring", stiffness: 260, damping: 22 }
}
>
<Send className="h-[18px] w-[18px]" strokeWidth={1.75} />
</motion.span>
)}
</AnimatePresence>
</motion.span>
<AnimatePresence mode="popLayout" initial={false}>
{state !== "flying" && (
<motion.span
key={state}
layout="position"
className="whitespace-nowrap leading-5"
initial={sendText.initial}
animate={sendText.animate}
exit={sendText.exit}
transition={sendSpring}
>
{state === "sent" ? sentLabel : label}
</motion.span>
)}
</AnimatePresence>
<span className="sr-only" aria-live="polite">
{state === "sent" ? sentLabel : ""}
</span>
</motion.button>
);
}
Prompt für KI
Füg ihn in ChatGPT, Claude oder Cursor ein und der Baustein wird mit diesen Einstellungen an dein Projekt angepasst, auch ohne React.
Mehr aus Mikrointeraktionen
Wofür du ihn nutzen kannst
Eine Animation am Senden-Button zeigt, dass die Nachricht raus ist, und das zählt mehr als gedacht: im Kontaktformular, bei der Newsletter-Anmeldung, am Senden-Button eines Chats. Hier holt der Papierflieger Schwung, fliegt vom Button weg, und neben „Gesendet“ zeichnet sich ein Häkchen. Dann gleitet er zurück und der Button ist wieder bereit.
Starte den Flug erst, wenn dein Formular wirklich verschickt wurde, nicht beim Klick, sonst versprichst du etwas, das schiefgehen kann. Der ganze Ablauf dauert gut zwei Sekunden. Halte beide Texte kurz, damit der Button nicht stark in der Breite springt.