Wackelnde Benachrichtigungsglocke
Tipp auf die Glocke: Sie schwingt an einer Feder, der Klöppel hängt nach, zwei Schallwellen erscheinen und der Zähler rollt hoch.
Einstellungen
Code
import { useEffect, useState } from "react";
import {
AnimatePresence,
animate,
motion,
useMotionValue,
useReducedMotion,
useSpring,
useTransform,
} from "framer-motion";
const BELL_BODY =
"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326";
const BELL_CLAPPER = "M10.268 21a2 2 0 0 0 3.464 0";
interface BellShakeProps {
color?: string;
start?: number;
}
export default function BellShake({ color = "#ff453a", start = 3 }: BellShakeProps) {
const [count, setCount] = useState(start);
const [rings, setRings] = useState(0);
const reduce = useReducedMotion();
const rot = useMotionValue(0);
const clapX = useSpring(
useTransform(rot, (r) => r * -0.12),
{ stiffness: 260, damping: 7 },
);
const bump = useMotionValue(1);
useEffect(() => {
setCount(start);
}, [start]);
const ring = () => {
setCount((c) => c + 1);
setRings((r) => r + 1);
if (reduce) return;
animate(rot, 0, { type: "spring", velocity: -620, stiffness: 520, damping: 6 });
animate(bump, 1, { type: "spring", velocity: 5, stiffness: 600, damping: 12 });
};
return (
<motion.button
type="button"
onClick={ring}
aria-label={"Mitteilungen: " + count + " ungelesen"}
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.92 }}
transition={{ type: "spring", stiffness: 500, damping: 22 }}
className="relative grid h-14 w-14 place-items-center rounded-full bg-white text-[#1d1d1f] shadow-[0_0_0_1px_rgba(0,0,0,0.05),0_1px_2px_rgba(0,0,0,0.06),0_10px_28px_-6px_rgba(0,0,0,0.14)] outline-none focus-visible:ring-2 focus-visible:ring-black/20 dark:bg-[#2c2c2e] dark:text-[#f5f5f7] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08),0_10px_28px_-6px_rgba(0,0,0,0.6)] dark:focus-visible:ring-white/30"
>
<span className="relative h-6 w-6">
<motion.svg
viewBox="0 0 24 24"
className="absolute inset-0 h-6 w-6"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
style={{ rotate: rot, originY: 0.08 }}
aria-hidden="true"
>
<path d={BELL_BODY} />
<motion.path d={BELL_CLAPPER} style={{ x: clapX }} />
</motion.svg>
{rings > 0 && !reduce && (
<svg
key={rings}
viewBox="0 0 24 24"
className="pointer-events-none absolute inset-0 h-6 w-6 overflow-visible"
fill="none"
stroke={color}
strokeWidth={1.5}
strokeLinecap="round"
aria-hidden="true"
>
<motion.path
d="M2.6 4.2C1.4 5.6 0.8 7.2 0.8 9"
initial={{ opacity: 0, x: 1 }}
animate={{ opacity: [0, 1, 0], x: -2.5 }}
transition={{ duration: 0.55, ease: "easeOut" }}
/>
<motion.path
d="M21.4 4.2C22.6 5.6 23.2 7.2 23.2 9"
initial={{ opacity: 0, x: -1 }}
animate={{ opacity: [0, 1, 0], x: 2.5 }}
transition={{ duration: 0.55, ease: "easeOut" }}
/>
</svg>
)}
</span>
<AnimatePresence>
{count > 0 && (
<motion.span
className="absolute -right-0.5 -top-0.5"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
transition={{ type: "spring", stiffness: 500, damping: 20 }}
>
<motion.span
className="flex h-5 min-w-5 items-center justify-center overflow-hidden rounded-full px-1.5 text-[11px] font-semibold tabular-nums text-white ring-[2.5px] ring-[#fafafa] dark:ring-[#161617]"
style={{ backgroundColor: color, scale: bump }}
>
<AnimatePresence mode="popLayout" initial={false}>
<motion.span
key={count}
initial={{ y: 12, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -12, opacity: 0 }}
transition={{ type: "spring", stiffness: 600, damping: 30 }}
>
{count > 99 ? "99+" : count}
</motion.span>
</AnimatePresence>
</motion.span>
</motion.span>
)}
</AnimatePresence>
</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 animierte Benachrichtigungsglocke sorgt dafür, dass Neues auffällt, ohne ein lautes Banner. Sie gehört in die Kopfzeile eines Dashboards, einer Social App oder einer Plattform für Onlinekurse. Die Glocke schwingt mit echter Federphysik, der Klöppel folgt einen Tick später, an den Seiten erscheinen zwei kleine Wellen und das rote Badge rollt zur neuen Zahl.
In der Demo zählt jeder Tipp eins hoch, in deiner App sollte sie nur wackeln, wenn wirklich eine Benachrichtigung kommt. Niemals in Schleife. Das Badge endet bei 99+, und du kannst einstellen, mit wie vielen Ungelesenen es startet.