Let CSS do the animating
jQuery's effects work by changing a style many times a second from JavaScript. Modern browsers have a better tool: CSS transitions. You describe the two states in CSS, and the browser animates between them, smoothly and often on the graphics hardware.
jQuery's job becomes tiny: add or remove a class.
<style>
.heart {
display: inline-block; font-size: 28px; color: #94a3b8;
transition: color .3s ease, transform .3s ease;
}
.heart.liked { color: #e11d48; transform: scale(1.3); }
</style>
<button id="like" aria-pressed="false"><span class="heart" aria-hidden="true">♥</span> Like</button>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$("#like").on("click", function () {
const liked = $(this).find(".heart").toggleClass("liked").hasClass("liked");
$(this).attr("aria-pressed", liked);
console.log("liked:", liked);
});
</script>liked: trueThis animates a colour and a transform (scale), neither of which .animate() can do. The JavaScript only toggles a class and updates aria-pressed, the attribute that tells assistive technology a toggle button is on.
Why this is better
- Smoother. The browser can run the animation without waiting for JavaScript on every frame.
- More properties. Colours, shadows, transforms, filters: anything CSS can transition.
- Design in one place. The look and timing live in the stylesheet, where designers can change them.
- Less code. No durations, easings or callbacks in JavaScript.
Showing and hiding with transitions
display cannot be transitioned, so to fade a panel in and out with CSS, transition opacity and visibility instead:
<style>
.drawer {
opacity: 0; visibility: hidden; transform: translateY(-8px);
transition: opacity .2s, transform .2s, visibility .2s;
background: #f1f5f9; padding: 10px;
}
.drawer.is-open { opacity: 1; visibility: visible; transform: none; }
</style>
<button id="open" aria-expanded="false" aria-controls="drawer">Account menu</button>
<div id="drawer" class="drawer">Profile · Orders · Sign out</div>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$("#open").on("click", function () {
const open = $("#drawer").toggleClass("is-open").hasClass("is-open");
$(this).attr("aria-expanded", open);
console.log("drawer open:", open);
});
</script>drawer open: truevisibility: hidden keeps the hidden drawer out of the keyboard tab order and away from screen readers, which opacity: 0 alone would not.
Running code when a transition ends
.addClass() is instant and has no callback. To run code once the CSS transition has finished, listen for the browser's transitionend event:
<style>
.alert { padding: 10px; background: #fee2e2; transition: opacity .25s, transform .25s; }
.alert.leaving { opacity: 0; transform: translateX(30px); }
</style>
<div class="alert" id="alert">Payment failed. <button id="dismiss">Dismiss</button></div>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$("#dismiss").on("click", function () {
$("#alert")
.one("transitionend", function () {
$(this).remove();
console.log("removed after the transition");
})
.addClass("leaving");
});
</script>removed after the transitionUse .one() here, not .on(). The transition animates two properties (opacity and transform), and the browser fires transitionend once for each. With .one(), the code runs on the first and the handler removes itself.
If there is a chance the transition never runs (for example, the element is already hidden, or reduced motion turns transitions off), transitionend never fires. A safe pattern is to also set a timer slightly longer than the transition as a fallback.
Restarting a CSS animation
CSS @keyframes animations can be restarted by removing and re-adding their class. The browser needs to notice the removal, so read a layout property in between:
<style>
@keyframes shake { 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }
.shake { animation: shake .3s; }
</style>
<input id="pw" type="password" placeholder="Password"> <button id="shake">Wrong password</button>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$("#shake").on("click", function () {
const $pw = $("#pw").removeClass("shake");
void $pw[0].offsetWidth; // force the browser to apply the removal
$pw.addClass("shake");
console.log("shake class added:", $pw.hasClass("shake"));
});
</script>shake class added: trueWithout the offsetWidth line, removing and adding the class in the same moment does nothing, because the browser never sees the class missing. A shake must not be the only error signal, so also show the error in text.
Respecting reduced motion
Wrap your transitions in a media query so that people who have asked their system for less motion get instant changes:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}A tiny duration (rather than none) keeps transitionend firing, so code that waits for it still runs. For jQuery effects, $.fx.off = true does the same job, as shown in the show and hide lesson.
When to use which
| Use | When |
|---|---|
CSS transition + .toggleClass() |
hover-like states, menus, drawers, toggles, colour and transform changes |
.fadeIn(), .slideDown() |
quick scripts, sliding content whose height you do not know, existing jQuery code |
.animate() |
values calculated at run time, scroll positions, step-by-step updates |
Common mistakes
- Trying to transition
display. Useopacityandvisibility. - Using
.on("transitionend")and running code once per property. - Relying only on
transitionendwith no fallback. - Animating
width,height,toporleftin CSS for frequent effects;transformandopacityare much smoother.
Exercise
Make a card that gets a shadow and moves up by 4px when it has the class raised, using a CSS transition. Toggle the class with a button, and update the button's aria-pressed attribute.
Show one possible answer
<style>
.card { padding: 16px; background: white; border: 1px solid #e2e8f0; transition: box-shadow .2s, transform .2s; }
.card.raised { box-shadow: 0 8px 20px rgba(0,0,0,.15); transform: translateY(-4px); }
</style>
<div class="card" id="card">A card</div>
<button id="raise" aria-pressed="false">Raise card</button>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$("#raise").on("click", function () {
const raised = $("#card").toggleClass("raised").hasClass("raised");
$(this).attr("aria-pressed", raised);
console.log($(this).attr("aria-pressed"));
});
</script>trueKey points
- Describe the start and end states in CSS with a transition; use jQuery only to add or remove the class.
- CSS transitions can animate colours and transforms, which .animate() cannot, and they are smoother.
- Listen for transitionend (with .one()) to run code when a CSS transition finishes.
- Wrap motion in a prefers-reduced-motion media query so people who ask for less motion get instant changes.