# UX Animation Motion

> 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)

- Skill: `majiayu000/ux-animation-motion` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/ux-animation-motion`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/ux-animation-motion/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/ux-animation-motion

---


# 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 reference
- **`js-micro-utilities`**: Array utilities like `.at(-1)` for accessing last element
- **`ux-iconography`**: Icon animation patterns
- **`ux-accessibility`**: Reduced motion requirements

## Animation Import

```javascript
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`:

```javascript
import {
  shake,
  pressEffect,
  successBounce,
  glow,
  DURATION,
  EASE
} from '../../utils/animations.js';
```

### Duration Constants

```javascript
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

```javascript
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:

```javascript
pressEffect(element);
// or
animate(element, {
  scale: [1, 0.95, 1],
  duration: DURATION.instant,
  ease: EASE.snap
});
```

### Success Bounce

For completed actions:

```javascript
successBounce(element);
// or
animate(element, {
  scale: [1, 1.1, 1],
  duration: DURATION.normal,
  ease: EASE.bounceOut
});
```

### Shake (Error/Invalid)

For validation failures:

```javascript
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:

```javascript
glow(element, {
  color: 'rgba(74, 222, 128, 0.6)',
  intensity: 15
});
// Uses box-shadow animation
```

### Pulse (Attention)

For elements requiring attention:

```css
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.attention {
  animation: pulse 1.5s ease-in-out infinite;
}
```

## Transition Patterns

### Fade In

```javascript
animate(element, {
  opacity: [0, 1],
  duration: DURATION.normal,
  ease: EASE.smooth
});
```

### Slide In

```javascript
animate(element, {
  translateY: [20, 0],
  opacity: [0, 1],
  duration: DURATION.normal,
  ease: EASE.snap
});
```

### Scale In

```javascript
animate(element, {
  scale: [0.9, 1],
  opacity: [0, 1],
  duration: DURATION.quick,
  ease: EASE.bounceOut
});
```

### Staggered List

```javascript
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

```javascript
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

```javascript
#animatePhaseChange() {
  animate(this.#container, {
    opacity: [1, 0, 1],
    duration: DURATION.slow,
    ease: EASE.smooth
  });
}
```

## Game Feedback Animations

### Word Completion

```javascript
#celebrateWord() {
  successBounce(this.#wordElement);
  glow(this.#wordElement, { color: 'rgba(74, 222, 128, 0.6)' });
}
```

### Score Update

```javascript
#animateScore() {
  animate(this.#scoreElement, {
    scale: [1, 1.2, 1],
    duration: DURATION.quick,
    ease: EASE.bounceOut
  });
}
```

### Achievement Unlock

```javascript
#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

```javascript
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

```css
@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

```javascript
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:

```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

```css
.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

```css
.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 `transform` and `opacity` for smooth 60fps
- Keep animations under 300ms for interactions
- Use `will-change` sparingly 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

```javascript
#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 |

