Clouds in the evening.

AVIF Preview Image Example

By Jim (jimbo2150) • Published

A common feature of web image formats is to display something - anything - while a large image is being downloaded. This progressive rendering allows the user to see that an image is being loaded and potentially start understanding the contents of the image. However, AVIF, being a video format, does not come with this feature.

Below is an example of JPEG using top-down progressive rendering to display the image as it loads, line-by-line, from the top to the bottom. WebP uses this rendering technique as well. This allows the user to “peek” the image from the top as it’s downloading.

Below is an example of JPEG using interlaced progressive rendering to show a very low-resolution version of the image. As the browser loads more the image, it “steps up” the resolution progressively. This allows the user to get an idea of what the complete image contains while it's still downloading.

What About AVIF?

AVIF developers debated including something similar to what I describe below, however, they tabled the idea. Including a preview image in the header would add complexity with little gain. Adding full progressive rendering this late would require a large change to the code & specification.

Developed for low-bitrate situations, they expect images should load quickly. However, it’s not being properly promoted for its expected use case. I will add a more detailed article covering this in the future.

AVIF Preview Image Example

This example shows how an AVIF image can display a lower quality preview while the larger image is loading. CSS restrictions require the use of a parent <picture> tag. If anyone knows a way to fold this into a single <img> tag, preferably without JavaScript, please let me know.

It uses no JavaScript for the actual image or loading portion. The script is only to add a more appealing transition effect that occurs when the large image has completed downloading. Browsers without JS will still load and display the image, but the large image will pop in instantly overtop of the inferior quality version with no transition animation.

Other image formats (WebP/WebP2, HEIF, GIF, PNG, etc) can also use this technique, but probably wouldn't be necessary for types that support progressive encoding/rendering (such as JPEG and JPEG XL) as they will provide a similar effect without the use of an extra image or code.

Clouds in the evening.

Lets take a look at the HTML structure...

<picture id="avif-cats" class="block" style="background-image: url('/path-to/preview-image.avif');">
    <img class="block" width="4032" height="3024" style="display:none;" src="/path/to/preview-image.avif" decoding="async">
    <img class="block" width="4032" height="3024" onload="this.imageIsLoaded();" src="/path/to/large-image.avif" decoding="async">
</picture>

Wait... why are there 2 img tags? If you omit this first tag some browsers will try to load the large image first which can block loading of the preview image until it is done. Yikes! That is not what we would want. The first one is linking to the same preview image that is set as the background of the picture tag. Note the display is also none making it invisible. This is done specifically so that it will prioritize it above the larger image when loading resources.

Now let's take a look at the code (it's recommended you run the code after the DOM has finished loading [DOMContentLoaded event]):

// This part can be anywhere inside or outside your event listener.
HTMLImageElement.prototype.imageIsLoaded = function() {
    this.parentElement.style.filter='';
    this.style.opacity=1;
}

(() => {
    let afterLoaded = function() {
        let isLoaded = false;
        let picElemCol = document.getElementsByTagName('picture');
        let imgElemCol = false;
        for(let picElem of picElemCol) {
            imgElemCol = picElem.getElementsByTagName('img');
            for(let imgElem of imgElemCol) {
                isLoaded = imgElem.complete && imgElem.naturalHeight !== 0;
                if(!isLoaded) {
                    picElem.style.filter = "blur(3px)";
                    imgElem.style.opacity = 0;
                }
            }
        }
    };

    if(/complete|interactive|loaded/.test(document.readyState)) {
        afterLoaded();
    } else {
        document.addEventListener('DOMContentLoaded', afterLoaded, {"once": true});
    }
})();

The script is only used to blur the preview image until the large image has loaded, providing a more pleasing and interlaced JPEG-like appearance. Feel free to add a CSS animation to it, if you like.

Categories: web-development js