ux-animation-motion

matthewharwood's avatarfrom matthewharwood

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)

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 9, 2025

When & Why to Use This Skill

This Claude skill provides a comprehensive framework for implementing responsive and accessible UI animations using Anime.js v4. It streamlines the creation of high-quality motion patterns for user feedback, state transitions, and celebratory effects, while ensuring performance optimization and full compliance with accessibility standards through reduced motion handling.

Use Cases

  • Implementing tactile UI feedback such as button press effects, success bounces, and error shakes to improve interactivity.
  • Creating smooth component transitions including staggered list animations, fade-ins, and scale-in effects for modals and overlays.
  • Developing gamified feedback systems for web applications, such as achievement unlocks, score updates, and celebratory glow animations.
  • Ensuring web accessibility by automatically detecting and respecting user 'prefers-reduced-motion' settings with safe animation wrappers.
  • Optimizing frontend performance by utilizing 60fps-ready animation patterns that focus on transform and opacity properties.
nameux-animation-motion
descriptionAnimation 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)

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.

  • 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

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

#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