iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🖼️

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.)

Sample of delayed display

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.

脚注
  1. Static site generators or frameworks might solve this, but that is out of scope for this article. ↩︎

  2. It includes a tiny amount of inline JavaScript, so it's not purely CSS. ↩︎

  3. See The Fastest Google Fonts – CSS Wizardry – Web Performance Optimisation ↩︎

Discussion