文章

/

About 1,566 words

/

8 min read

A Problem I Investigated for a Long Time

By Kalopsia / ChatGPT 5.4

A seemingly simple styling issue of 'no blur' led me all the way to will-change. This article documents how I unraveled it last night.

#前端 · #CSS · #毛玻璃 · #调试

Article

A Long-Lasting Issue

A late-night investigation that started with "styles not taking effect," which I eventually traced back to the host chain and will-change.


This Isn't the Kind of Bug That's "Obvious at First Glance"

Last night, I sat in front of my computer, staring at the widgets for nearly half an hour, and the more I looked, the more something felt off.

The cards lacked the frosted glass effect they were supposed to have. It wasn't the kind of "completely missing styles" absence, but rather it looked like it was written, yet seemed not to take effect—you could sense it wanted to be glass, but it just fell short.

The most frustrating part was that it wasn't consistently "completely without blur." At some angles, on some cards, you could see a hint of it, as if it had been washed away by something. Within the same set of widgets, there was a visual mismatch where "this one looks like glass, that one looks like a regular translucent panel."

My first reaction, of course, was to check if backdrop-filter was written, if the opacity was too low, or if the background was being covered by something. I even started to wonder if I had stumbled into another browser compatibility pitfall.

But the more I looked, the more it didn't add up.

This is exactly what makes such issues so torturous: you think you're investigating a CSS property, but in reality, you're investigating an entire rendering pipeline.


Why Frosted Glass Issues Are Particularly Maddening

In this widget system, what truly contributes to the visual outcome isn't just the card itself. It involves at least the following layers:

  • The widget's own shell
  • The veil / highlight / border decorations on the shell
  • The scrolling container and clipping relationships of the rail
  • The motion animation wrapper
  • The overflow, isolation, and compositing contexts of ancestor layers

This is why I later forced myself not to adjust parameters based on gut feelings. Because if the responsible layer isn't pinpointed, any seemingly effective opacity tweak is just masking the problem, not solving it.

Especially with a frosted glass effect, it can't be faked with just a light-colored background pretending to be sophisticated. It must simultaneously satisfy three things: the background is actually sampled, the foreground has a glass-like shell, and the hierarchical relationship between them doesn't wash away the blur again. If any link is misaligned, what you end up seeing with your eyes is just a "wannabe glass that failed" translucent panel.


I Decided to Stop Guessing and Break Down the Investigation into A/B/C/D

The best thing I did last night was to stop randomly testing in the live page. I forced myself to sit down and break the problem apart.

I divided the investigation into four stages:

  • A: Start with minimal isolation. Just see if the basic blur can work, ruling out the most basic issue of "the property simply not taking effect."
  • B: Then gradually add back the shared shell and surface layers to confirm if pseudo / wash / veil are washing out the visual effect.
  • C: Return to the real rail host chain, but instead of enabling everything at once, add back layers C0 to C4 step by step.
  • D: Finally, bring in the full host environment, restoring the real animations, wrappers, and scrolling containers to the scene.

This breakdown approach may seem clumsy, but its benefits are huge—I no longer have to speculate about a complex page with "everything inside," but can clearly know at which layer the problem first appears.


What really brought me close to the answer was segment C

In the step-by-step addition from C0 to C4, I wasn't focusing on "which card looks more like glass," but which host condition, once restored, causes the blur to collapse.

Looking at it this way, the problem became less mysterious. The first stable point where it collapsed wasn't within the widget's own internal content layer, nor in the rail's decorative layer, but near the host item that carries motion.

Then I continued to break down within segment C, removing suspect conditions one by one:

  • C0: Keep as is → blur didn't appear
  • C1: Remove overflow-y: auto → still didn't appear
  • C2: Remove will-changewait, blur came back
  • C3: Remove overflow-x: clip → still there
  • C4: Adjust diagnostic background → still there

The result was very clear: The first node that restored the blur was removing will-change.

I stared at the screen for several seconds, thinking: That's it?


The Culprit: will-change: transform, opacity

Yes, it's this very common line of styling, often considered a "performance optimization standard":

will-change: transform, opacity;

This thing usually looks completely harmless, even with a bit of "I'm professional, I've done performance optimization" vibe. But once it's permanently attached to the real host chain responsible for blur sampling, the situation changes entirely.

This time, the issue I encountered wasn't a theoretical "might have an impact under certain circumstances," but a very specific phenomenon: as long as this layer will-change is reattached, the frosted glass effect that should be established in Glass mode is noticeably broken; once it's removed from the real blur host chain, the result immediately starts to recover.

Why is this? I later checked MDN and roughly understood: backdrop-filter inherently heavily relies on the browser's handling of compositing layers, sampling areas, and painting order. After will-change pushes the element into a more aggressive compositing preparation state, the background sampling that should have been established on that chain is cut off, resulting in a different outcome. It's not necessarily that "the filter completely disappears," but ultimately you see a visual result that very much resembles "the glass effect not being established."

(In plain terms: you tell the browser "this element is about to move, prepare in advance," and it prepares too eagerly, "optimizing" away the glass effect.)


I set a new rule for myself

Precisely because of this, I later established a stricter rule for myself:

Any item serving as the real blur host is not allowed to permanently have will-change: transform, opacity.

This doesn't mean will-change can never be used, but rather that it can no longer be treated as a default safe sugar coating, especially not indiscriminately applied to glass hosts. If performance optimization is absolutely necessary, then:

  • Add it temporarily and remove it after the animation ends
  • Or move the animation down to inner elements that do not bear the blur responsibility

This is not the end of the story

By the end of last night's round of wrapping up, the things I confirmed were roughly as follows:

First, the main failure of the frosted glass effect for widgets in Glass mode is no longer a "speculative level" issue; the real recovery point has been found and can be stably reproduced and reversed.

Second, the visual perception of "a whole column of light-colored rectangular backing" on the rail is not due to some mysterious large container secretly painting a background layer, but rather a false impression created by the combination of scroll container clipping, card shadows being consumed, and the shell layer being too solid. This part was incidentally discovered and hasn't been fully cleaned up yet.

Third, music is not perfect from the start either. It just appears more "established" than other cards because it has more internal layers; the real work still needs to return to the unified shell layer and real host chain for calibration.

Of course, last night doesn't mean everything is over. There are still a few things I clearly know should not be conflated:

  • The Ticket mode cannot simply adopt Glass's judgment criteria
  • More panels and mobile widget entry points represent another independent shell and interaction issue
  • Readability veils, surface layers, drag-and-drop feel—these finishing tasks must be handled separately after the main issue is confirmed

In other words, will-change's positioning solved "who is the main culprit of the fault," but it did not automatically answer all visual and interaction questions for me.


After this investigation, I believe one thing even more

Looking back now, the most noteworthy point from last night wasn't which line of style I ultimately deleted, but that I was once again reminded:

In complex frontend issues, the most dangerous action often isn't making a mistake, but making changes too quickly.

If I had immediately started randomly adjusting opacity, shadows, borders, and blur radius on both Ticket and Glass sides last night, I likely would have ended up with a version that "looks temporarily acceptable," but I wouldn't have known what I actually fixed.

And this time, I was able to trace the problem to will-change not by inspiration, but by discipline:

  • First isolate the environment, then reintroduce complexity
  • First identify the responsible layer, then adjust visual parameters
  • First write interim conclusions, then decide whether to touch other lines in the next round

I'm increasingly convinced that this investigation approach itself is also an engineering asset.

Because what's truly frightening is never a specific blur bug, but our habitual tendency to attribute everything to "let's try adjusting a few more parameters" when we lack evidence.


At least one thing I can say with certainty from last night: this "no frosted glass" fault is not a perception issue, nor a browser temperament issue. It indeed has a culprit, and I've already pulled that culprit out of the host chain.

(Next, I should investigate Ticket mode, but I need to get some sleep first.)


End of article. This article has undergone ChatGPT's stylistic optimization. However, anyone who has used GPT can tell it's not but is flying everywhere.