Front-End
A janky animation is worse than no animation at all — it draws attention to itself for the wrong reason. Most of the time, the difference between a buttery-smooth transition and one that stutters isn't the animation library or the easing curve, it's which CSS properties are actually being animated.
Every frame, the browser roughly does three things: layout (work out the size and position of everything), paint (work out pixels and colours), and composite (combine already-painted layers on the GPU). The further up that list a property change forces the browser to go back to, the more expensive the frame is.
width, height, top, left, or adding/removing content forces layout — the browser has to recalculate the position of potentially everything else on the page, then repaint, then recomposite. This is the expensive path.background-color, box-shadow, or border-color skips layout but still forces a repaint of that element.transform and opacity can usually skip both layout and paint entirely — the GPU just recomposites already-painted layers in a new position or with new transparency. This is the cheap path, and it's why every animation library defaults to it.Prefer transform and opacity for anything that needs to run smoothly, especially on lower-powered devices:
transform: translate(...), not left/top.transform: scale(...), not width/height, where the visual result allows it.opacity, not by animating background-color's alpha channel.transform: rotate(...) rather than any layout-affecting alternative.If you genuinely need to animate something like height (an accordion expanding, for instance), it's often smoother to animate max-height or a grid-template-rows trick, or to measure the element's height once with JavaScript and animate to that fixed pixel value with transform: scaleY() instead — depending on how forgiving the layout around it is.
will-change property is a hint, not a switchwill-change: transform tells the browser to promote an element to its own compositor layer ahead of time, which can help avoid a jump the first time an animation runs. It's easy to overuse, though — promoting too many elements to their own layers eats GPU memory and can make things slower, not faster. Use it sparingly, on elements you know are about to animate, and remove it once the animation is done rather than leaving it on permanently.
Animation libraries are good at scheduling and easing, but they can't rescue an animation that's targeting an expensive property. If a GSAP tween feels stuttery, the first thing worth checking is what property it's actually touching — often the fix is switching the tween's target from a layout property to a transform, not tweaking the library's configuration.
Not every visitor wants motion, and for some it's genuinely uncomfortable. Wrap non-essential animation in a prefers-reduced-motion check and provide a calmer (or instant) alternative — this matters for accessibility as much as it does for performance.
Chrome DevTools' Performance panel and the Rendering tab's "Paint flashing" and "Layout Shift Regions" overlays make it obvious, in seconds, whether an animation is triggering layout or paint it doesn't need to. It's a five-minute check that saves a lot of guesswork.
Often it's a one-line fix once you know which property is the culprit — happy to take a look.