Enhancing User Experience with CSS and JavaScript Animations

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
transformandopacity. - 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.Scroll‑based effects
, such as parallax scrolling or lazy loading.React and Next.js applications
, where component‑based 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:
transformandopacitycan avoid layout and painting work that changes towidthorheightmay 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 theprefers-reduced-motionsetting to disable animations for users who prefer minimal movement.Optimise for performance
: Avoid animations that trigger layout recalculations, and userequestAnimationFrame()instead ofsetTimeout()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 reduced‑motion 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 longer‑running sequences, also respond if that preference changes during playback.