iTranslated by AI
Fixing background-image display lag with (nearly) CSS-only CSS lazy loading
This may not necessarily be the best practice, but I'll introduce a method that can be easily implemented even on plain HTML/CSS sites.
The Issue of background-image Not Displaying Momentarily
CSS background-image properties are not loaded until the image needs to be displayed. For example, if you change a background-image in response to a user action, the new image is loaded only after that action occurs. (This loading happens only the first time, so it won't occur from the second time onwards.)
A more specific example of this phenomenon is when clicking on custom-designed radio buttons or checkboxes; they might display with a slight delay only the first time. Similar display lags occur when showing elements that were previously hidden with display: none.
▼ Example of the display being slow only the first time (network speed has been throttled for clarity)

To resolve this lag, it's necessary to preload the images. A straightforward fix would be to load those images when the page loads, but in today's performance-oriented web, we want to avoid loading images that aren't immediately used during initial page load.
Another technique is to use rel="preload" to load images.
<link rel="preload" href="hoge.png" as="image">
Using this will preload the specified images. While this works fine for a small number of images, for larger sites, the number of background images can grow quite large, making it a bit inconvenient to list them one by one[1].
I will introduce a simple method to solve this momentary non-display issue using (almost[2]) only CSS.
Lazy Loading background-image
To solve this, we will lazy load the CSS background images. While HTML <img> tags have a loading attribute where you can easily implement lazy loading by specifying loading="lazy", no such thing exists for CSS.
Therefore, we will create a CSS file for the images we want to lazy load as follows.
▼preload.css
html:after {
content: "";
background-image: url("https://picsum.photos/id/120/1200/900"),
url("./images/radio_on_preload.png"),
...
}
Since the background-image property allows specifying multiple images, you can list as many images as you want to lazy load. This CSS sets background images on a pseudo-element of the <html> tag; since it has no size, nothing is actually displayed. However, unlike display: none or unused background images, the images themselves will be loaded once this CSS is applied.
Next, configure the CSS loading within the <head> tag as follows.
<link
rel="stylesheet"
href="./preload.css"
media="print"
onload="this.media='all'"
/>
media="print" and onload="this.media='all'" might look unfamiliar, but this is a technique to avoid CSS rendering blocks.
During the initial load, it is treated as media="print" (CSS for printing), so it is ignored and doesn't block rendering. Since there is an onload event via inline JavaScript, this.media='all' is executed once the entire page has finished loading. In other words, the media type changes from media="print" to media="all" upon completion, at which point preload.css is loaded.
Once preload.css is loaded, the CSS is applied to the HTML pseudo-element we set up earlier, and the images are loaded. Now that the images are loaded, they will be displayed without any lag during dynamic changes.
Summary
I have introduced a method to resolve display lag by lazy loading CSS files using media="print" and this.media='all'. As a side note, this technique can be used for general lazy loading of CSS files, such as for lazy loading Web fonts[3]. Even though it's only a momentary flicker at the very beginning, paying attention to these details will lead to a site with a better user experience.
-
Static site generators or frameworks might solve this, but that is out of scope for this article. ↩︎
-
It includes a tiny amount of inline JavaScript, so it's not purely CSS. ↩︎
-
See The Fastest Google Fonts – CSS Wizardry – Web Performance Optimisation ↩︎
Discussion