withDecay
The withDecay modifier simulates natural momentum with friction. It's perfect for scenarios where you want values to gradually slow down, like a thrown ball losing speed or a scroll that decelerates naturally.
Why Use withDecay?
Decay animations feel natural because they:
- Simulate physics - Realistic momentum and friction
- Feel responsive - Natural slowdown after gestures
- Work great with gestures - Perfect for drag, scroll, and wheel interactions
- No fixed destination - Values slow down naturally rather than reaching a target
Basic Syntax
withDecay(velocity, options?)
Parameters
- velocity (required): The initial velocity. Higher values = faster initial movement.
- options (optional): Configuration object for decay behavior.
Simple Example
The simplest decay animation:
import { useValue, withDecay, animate } from 'react-ui-animate';
function MyComponent() {
const [x, setX] = useValue(0);
return (
<>
<button onClick={() => setX(withDecay(0.5))}>
Start Decay
</button>
<animate.div
style={{
translateX: x,
width: 100,
height: 100,
background: 'teal',
}}
/>
</>
);
}
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
onStart | () => void | - | Called when animation starts |
onChange | (value) => void | - | Called on every frame with current value |
onComplete | () => void | - | Called when animation finishes |
Real-World Examples
Example 1: Drag with Momentum
Perfect for draggable elements that continue moving after release:
import { useValue, useDrag, withDecay, animate } from 'react-ui-animate';
function DraggableWithMomentum() {
const ref = useRef(null);
const [x, setX] = useValue(0);
const [velocity, setVelocity] = useValue(0);
useDrag(ref, ({ down, movement, velocity: dragVelocity }) => {
if (down) {
setX(movement.x);
setVelocity(dragVelocity.x);
} else {
// Apply decay with the drag velocity
setX(withDecay(velocity * 0.1));
}
});
return (
<animate.div
ref={ref}
style={{
translateX: x,
width: 100,
height: 100,
background: 'teal',
cursor: 'grab',
}}
/>
);
}
Example 2: Scroll Momentum
Simulate natural scroll deceleration:
function ScrollableContent() {
const [scrollY, setScrollY] = useValue(0);
const [velocity, setVelocity] = useValue(0);
useScroll(window, ({ scroll, velocity: scrollVelocity }) => {
setScrollY(scroll.y);
setVelocity(scrollVelocity.y);
});
const applyMomentum = () => {
// Continue scrolling with decay
setScrollY(withDecay(velocity * 0.05));
};
return (
<div>
<button onClick={applyMomentum}>Apply Momentum</button>
<animate.div
style={{
translateY: -scrollY,
}}
>
{/* Content */}
</animate.div>
</div>
);
}
Example 3: Flick Gesture
Create a flickable card:
function FlickableCard() {
const ref = useRef(null);
const [x, setX] = useValue(0);
const [velocity, setVelocity] = useValue(0);
useDrag(ref, ({ down, movement, velocity: dragVelocity }) => {
if (down) {
setX(movement.x);
setVelocity(dragVelocity.x);
} else {
// Flick with decay based on drag velocity
if (Math.abs(velocity) > 0.5) {
setX(withDecay(velocity * 0.15));
} else {
// Snap back if velocity is too low
setX(withSpring(0));
}
}
});
return (
<animate.div
ref={ref}
style={{
translateX: x,
width: 200,
height: 200,
background: 'teal',
borderRadius: 8,
cursor: 'grab',
}}
>
Flick me
</animate.div>
);
}
Understanding Velocity
Velocity determines how fast the animation starts:
- Low velocity (0.1-0.3) - Slow, gentle deceleration
- Medium velocity (0.5-1.0) - Moderate speed, natural feel
- High velocity (1.5+) - Fast start, longer deceleration
Using with Gestures
withDecay works perfectly with gesture hooks:
useDrag(ref, ({ down, movement, velocity }) => {
if (down) {
setX(movement.x);
} else {
// Use drag velocity for natural momentum
setX(withDecay(velocity.x * 0.1));
}
});
Using Callbacks
Track decay progress:
function DecayAnimation() {
const [x, setX] = useValue(0);
const startDecay = () => {
setX(withDecay(0.5, {
onStart: () => {
console.log('Decay started');
},
onChange: (value) => {
console.log('Current position:', value);
},
onComplete: () => {
console.log('Decay complete - stopped');
},
}));
};
return (
<>
<button onClick={startDecay}>Start Decay</button>
<animate.div
style={{
translateX: x,
width: 100,
height: 100,
background: 'teal',
}}
/>
</>
);
}
Common Patterns
Pattern 1: Drag and Release
useDrag(ref, ({ down, movement, velocity }) => {
if (down) {
setX(movement.x);
} else {
// Continue with momentum
setX(withDecay(velocity.x * 0.1));
}
});
Pattern 2: Scroll Momentum
useScroll(container, ({ velocity }) => {
// Apply decay when scroll stops
setScrollPosition(withDecay(velocity.y * 0.05));
});
Pattern 3: Flick to Dismiss
useDrag(ref, ({ down, movement, velocity }) => {
if (down) {
setX(movement.x);
} else {
// Flick with high velocity = dismiss
if (Math.abs(velocity.x) > 1.0) {
setX(withDecay(velocity.x * 0.2));
// Dismiss when far enough
if (Math.abs(x) > 200) {
onDismiss();
}
} else {
// Snap back
setX(withSpring(0));
}
}
});
Best Practices
✅ Do
- Use
withDecaywith gesture hooks for natural momentum - Adjust velocity multiplier based on your needs (0.05-0.2 is common)
- Combine with
withSpringfor snap-back behavior - Use for scroll, drag, and wheel interactions
❌ Don't
- Don't use
withDecayfor fixed destination animations (usewithSpringorwithTiming) - Don't set velocity too high - can cause janky animations
- Don't forget to handle edge cases (snap back, boundaries)
- Don't use for simple show/hide animations
Performance Tips
- Decay is optimized - Very performant for gesture-based animations
- Use transforms -
translateX,translateYare GPU-accelerated - Combine with gestures - Works best with
useDrag,useScroll,useWheel - Set boundaries - Prevent values from going too far
Troubleshooting
Animation doesn't slow down:
- Check that velocity is being set correctly
- Verify you're using
withDecaycorrectly
Animation stops too quickly:
- Increase velocity multiplier
- Check if boundaries are cutting off the animation
Animation feels janky:
- Make sure you're using transforms, not layout properties
- Reduce velocity if too high
Next Steps
- Learn about useDrag for drag interactions
- Explore useScroll for scroll-based animations
- Check out withSequence to combine decay with other animations