Performance debugging
Page slow at 30 fps? Check backdrop-filter before you tune your 3D
The page has a live animated background: WebGL, a canvas, a video. It runs at around 30 frames per second and feels sluggish. The obvious suspect is the animation, so you start making it cheaper: fewer points, a lower pixel ratio, simpler shaders. It barely helps.
That is exactly what happened on this site. The 3D scene was innocent. One CSS class on the header was the whole problem. This page shows how we proved it in a few minutes, and how you can check your own page.
Short answer
- `backdrop-filter` re-blurs everything behind an element on every frame that the content behind it changes. Over an animating canvas, that is every frame.
- Measure first: frame rate with the full page, then with the animation removed, then with one layer off at a time.
- Our numbers: full page 29 fps, page without any 3D 61 fps, blur removed from the header 61 fps.
- The fix: drop the blur and give the header a nearly opaque background instead. On a dark design it looks the same.
The wrong suspect
Our home page has a live WebGL point cloud behind the content. It measured 28 to 33 fps, even after we had already reduced the number of points and the pixel ratio of the scene. So we kept tuning the scene, because that was the expensive-looking part.
That is the trap: you optimise the thing you assume is guilty, and every small gain convinces you that you are on the right track. The real question is not "how do I make the 3D cheaper?" but "what is actually costing the frames?"
The control experiment
Instead of more tuning, we measured the page in different states. Same browser, same machine, same window size. One change at a time.
Page with no 3D at all: 61 fps. Full page as built: 29 fps. Gradient overlay removed: 35 fps. `backdrop-filter` removed from the fixed header: 61 fps.
The second line proves the page can run at full speed. The last line shows where the frames went: one backdrop-blur-md class on the header. With the blur gone, the full page, 3D included, ran at the same speed as the page without any 3D.
Measure it on your own page
Paste this in the browser console. It lists every element with a backdrop-filter, then measures the frame rate for three seconds. Run toggleBlur() to switch all of those filters off, and measure again with fps(). Run toggleBlur() once more to switch them back on. Keep the tab in front while measuring: browsers slow down tabs in the background.
const blurred = [...document.querySelectorAll("*")].filter(
(el) => getComputedStyle(el).backdropFilter !== "none",
);
console.table(blurred.map((el) => ({
element: el.tagName.toLowerCase() + (el.id ? "#" + el.id : ""),
backdropFilter: getComputedStyle(el).backdropFilter,
})));
function fps(ms = 3000) {
return new Promise((done) => {
let frames = 0;
const start = performance.now();
(function tick(now) {
frames++;
if (now - start < ms) requestAnimationFrame(tick);
else done(Math.round((frames * 1000) / (now - start)));
})(start);
}).then((v) => console.log("fps:", v));
}
const original = blurred.map((el) => el.style.backdropFilter);
let blurOff = false;
function toggleBlur() {
blurOff = !blurOff;
blurred.forEach((el, i) => {
el.style.backdropFilter = blurOff ? "none" : original[i];
});
console.log(blurOff ? "blur off" : "blur on");
}
fps();Why the blur is so expensive here
A backdrop-filter blurs whatever is drawn behind the element. If that background is static, there is little to redo. If it changes every frame, like an animating canvas, the browser has to read the pixels behind the header and blur them again, every frame, on top of drawing the animation itself.
The cost grows with the size of the blurred area and the blur radius. A full-width fixed header over a full-screen canvas is close to the worst case.
The fix
We removed the blur and raised the opacity of the header background instead: a nearly opaque version of the page background colour. On a dark design the difference is hard to see, and the cost is zero. The page went back to a steady 60 fps at full quality, without making the 3D scene any cheaper.
If you really want the frosted look, keep it away from moving content. Blur over a static image or plain colour is cheap. Blur over animation is not.
The method matters more than the bug
The lesson is not only about backdrop-filter. When something is slow, switch layers off one at a time and measure, instead of tuning the part you assume is guilty. Two minutes of measuring can save you hours of optimising the wrong thing. The same goes for heavy shadows, large filter effects, and big transparent overlays on top of animation.
Slow page, and not sure why?
Send us the URL, the device and browser where it feels slow, and what you already tried. We first measure where the time goes, then tell you what the fix takes.
Please do not send passwords or API keys. If we need access, we arrange that safely.
FAQ
Frequently asked questions
Is backdrop-filter bad for performance?
Not always. Over a static background it is usually fine. Over content that changes every frame, such as an animated canvas or video, the browser has to re-blur the background every frame, which can cost a lot of frame rate.
Why is my page with a three.js or canvas background slow?
It may not be the 3D. Measure the page with the animation removed and with other layers switched off one at a time. In our case a blurred fixed header over the canvas cost half the frame rate.
What can I use instead of backdrop-filter blur?
A nearly opaque background colour on the element. On dark designs it looks almost the same. If you need the frosted effect, use it only over static content.
How do I measure frame rate in the browser?
Use the Performance panel in DevTools, or count requestAnimationFrame callbacks over a few seconds with the snippet on this page. Keep the tab in front, because background tabs are slowed down.