I built it in a glass style and mobile dropped to 20fps
My glass-style portfolio fell to 20fps on mobile. I throttled the CPU, measured frame intervals, found background blur, hidden badges and card backdrop-filter, and got 60fps back.
#Performance #CSS #backdrop-filter #Mobile #React
I redid my portfolio in a glass style. That translucent glass look Apple uses. What's behind each card shows through a little, and behind the whole screen I had aurora-like blobs of color drifting slowly around. On my laptop I liked it quite a bit. The problem was the phone. When I tapped a menu, the screen followed a beat late, and scrolling couldn't keep up with my finger. At first I thought it was just my phone being old, but it was the same on other phones. This post is a record of finding the cause with numbers and fixing it. This can't be fixed by feel "It stutters" doesn't tell me where to start. So I launched headless Chrome, set the screen to 390x844 and slowed the CPU down 4x. Think of it as roughly a mid-range Android phone. In that state I just opened the page, touched nothing, and measured the frame intervals. await send('Emulation.setDeviceMetricsOverride', { width: 390, height: 844, deviceScaleFactor: 2, mobile: true, }); await send('Emulation.setCPUThrottlingRate', { rate: 4 }); With requestAnimationFrame I recorded the time whenever a frame was drawn and measured the gap from the previous frame. Collecting 180 frames that way gave this. To run at 60fps the frame interval has to be 16.7ms, and it came out at 50ms. That means it was drawing about 20 frames a second. It was already like this without me touching anything, so there was no way scrolling or screen transitions could be smooth. There were three culprits I counted the elements with expensive properties on that same screen, and out of 221 in total, 16 had backdrop-filter on them. Listing where they were, there was one glass sheet covering the whole screen, six project cards, six badges that appear at the top right of a card on mouse hover, and three for the bottom dock and buttons. The badges were odd to begin with. They only show on hover, so normally their opacity is 0. But transparent and absent are different things. Even at opacity 0 the layer still gets created and is composited every frame. On top of that, phones have no mouse, so those six were making six blur layers even though a user would never see them in their life. I just switched them to a translucent background. That took care of the badges, but the really big one was somewhere else. It was the three blobs of color I'd laid in the background, and looking at the code again I winced. .aurora-blob { width: 46vw; height: 46vw; background: radial-gradient(circle at center, #a893ff 0%, rgba(168,147,255,0) 70%); filter: blur(88px); animation: aurora-drift-1 22s ease-in-out infinite; } I had put an 88px blur on a blob about half the size of the screen and kept it moving for the full 22 seconds. For the browser, every time this moves it has to blur that whole wide area again. And I was running three of them, so of course there was no CPU left over. But looking closely, the background is already a radial-gradient. The opacity goes to 0 from the center outward, so the edges already fade out softly. I had put one more blur on top of that . So I deleted the filter line and instead just widened the point where the gradient fades out a little, from 70% to 78%. I can't see the difference by eye, but the amount of computation changed completely. The last one was the cards. backdrop-filter takes what's behind and blurs it, so with six cards that work happens six times. A laptop can take it, but it was too much for a phone GPU. I went back and forth a bit here. Strip out all the glass and there's no reason to have built it this way in the first place, leave it as is and it's unusable on a phone. In the end I decided to give up only the ones there are many of. On touch devices I turned off the blur on the cards and switched them to an opaque surface that goes with the background color. It behaves that way on devices with no mouse (hover: none) or when the width is under 640px. @media (hover: none), (max-width: 640px) { .glass-card, .card { backdrop-filter: none; background: var(--card-solid); } .glass-sheen::before { display: none; } .aurora-blob { animation: none; } } The one sheet covering the whole screen and the dock and buttons, three of them, I left as glass. There are few of them so the cost isn't large, and come to think of it, the things that need to look like glass are the ones that float, not the cards lying flat underneath. Even with the cards opaque, the background color shows through from behind, so the mood stayed the same. Results Measuring again under the same conditions, backdrop-filter went from 16 down to 4 and the blur filter was gone entirely. The frame interval came out at a median of 16.7ms, and p95 was 16.7ms too. Not a single frame went over 32ms. It went from 20fps to 60fps. I also measured while the screen changes after tapping a menu, and the median was the same 16.7ms, with exactly one frame over 32ms. That one is the moment the new screen is drawn for the first time, so it can't be helped. This is how it looks on a phone. The cards are opaque now, but the background tint is the same. The laptop still has the glass exactly as it was. Looking back, of the three, the cards were the only one I had guessed in advance. There were a lot of them, so I figured they'd be expensive. The biggest one turned out to be a single filter line I'd put in the background without thinking, and it was a line that didn't even need to be there. The least noticeable element on the screen was the most expensive one. I did know that a layer stays alive even at opacity 0, but I didn't know there were six of those in my own code until I counted. On the laptop every card still has a blur on it, so the same thing will happen if the cards grow to twenty or so, and I plan to measure again when that time comes.