The Art of Delaying Image Content

How to balance image deferral techniques in Angular for a smooth user experience

Based on this article’s title, let’s start with a question: Why could deferring image loading in a web browser also be considered an art rather than just a pure programming practice?

Optimizing web performance through carefully examining image content to be loaded requires a fine-tuned balance between technical constraints and user experience. Lazy-loading decisions usually go beyond straightforward HTML, CSS, and JavaScript implementation. It requires judgment calls based on understanding content relevance, viewport placement, and expected user interaction, among other possible factors.

Delaying images is not just about speeding up load times but optimizing how that improvement feels to the user. Decisions about when to replace, for instance, high-resolution images with placeholders or thumbnail versions, are informed by more than just programming skills. They require combining technical knowledge with visual design sensibilities and understanding user expectations.

Lazy loading images

Before the loading HTML attribute became standardized and adopted in 2019 by the major browsers, front-end developers had to rely on JavaScript to defer the loading of non-critical images.

A common technique was to initially load low-resolution placeholder images, or even empty <div> elements or CSS pseudo-elements styled with a background color. Scroll events and element positions in the browser viewport were used to determine when the higher-resolution images should be loaded. This approach was practical but required time-consuming tuning to avoid performance degradation that could beat the purpose of lazy loading. Throttling or debouncing the scroll event listener to avoid running the callback function too frequently or relying on the requestAnimationFrame() method to ensure our code didn’t run faster than the optimal FPS value of 60hz was much needed to provide a decent experience.

Gladly, by the time Chrome v77 released the much anticipated native lazy loading feature, other browsers like Opera, Edge — and months later, Firefox and Safari — jumped aboard. The new method was much simpler and let developers rely on browser optimization to proceed with the image deferral process. There was no need for additional JavaScript to implement the lazy loading functionality.

<img src=”deferred-image.jpg” loading=”lazy” alt=”Image description”>

But we still need to decide which images to mark for lazy loading, and determine the explicit values of their width and height properties based on their intrinsic dimensions to assist the browser in calculating the layout before receiving the image content to prevent layout shifts that negatively impact the CLS vital.

The decoding HTML attribute offers another layer of control. By setting the value to “async”, we can instruct the browser to decode and render an image after processing other critical resources, further reducing the occurrence of possible layout shifts when images are dynamically added.

<img src=”deferred-image.jpg” loading=”lazy” decoding=”async” alt=”Image description”>

Adjusting these attributes consciously guarantees that lazy loading is not just deferring off-screen images using native browser capabilities but part of a comprehensive strategy for performance optimization in alignment with UX and product needs.

Viewport conditions

We can’t afford to ignore viewport conditions when approaching a strategy to delay image rendering, as they’re vital in determining which images get loaded and when under which circumstances.

The srcset attribute — in combination with the sizes attribute — gives us control over this to allow the browsers to choose the most appropriate image file based on the user’s device conditions, like screen size and pixel density.

<img srcset=”image-480w.jpg 480w,
image-960w.jpg 960w,
image-1200w.jpg 1200w,
image-480w@2x.jpg 480w 2x,
image-960w@2x.jpg 960w 2x,
image-1200w@2x.jpg 1200w 2x”
sizes=”(max-width: 480px) 480px,
(max-width: 960px) 960px,
1200px”
src=”image-1200w.jpg” alt=”Image description”>

This minimizes the amount of unnecessary data that needs to be downloaded and ensures that only the most pertinent and optimized images are loaded as the user scrolls through the page.

The <picture> HTML element offers even more granularity, letting us set which image file should be used based on media queries. This approach is very valuable for complying with visual design requirements that may dictate different images for different screen sizes, orientations, or color schemes.

<picture>
<!– WebP images for dark mode and landscape orientation in small screens –>
<source media=”(max-width: 480px) and (orientation: landscape) and (prefers-color-scheme: dark)”
srcset=”image-480w-dark-landscape.webp 480w,
image-480w-dark-landscape@2x.webp 480w 2x”
type=”image/webp”>

<!– WebP images for light mode and landscape orientation in small screens –>
<source media=”(max-width: 480px) and (orientation: landscape) and (prefers-color-scheme: light)”
srcset=”image-480w-light-landscape.webp 480w,
image-480w-light-landscape@2x.webp 480w 2x”
type=”image/webp”>

<!– WebP images for other conditions –>
<source srcset=”image-480w.webp 480w,
image-1200w.webp 1200w,
image-480w@2x.webp 480w 2x,
image-1200w@2x.webp 1200w 2x”
sizes=”(max-width: 480px) 480px, 1200px”
type=”image/webp”>

<!– JPEG images for dark mode and landscape orientation in small screens –>
<source media=”(max-width: 480px) and (orientation: landscape) and (prefers-color-scheme: dark)”
srcset=”image-480w-dark-landscape.jpg 480w,
image-480w-dark-landscape@2x.jpg 480w 2x”
type=”image/jpeg”>

<!– JPEG images for light mode and landscape orientation in small screens –>
<source media=”(max-width: 480px) and (orientation: landscape) and (prefers-color-scheme: light)”
srcset=”image-480w-light-landscape.jpg 480w,
image-480w-light-landscape@2x.jpg 480w 2x”
type=”image/jpeg”>

<!– JPEG images for other conditions –>
<source srcset=”image-480w.jpg 480w,
image-1200w.jpg 1200w,
image-480w@2x.jpg 480w 2x,
image-1200w@2x.jpg 1200w 2x”
sizes=”(max-width: 480px) 480px, 1200px”
type=”image/jpeg”>

<!– fallback image –>
<img src=”image-1200w.jpg” alt=”Image description”>
</picture>

Modern file formats like WebP and AVIF offer superior compression and quality compared to traditional JPEG or PNG images. The <picture> element also allows the users’ browser to select the most suitable and supported format for rendering the images for them.

At some point in the near future, AI-powered browsers, crawlers, and screen reader engines may reach a level where providing alternative text may become unnecessary — in most cases. These trained machines could translate image content into readable text for users who may need it, but if a machine hasn’t yet accessed the image file, the alt attribute value may still be necessary in advance in the HTML declaration.

This attribute is still crucial for making our images — the ones relevant to the page content, not just for aesthetic purposes — accessible to users who don’t consume them visually, and it also serves as a fallback when images can’t be loaded or are intentionally deferred for later rendering. Providing meaningful alternative text supports a more inclusive and effective image-deferring strategy.

Angular’s Optimized Images

The standalone NgOptimizedImage directive, stable in the Angular framework since version 15, has been instrumental in spreading best image rendering practices, regardless of the developer’s interest in visual performance tuning.

This built-in Angular directive helps not only with providing the necessary hints to prioritize critical images but also with deferring the processing of other images. It sets the lazy mode for non-priority images unless we set the loading attribute to a different value. This directive even offers CDN-tailored support through the ImageLoaderConfig provider, automatic srcset attribute values based on available image sizes, and console warnings on aspect ratio distortion and oversized images based on the container dimension.

Rendering Images the Angular Way

Serving images through CDNs is also relevant in loading non-critical images. These servers can store the image files closer to the user’s geographical location, significantly reducing network latency and improving the lazy loading results once the user reaches the scrolling point or other interactive conditions are met.

Conditionally deferred blocks

The upcoming stable release of Angular 17 will bring @defer blocks, which can be declared inside component templates to enable “lazy” content, not just for images or routes. In addition to the loading attribute in HTML images, we will be able to manipulate — during the Developer Preview of this feature — entire markup segments.

@Component({
selector: ‘container-component’,
standalone: true,
template: `
@defer (when isFeatureVisible) {
<sub-component />
}
`
})
class ContainerComponent { … }

The defer syntax goes beyond boolean checks. We can delay component rendering based on a variety of conditions, such as a specific wait time, browser idle status, or event triggers. For instance, rendering deferral could happen until a mouse hover or keyboard focus occurs on an element identified with a template reference or until that element enters the viewport.

@Component({
selector: ‘container-component’,
standalone: true,
template: `
@defer (on viewport(element)) {
<sub-component />
}

<section #element style=”margin-top: 100vh”>Content below the fold</section>
`
})
class ContainerComponent { … }

The @loading and @placeholder blocks offer more flexibility in what to display during the dependency resolution and while the content is rendering, respectively. We can use them to display spinners, progress indicators, or visual skeletons depending on technical constraints and art direction.

@Component({
selector: ‘container-component’,
standalone: true,
template: `
@defer (on idle) {
<sub-component />
}
@loading {
<p>Loading indicator…</p>
} @placeholder {
<div>Placeholder</div>
} @error {
<p>Corrective actions:</p>
}
`
})
class ContainerComponent { … }

The @error block handles those cases where the deferred content fails to load, like when there’s an issue loading dependencies. This feature provides a fallback mechanism, allowing us to inform users in-line about the problem and guide them on how to proceed, such as triggering a content reload or checking their internet connection.

Outro

It’s exciting to see how integrating deferred blocks with optimized, lazy-loaded images will continue improving page speed and user experience. This is a promising advancement in the Angular framework for developers who aim to serve pixels at lightning speed to capture users’ flying attention.

Reproducible algorithms can’t entirely define the process of engineering an optimal user experience. The art of delaying web content literally transcends coding techniques to become a strategy that enhances web performance based on the understanding of design principles, human psychology, and the purpose of the content presented to our users.

Did anyone mention cross-discipline collaboration for making informed decisions about image deferral?

La versión en español de este artículo también se encuentra disponible en el blog de ng-conf:

El arte de diferir imágenes

The Art of Delaying Image Content was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *