Schwebende Emoji-Reaktionen
Eine Reaktionsleiste im Dock-Stil: Emojis wachsen, wenn du dich näherst, und jeder Tipp lässt ein paar nach oben schweben.
Einstellungen
Code
import { useRef, useState } from "react";
import { AnimatePresence, motion, useMotionValue, useReducedMotion, useSpring, useTransform } from "framer-motion";
import type { MotionValue } from "framer-motion";
interface Floater {
id: number;
emoji: string;
x: number;
drift: number;
rot: number;
size: number;
rise: number;
delay: number;
}
interface DockEmojiProps {
emoji: string;
mouseX: MotionValue<number>;
onReact: (emoji: string, clientX: number) => void;
}
function DockEmoji({ emoji, mouseX, onReact }: DockEmojiProps) {
const ref = useRef<HTMLButtonElement>(null);
const distance = useTransform(mouseX, (x) => {
const r = ref.current?.getBoundingClientRect();
return r ? x - (r.left + r.width / 2) : Infinity;
});
const size = useSpring(useTransform(distance, [-96, 0, 96], [40, 56, 40]), {
stiffness: 420,
damping: 30,
mass: 0.4,
});
const fontSize = useTransform(size, (s) => s * 0.56);
return (
<motion.button
ref={ref}
type="button"
aria-label={"Reagieren mit " + emoji}
onClick={(e) => onReact(emoji, e.currentTarget.getBoundingClientRect().left + e.currentTarget.offsetWidth / 2)}
whileTap={{ scale: 0.8 }}
transition={{ type: "spring", stiffness: 600, damping: 20 }}
style={{ width: size, height: size, fontSize }}
className="grid shrink-0 place-items-center rounded-full leading-none outline-none focus-visible:ring-2 focus-visible:ring-black/20 dark:focus-visible:ring-white/30"
>
{emoji}
</motion.button>
);
}
interface LiveReactionsProps {
emojis?: string;
burst?: number;
}
export default function LiveReactions({
emojis = "👍 ❤️ 😂 😮 🔥",
burst = 3,
}: LiveReactionsProps) {
const list = emojis.split(/\s+/).filter(Boolean).slice(0, 6);
const [floaters, setFloaters] = useState<Floater[]>([]);
const stage = useRef<HTMLDivElement>(null);
const nextId = useRef(0);
const mouseX = useMotionValue(Infinity);
const reduce = useReducedMotion();
const react = (emoji: string, clientX: number) => {
const left = stage.current?.getBoundingClientRect().left ?? 0;
const items = Array.from({ length: burst }, (_, i) => ({
id: nextId.current++,
emoji,
x: clientX - left + (Math.random() - 0.5) * 14,
drift: (Math.random() - 0.5) * 56,
rot: (Math.random() - 0.5) * 40,
size: 22 + Math.random() * 12,
rise: 120 + Math.random() * 60,
delay: i * 0.08,
}));
setFloaters((f) => [...f, ...items].slice(-36));
};
return (
<div ref={stage} className="relative flex h-60 w-full max-w-xs items-end justify-center">
<div className="pointer-events-none absolute inset-0" aria-hidden="true">
<AnimatePresence>
{floaters.map((p) => (
<motion.span
key={p.id}
className="absolute bottom-14 select-none leading-none"
style={{ left: p.x, fontSize: p.size, marginLeft: -p.size / 2 }}
initial={{ y: 0, opacity: 0, scale: 0.4 }}
animate={{
y: reduce ? -40 : -p.rise,
x: reduce ? 0 : [0, p.drift * 0.5, -p.drift * 0.3, p.drift],
rotate: reduce ? 0 : [0, p.rot, -p.rot * 0.5, p.rot * 0.3],
opacity: [0, 1, 1, 0],
scale: [0.4, 1.1, 1, 0.9],
}}
transition={{ duration: 1.8, delay: p.delay, ease: [0.22, 1, 0.36, 1] }}
onAnimationComplete={() => setFloaters((f) => f.filter((q) => q.id !== p.id))}
>
{p.emoji}
</motion.span>
))}
</AnimatePresence>
</div>
<div
onPointerMove={(e) => mouseX.set(e.clientX)}
onPointerDown={(e) => mouseX.set(e.clientX)}
onPointerLeave={() => mouseX.set(Infinity)}
className="relative flex items-end gap-1 rounded-full bg-white/85 p-1.5 shadow-[0_0_0_1px_rgba(0,0,0,0.05),0_1px_2px_rgba(0,0,0,0.05),0_12px_32px_-8px_rgba(0,0,0,0.16)] backdrop-blur-xl dark:bg-[#2c2c2e]/85 dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08),0_12px_32px_-8px_rgba(0,0,0,0.6)]"
>
{list.map((emoji, i) => (
<DockEmoji key={emoji + i} emoji={emoji} mouseX={mouseX} onReact={react} />
))}
</div>
</div>
);
}
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
Schwebende Emoji-Reaktionen bringen Leben in einen Live-Moment: einen Livestream, ein Webinar, ein Online-Event oder das Ende eines Blogartikels. Die Leiste vergrößert sich wie das Dock von macOS, wenn der Cursor darüberfährt, und jeder Tipp lässt ein bis sechs Emojis mit Drift und Drehung aufsteigen. Ein Schwall Herzen wirkt dann wie echtes Publikum.
Die Lupe braucht eine Maus, am Handy tippt man einfach und die Emojis steigen trotzdem auf. Bleib bei fünf oder sechs Reaktionen. Emojis sehen auf jedem System etwas anders aus, prüf dein Set also auf einem iPhone und einem Android-Gerät.