Skip to content
F FinanceMass Arcade Free HTML5 arcade games
technical

How CSS Animations Sabotage Your Games Frame Rate

Why CSS animations next to your canvas can silently steal frame-rate budget even when the JavaScript profiler shows nothing wrong. The bug nobody checks for and the one-line fix.

MB By Maya Brennan · October 20, 2025
How CSS Animations Sabotage Your Games Frame Rate

Honestly, I have had this conversation with browser-game developers more times than I can count, and it always starts the same way. They tell me their game is hitting 60fps in the canvas. They have profiled the JavaScript and it looks clean. The asset payload is small. But for some reason the game still feels slightly off. Animations stutter at random moments. The framerate counter shows 60 but the eyes say something different.

Most of the time the cause is the same: CSS animations running concurrently with the canvas game. It is the bug that browser-game developers almost never check for because it does not show up in the obvious places.

I want to write the explanation. This is one of those subtle technical issues that nobody talks about until they hit it, and once you understand it the fix is straightforward.

What CSS animation actually does

When you use CSS to animate a DOM element (a UI button, a menu, a hover effect, a smooth fade-in), the browser handles the animation outside of your game loop. The CSS animation system runs on its own internal clock, typically tied to the same vsync that drives your canvas rendering, but processed through a different code path inside the browser engine.

For most web pages, this is fine. CSS animations are exactly what they were designed for: animating page UI without requiring JavaScript intervention. The browser optimises CSS animations heavily, often offloading them to the GPU directly, and they run smoothly on a wide range of hardware.

For browser games, CSS animations interact with the canvas in ways that are easy to miss. Both your canvas updates and the CSS animations compete for the same vsync-aligned paint window. If the CSS animation needs to repaint a chunk of the page at the same moment your canvas is updating, the browser has to choose. The choice usually goes against you.

The specific failure mode

Here is what happens in detail. Your game runs its requestAnimationFrame callback on schedule. It updates the canvas at 60 times per second. Each frame takes maybe 8 milliseconds to render.

Meanwhile, you have a CSS animation running on a sidebar element, maybe a pulsing logo or a sliding menu item. This animation also updates 60 times per second. Each CSS frame takes maybe 1 to 2 milliseconds. Small.

When both want to paint simultaneously, the browser composites them in sequence. Your canvas paint happens first, then the CSS layout work, then the compositor pass, then the final present-to-screen step. The total work per frame is now 8 plus 2 plus a small compositor overhead. Maybe 11 milliseconds total.

This is still under the 16.67-millisecond budget for 60fps. So the game runs at 60fps and everyone is happy.

Until the CSS animation hits a complex state. The element it is animating triggers a layout recalculation. The browser has to recompute styles for surrounding elements. The recompute takes 8 extra milliseconds on a slow phone. Suddenly your frame budget is gone. The canvas update gets queued for the next frame, which means you skipped a frame. The player sees a stutter.

Why this is hard to detect

The frame-rate counter inside your game does not see this stutter because the canvas itself is still drawing at 60fps. The frame counter is measuring canvas operations. The stutter is happening because the canvas updates are reaching the screen late, not because they are being computed late.

Browser dev tools have a Performance tab that can show this if you know what to look for. The flame chart shows your script work, the CSS layout work, and the compositor work separately. If the layout work is taking longer than you expect, that is your CSS animations interfering.

But most game developers do not open the Performance tab unless something is obviously wrong. The CSS-animation interference rarely looks obviously wrong. The game feels slightly off and the developer concludes it is a JavaScript optimisation problem and chases the wrong fix.

The common offenders

A few specific CSS patterns are the most common culprits.

Pulsing or breathing animations on UI elements next to the game canvas. These run continuously and force constant layout work. A breathing logo in the page header is a frame-rate killer for a game running below it.

Hover effects with smooth transitions. When the player moves the mouse, hover effects fire and trigger layout recalculations. Mouse movement during gameplay is exactly when frame rate matters most.

Scrolling animations or parallax effects on the page. If your game canvas is inside a page that has scroll-linked animations, scrolling during gameplay (which players do on mobile to dismiss the address bar) triggers the parallax recalculations and steals your frame budget.

Animated gradients or background patterns. These look elegant but force the browser to repaint large screen areas continuously. Even when the player is focused on the canvas in the middle of the screen, the background is paying for the animation.

The fix is straightforward

When your game starts, pause every CSS animation on the page. When the game ends or pauses, resume them.

Actual implementation is one line of CSS plus a JavaScript toggle. You add a class to the body element when the game is active. Inside that class, you set animation-play-state to paused for all your animations. The browser stops doing the work. Your canvas gets its full frame budget back.

I have seen this fix change a game from feeling sluggish to feeling crisp without any other code changes. The CSS animations look fine when you are not playing. They are paused while you are playing. The visual cost is minimal because the game canvas is what the player is looking at anyway.

The will-change property is not always your friend

A related issue. The CSS will-change property tells the browser to optimise an element for upcoming animation. It works by promoting the element to its own GPU layer. This sounds good and usually is good. But every element you promote to its own layer costs GPU memory and compositor overhead.

If you over-use will-change on lots of small elements, the compositor has to manage all those layers every frame. This adds compositor cost even when the elements are not changing. The compositor cost shows up as occasional stutter, the same way CSS animations do.

Rule of thumb is to use will-change sparingly and remove it when an animation finishes. Most browser games can avoid will-change entirely. The performance gains from promotion are small compared to the cost of having too many layers active.

What this tells you about browser performance

Wider lesson is that browser performance is a system-level concern. Your JavaScript can be optimised perfectly and your canvas rendering can be tight, but if your CSS is fighting your canvas for paint time, the game still feels off. The lesson took me embarrassingly long to learn because each piece looked fine in isolation.

When a browser game feels sluggish and nothing obvious is wrong, check the CSS. Pause animations during gameplay. Remove will-change from elements that no longer need it. Check the Performance tab for layout work that is taking longer than it should. And profile on the slowest device you reasonably expect a player to use.

I have flagged this pattern in maybe four game reviews on this site over the past year. The games that get it right have noticeably better feel. The games that get it wrong feel slightly off in ways the developer probably did not understand. Now you know what to look for.

Frequently asked questions

How do I check if CSS animations are hurting my games performance?

Open browser developer tools, switch to the Performance tab, record a few seconds of gameplay. Look for long bars labelled as Layout or Paint work happening at the same rhythm as your CSS animations. That overlap is the bug.

Should I never use CSS animations on a page that has a game?

CSS animations are fine when the game is not active. Pause them during gameplay using animation-play-state set to paused on a class added to the body. Resume them when the game pauses or ends.

What is animation-play-state?

A CSS property that controls whether an animation is running or paused. Setting it to paused freezes the animation at its current frame and the browser stops doing layout work for it. Setting it to running resumes.

Is will-change always a performance optimisation?

Not always. The will-change property promotes the element to its own GPU layer, which has memory and compositor costs. Over-using it on many small elements can hurt performance more than it helps. Use sparingly and remove when the animation finishes.

How does this issue affect mobile browser games specifically?

Mobile browsers have less compositor overhead capacity than desktop. CSS-animation interference shows up more obviously on phones, especially older mid-range ones. The Pixel 4a benchmark in another post on this site is where I notice this issue most often.

MB
About the writer
Maya Brennan
Puzzle and logic games · Boston, MA

Math tutor turned freelance writer. Reviews puzzle and logic games, mostly the ones with an obvious right answer she got wrong on the first three tries.

More from Maya →