| name | ux-animation-motion |
| description | Animation patterns using Anime.js v4 for UI feedback, transitions, and celebrations. Use when implementing hover effects, transitions, loading animations, or gamification feedback. Includes reduced motion handling. (project) |
| allowed-tools | Read, Write, Edit, Glob, Grep |
UX Animation & Motion Skill
Animation system using Anime.js v4 for responsive, accessible UI motion. This skill covers animation patterns, timing, and reduced motion support.
Related Skills
animejs-v4: Complete Anime.js 4.0 API referencejs-micro-utilities: Array utilities like.at(-1)for accessing last elementux-iconography: Icon animation patternsux-accessibility: Reduced motion requirements
Animation Import
import { animate } from 'animejs';
Note: Project uses import maps to resolve animejs to local node_modules.
Animation Utilities
The project provides reusable animations in js/utils/animations.js:
import {
shake,
pressEffect,
successBounce,
glow,
DURATION,
EASE
} from '../../utils/animations.js';
Duration Constants
const DURATION = {
instant: 100, // Micro-interactions
quick: 200, // Button press, toggles
normal: 300, // Standard transitions
slow: 500, // Page transitions
celebration: 600 // Success animations
};
Easing Functions
const EASE = {
snap: 'easeOutQuad', // Quick, snappy feel
smooth: 'easeInOutQuad', // Gentle transitions
bounceOut: 'easeOutBack' // Playful, overshooting
};
Animation Patterns
Press Effect (Tactile Feedback)
For button clicks and taps:
pressEffect(element);
// or
animate(element, {
scale: [1, 0.95, 1],
duration: DURATION.instant,
ease: EASE.snap
});
Success Bounce
For completed actions:
successBounce(element);
// or
animate(element, {
scale: [1, 1.1, 1],
duration: DURATION.normal,
ease: EASE.bounceOut
});
Shake (Error/Invalid)
For validation failures:
shake(element, { intensity: 6 });
// or
animate(element, {
translateX: [-6, 6, -6, 6, -3, 3, 0],
duration: DURATION.normal,
ease: EASE.snap
});
Glow Effect
For achievements or highlights:
glow(element, {
color: 'rgba(74, 222, 128, 0.6)',
intensity: 15
});
// Uses box-shadow animation
Pulse (Attention)
For elements requiring attention:
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.attention {
animation: pulse 1.5s ease-in-out infinite;
}
Transition Patterns
Fade In
animate(element, {
opacity: [0, 1],
duration: DURATION.normal,
ease: EASE.smooth
});
Slide In
animate(element, {
translateY: [20, 0],
opacity: [0, 1],
duration: DURATION.normal,
ease: EASE.snap
});
Scale In
animate(element, {
scale: [0.9, 1],
opacity: [0, 1],
duration: DURATION.quick,
ease: EASE.bounceOut
});
Staggered List
animate('.list-item', {
translateY: [20, 0],
opacity: [0, 1],
delay: (el, i) => i * 50,
duration: DURATION.normal,
ease: EASE.snap
});
State Change Animations
Attribute Change Response
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'completed' && newVal !== null) {
this.#animateComplete();
}
}
#animateComplete() {
animate(this, {
scale: [1, 2, 1],
duration: DURATION.normal,
ease: EASE.bounceOut
});
}
Phase Transition
#animatePhaseChange() {
animate(this.#container, {
opacity: [1, 0, 1],
duration: DURATION.slow,
ease: EASE.smooth
});
}
Game Feedback Animations
Word Completion
#celebrateWord() {
successBounce(this.#wordElement);
glow(this.#wordElement, { color: 'rgba(74, 222, 128, 0.6)' });
}
Score Update
#animateScore() {
animate(this.#scoreElement, {
scale: [1, 1.2, 1],
duration: DURATION.quick,
ease: EASE.bounceOut
});
}
Achievement Unlock
#celebrateAchievement() {
animate(this.#badge, {
scale: [0, 1.2, 1],
rotate: [0, -10, 10, 0],
duration: DURATION.celebration,
ease: EASE.bounceOut
});
}
Reduced Motion Support
Check Preference
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (!prefersReducedMotion) {
animate(element, { scale: [1, 1.1, 1] });
} else {
// Instant state change instead
element.classList.add('completed');
}
CSS Fallback
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Safe Animation Wrapper
function safeAnimate(element, props) {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
// Apply final state immediately
if (props.scale) element.style.transform = `scale(${props.scale.at(-1)})`;
if (props.opacity) element.style.opacity = props.opacity.at(-1);
return;
}
return animate(element, props);
}
CSS Transitions
For simple state changes, prefer CSS:
.button {
transition:
background-color 0.15s ease,
transform 0.1s ease,
opacity 0.15s ease;
}
.button:hover {
background: var(--color-hover-overlay);
}
.button:active {
transform: scale(0.98);
}
Loading Animations
Spinner
.spinner {
width: 24px;
height: 24px;
border: 2px solid var(--theme-outline);
border-top-color: var(--theme-primary);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { rotate: 360deg; }
}
Dots
.loading-dots span {
animation: bounce 1s ease-in-out infinite;
}
.loading-dots span:nth-child(2) { animation-delay: 0.1s; }
.loading-dots span:nth-child(3) { animation-delay: 0.2s; }
@keyframes bounce {
0%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-6px); }
}
Performance Guidelines
Do
- Use
transformandopacityfor smooth 60fps - Keep animations under 300ms for interactions
- Use
will-changesparingly for complex animations - Cancel animations when element unmounts
Don't
- Animate
width,height,margin,padding - Use long durations for frequent interactions
- Chain many sequential animations
- Animate elements off-screen
Cleanup
#animation = null;
disconnectedCallback() {
if (this.#animation) {
this.#animation.pause();
this.#animation = null;
}
}
Animation Timing Reference
| Action | Duration | Easing |
|---|---|---|
| Button press | 100ms | easeOutQuad |
| Toggle switch | 150ms | easeOutQuad |
| Dropdown open | 200ms | easeOutQuad |
| Modal appear | 200-300ms | easeOutBack |
| Page transition | 300-500ms | easeInOutQuad |
| Success celebration | 400-600ms | easeOutBack |