Enhancing User Experience with CSS and JavaScript Animations

Abstract image used to represent Enhance User Experience with CSS and JS Animations
Image by Joes Valentine.

Animation earns its place when it explains a state change, preserves spatial context, or gives useful feedback. It becomes noise when it merely delays the next action. CSS is usually enough for simple transitions; JavaScript belongs in the loop when the motion depends on application state or sequencing.


Why Use Animations?

Animations are not just about aesthetics; they serve important functional purposes in web applications. Key benefits include:

  • Visual feedback

    : Indicates user interactions, such as button hover effects or loading spinners.
  • Guiding attention

    : Directs focus towards important elements, such as form validation messages or navigation menus.
  • Enhancing aesthetics

    : Subtle animations can make an interface feel modern and refined.
  • Reducing cognitive load

    : Smooth transitions help users process changes more naturally, improving usability.

However, excessive or poorly optimised animations can negatively impact performance and accessibility, making careful implementation essential.


CSS Animations

CSS provides a lightweight way to create animations, using properties like transition and @keyframes.

Using transition for Simple Effects

The transition property allows elements to change smoothly between states, for example:

.button {
  background-color: #007bff;
  transition: background-color 0.3s ease-in-out;
}

.button:hover {
  background-color: #0056b3;
}

This will create a gradual colour change when hovering over the button, improving visual feedback. I go into detail on how to use transition within CSS animations in my article: "Understanding CSS Transitions".

Creating More Complex Animations with @keyframes

For more advanced animations, @keyframes allows defining movement over time, like this:

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.element {
  animation: fadeIn 1s ease-in-out;
}

This smoothly fades an element into view over one second.

I also go into much more detail on how you can make the most of @keyframes animations here: "Mastering CSS Animations with @keyframes".

When to Use CSS Animations

CSS animations are best suited for:

  • Simple effects like fades, hover effects, and colour transitions.
  • Effects using properties the browser can animate efficiently, such as transform and opacity.
  • Scenarios where JavaScript control over animations is unnecessary.

JavaScript Animations

JavaScript provides more flexibility for dynamic and interactive animations, especially when precise control over timing and state is required.

Using requestAnimationFrame() for Smooth Performance

The requestAnimationFrame() method schedules a callback before the next repaint. Use its timestamp to measure elapsed time, rather than adding a fixed amount on every frame:

function fadeIn(element) {
  const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
  if (reducedMotion.matches) {
    element.style.opacity = '1';
    return;
  }

  const duration = 1000;
  let startedAt = null;
  element.style.opacity = '0';

  function animate(timestamp) {
    if (reducedMotion.matches) {
      element.style.opacity = '1';
      return;
    }
    if (startedAt === null) startedAt = timestamp;
    const progress = Math.min((timestamp - startedAt) / duration, 1);
    element.style.opacity = String(progress);
    if (progress < 1) requestAnimationFrame(animate);
  }

  requestAnimationFrame(animate);
}

const element = document.querySelector('.fade-in');
if (element instanceof HTMLElement) fadeIn(element);

This plain JavaScript example fades the element in over one second, measured from the first frame. The callback still runs on the main thread, so keep its work short. It also checks prefers-reduced-motion before starting and on each frame, showing the final state immediately when the browser reports that preference.

When to Use JavaScript Animations

JavaScript is most useful for:

  • Complex animations

    that require physics, sequencing, or user interaction.
  • Scrollbased effects

    , such as parallax scrolling or lazy loading.
  • React and Next.js applications

    , where componentbased animation control is needed.

Best Practices for Web Animations

To ensure animations enhance the user experience without compromising performance, follow these best practices:

  • Choose properties carefully: transform and opacity can avoid layout and painting work that changes to width or height may trigger. The browser decides how to composite the result, so check the actual animation rather than assuming GPU acceleration.
  • Keep animations subtle

    : Excessive motion can be distracting and reduce usability.
  • Respect user preferences

    : Detect the prefers-reduced-motion setting to disable animations for users who prefer minimal movement.
  • Optimise for performance

    : Avoid animations that trigger layout recalculations, and use requestAnimationFrame() instead of setTimeout() for smooth updates.
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

This rule disables CSS animations and transitions when the browser supports prefers-reduced-motion and the user requests less motion. It does not stop JavaScript animations; those need their own check, as in the earlier example.


Wrapping Up

Key Takeaways

  • CSS animations

    work well for simple effects like fades and transitions.
  • JavaScript animations

    offer more control for interactive and dynamic motion.
  • Animation libraries can help with complex sequencing.
  • Performance and accessibility considerations

    should always be prioritised.

Animate properties such as transform and opacity where possible, keep the duration proportionate to the interaction, and honour reducedmotion preferences. Most importantly, make the state change understandable without the animation. Motion should clarify the interface, not become another task the user has to wait through.

Postscript

November 2019: GSAP 3 brings the animation tools together under gsap. This example uses that newer API, which was not available when I wrote the original article in January 2018:

const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
if (reducedMotion.matches) {
  gsap.set('.box', { x: 100, opacity: 1 });
} else {
  gsap.to('.box', { duration: 1, x: 100, opacity: 1 });
}

With GSAP loaded, this moves .box by 100 pixels on the horizontal axis and sets its opacity to 1. It checks the motion preference before starting and applies the final state immediately when less motion is requested. For longerrunning sequences, also respond if that preference changes during playback.

Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.