Spring-Based Mouse Interactions for Decorative Effects: A Complete Implementation Guide
Use spring physics with useSpring to interpolate mouse position rather than mapping directly, creating natural momentum that feels alive while staying GPU-optimized and interruptible.
The emilkowalski/skills repository codifies battle-tested animation patterns for modern UI engineering. At its heart lies a strict Animation Standards document in skills/review-animations/STANDARDS.md that governs every motion decision—including how to implement spring-based mouse interactions for decorative effects without sacrificing performance or accessibility.
Why Springs Beat Direct Mapping for Mouse Tracking
Directly setting an element's position to e.clientX and e.clientY produces "artificial" motion that feels robotic. The standards (lines 62-71) prescribe spring interpolation instead:
- Natural momentum: Springs decelerate organically rather than stopping instantly
- Interruptibility: Mid-motion updates don't cause stutter or restart-from-zero artifacts
- Perceived quality: The trailing behavior signals "premium" UI craftsmanship
The repository recommends two spring configurations:
// Apple-style spring (recommended for most decorative effects)
{ type: "spring", duration: 0.5, bounce: 0.2 }
// Traditional physics spring (fine-grained control)
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }
Core Implementation Pattern
The standards dictate a four-step workflow for any mouse-tracking component:
- Create springs for each animated axis using your library of choice
- Update spring targets on
pointermoveevents—never the DOM directly - Apply spring values to
transformproperties only (translate3d,scale,rotate) - Guard performance: Animate exclusively
transformandopacity
This pattern appears throughout skills/review-animations/SKILL.md as the canonical approach for decorative motion that responds to cursor position.
Framer Motion Implementation
Follow the repository's standards with this minimal, production-ready mouse follower in skills/review-animations/STANDARDS.md style:
import { useEffect } from "react";
import { motion, useSpring } from "framer-motion";
export function SpringMouseFollower() {
const x = useSpring(0, { type: "spring", bounce: 0.2 });
const y = useSpring(0, { type: "spring", bounce: 0.2 });
useEffect(() => {
const handleMove = (e) => {
// Interpolate via spring—never set style directly
x.set(e.clientX - 20); // center 40px element
y.set(e.clientY - 20);
};
window.addEventListener("pointermove", handleMove);
return () => window.removeEventListener("pointermove", handleMove);
}, [x, y]);
return (
<motion.div
style={{
position: "fixed",
left: 0,
top: 0,
width: 40,
height: 40,
borderRadius: "50%",
background: "rgba(0,120,255,0.4)",
pointerEvents: "none",
// GPU-only transform application
translateX: x,
translateY: y,
}}
/>
);
}
Compliance Check
This implementation satisfies every rule from STANDARDS.md:
- Easing: Uses
bounce: 0.2for anease-out-style feel - GPU-only: Animates
translateXandtranslateYexclusively - Interruptibility: Spring updates mid-motion without restart artifacts
- Physicality: Starts from rest, scales naturally via spring physics
React Spring Alternative
For teams using react-spring, the same pattern applies per skills/pick-ui-library/SKILL.md recommendations:
import { useSpring, animated } from "@react-spring/web";
import { useEffect } from "react";
export function ReactSpringFollower() {
const [props, api] = useSpring(() => ({
x: 0,
y: 0,
config: { mass: 1, tension: 170, friction: 26 }, // ~bounce 0.2
}));
useEffect(() => {
const handleMove = (e) => {
api.start({ x: e.clientX, y: e.clientY });
};
window.addEventListener("pointermove", handleMove);
return () => window.removeEventListener("pointermove", handleMove);
}, [api]);
return (
<animated.div
style={{
position: "fixed",
transform: props.x.to((x, y) => `translate3d(${x}px, ${props.y.get()}px, 0)`),
// ...styling
}}
/>
);
}
Accessibility Requirements
The standards enforce two non-negotiable guards for spring-based mouse interactions. Per STANDARDS.md, both must be present:
Reduced Motion Support
<motion.div
style={{
// ...base styles
}}
// Framer Motion shorthand
{...(prefersReducedMotion && { initial: false, animate: false })}
/>
Or via CSS:
@media (prefers-reduced-motion: reduce) {
.spring-follower {
display: none; /* or static positioning */
}
}
Hover Capability Detection
Decorative mouse effects should not activate on touch devices:
@media (hover: hover) and (pointer: fine) {
.spring-follower {
display: block;
}
}
Performance Optimization
skills/review-animations/STANDARDS.md lists strict performance rules for spring-based interactions:
| Rule | Implementation |
|---|---|
| GPU-only properties | Use translate3d, scale, rotate, opacity exclusively |
| Avoid layout thrash | Never animate width, height, top, left |
| Debounce expensive work | Keep pointermove handlers under 1ms |
Use will-change sparingly |
Add before animation, remove after |
Advanced: Velocity-Based Dismissal
For interactive spring-based components (swipe-to-delete, dismissible toasts), reference the pattern in skills/improve-animations/SKILL.md:
const bind = useGesture({
onDrag: ({ down, movement: [mx], velocity }) => {
// Momentum dismissal threshold from standards
if (!down && Math.abs(mx) / velocity > 0.11) {
api.start({ x: mx > 0 ? 500 : -500 }); // dismiss
} else {
api.start({ x: down ? mx : 0, immediate: down });
}
},
});
This applies the asymmetric timing principle: deliberate drags animate slowly, system responses (dismissal) execute fast.
Summary
- Spring interpolation creates natural, momentum-based mouse tracking that direct mapping cannot achieve
- The
emilkowalski/skillsrepository mandates specific spring configurations (type: "spring",bounce: 0.2) and physics parameters (mass,stiffness,damping) - Update spring targets on
pointermove, apply values to transform only, and never touch the DOM directly - Every implementation must include reduced-motion and hover-capability guards per
STANDARDS.md - Keep animations interruptible, GPU-optimized, and under 300ms where possible
Frequently Asked Questions
How do I choose between Framer Motion and react-spring for mouse interactions?
Both libraries satisfy the repository's standards. Framer Motion provides a more streamlined API with useSpring that handles interruptibility automatically, recommended in skills/pick-ui-library/SKILL.md. react-spring offers finer physics control through explicit mass/stiffness/damping parameters. Choose based on your team's existing dependencies and need for customization.
Why can't I just use CSS transitions for mouse tracking?
CSS transitions cannot interpolate based on a moving target value. When the cursor moves, a transition would restart from the current visual position, creating stutter. Springs maintain velocity state and smoothly adjust trajectory, which is why skills/review-animations/STANDARDS.md explicitly recommends them for decorative mouse effects.
What bounce value should I use for professional UIs versus playful ones?
Use bounce: 0.2 (Apple-style) for professional interfaces per the standards. Higher values (0.4-0.6) feel bouncier and suit playful brands, but never exceed 300ms total duration regardless of personality. The STANDARDS.md cohesion rule requires matching motion character to brand identity consistently.
How do I test reduced-motion compliance?
Enable "Reduce motion" in your operating system settings (System Preferences → Accessibility on macOS, Settings → Ease of Access on Windows). Your spring-based component should either disappear, freeze in place, or reduce to opacity-only changes. The repository's review skill in skills/review-animations/SKILL.md flags missing @media (prefers-reduced-motion: reduce) guards as blockers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →