lazy loading – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 13 Feb 2023 15:10:44 +0000 en-US hourly 1 https://wordpress.org/?v=6.2.2 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 lazy loading – CSS-Tricks https://css-tricks.com 32 32 45537868 An Approach to Lazy Loading Custom Elements https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/ https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/#comments Mon, 13 Feb 2023 15:10:41 +0000 https://css-tricks.com/?p=376991 We’re fans of Custom Elements around here. Their design makes them particularly amenable to lazy loading, which can be a boon for performance.

Inspired by a colleague’s experiments, I recently set about writing a simple auto-loader: Whenever a custom …


An Approach to Lazy Loading Custom Elements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We’re fans of Custom Elements around here. Their design makes them particularly amenable to lazy loading, which can be a boon for performance.

Inspired by a colleague’s experiments, I recently set about writing a simple auto-loader: Whenever a custom element appears in the DOM, we wanna load the corresponding implementation if it’s not available yet. The browser then takes care of upgrading such elements from there on out.

Chances are you won’t actually need all this; there’s usually a simpler approach. Used deliberately, the techniques shown here might still be a useful addition to your toolset.

For consistency, we want our auto-loader to be a custom element as well — which also means we can easily configure it via HTML. But first, let’s identify those unresolved custom elements, step by step:

class AutoLoader extends HTMLElement {
  connectedCallback() {
    let scope = this.parentNode;
    this.discover(scope);
  }
}
customElements.define("ce-autoloader", AutoLoader);

Assuming we’ve loaded this module up-front (using async is ideal), we can drop a <ce-autoloader> element into the <body> of our document. That will immediately start the discovery process for all child elements of <body>, which now constitutes our root element. We could limit discovery to a subtree of our document by adding <ce-autoloader> to the respective container element instead — indeed, we might even have multiple instances for different subtrees.

Of course, we still have to implement that discover method (as part of the AutoLoader class above):

discover(scope) {
  let candidates = [scope, ...scope.querySelectorAll("*")];
  for(let el of candidates) {
    let tag = el.localName;
    if(tag.includes("-") && !customElements.get(tag)) {
      this.load(tag);
    }
  }
}

Here we check our root element along with every single descendant (*). If it’s a custom element — as indicated by hyphenated tags — but not yet upgraded, we’ll attempt to load the corresponding definition. Querying the DOM that way might be expensive, so we should be a little careful. We can alleviate load on the main thread by deferring this work:

connectedCallback() {
  let scope = this.parentNode;
  requestIdleCallback(() => {
    this.discover(scope);
  });
}

requestIdleCallback is not universally supported yet, but we can use requestAnimationFrame as a fallback:

let defer = window.requestIdleCallback || requestAnimationFrame;

class AutoLoader extends HTMLElement {
  connectedCallback() {
    let scope = this.parentNode;
    defer(() => {
      this.discover(scope);
    });
  }
  // ...
}

Now we can move on to implementing the missing load method to dynamically inject a <script> element:

load(tag) {
  let el = document.createElement("script");
  let res = new Promise((resolve, reject) => {
    el.addEventListener("load", ev => {
      resolve(null);
    });
    el.addEventListener("error", ev => {
      reject(new Error("failed to locate custom-element definition"));
    });
  });
  el.src = this.elementURL(tag);
  document.head.appendChild(el);
  return res;
}

elementURL(tag) {
  return `${this.rootDir}/${tag}.js`;
}

Note the hard-coded convention in elementURL. The src attribute’s URL assumes there’s a directory where all custom element definitions reside (e.g. <my-widget>/components/my-widget.js). We could come up with more elaborate strategies, but this is good enough for our purposes. Relegating this URL to a separate method allows for project-specific subclassing when needed:

class FancyLoader extends AutoLoader {
  elementURL(tag) {
    // fancy logic
  }
}

Either way, note that we’re relying on this.rootDir. This is where the aforementioned configurability comes in. Let’s add a corresponding getter:

get rootDir() {
  let uri = this.getAttribute("root-dir");
  if(!uri) {
    throw new Error("cannot auto-load custom elements: missing `root-dir`");
  }
  if(uri.endsWith("/")) { // remove trailing slash
    return uri.substring(0, uri.length - 1);
  }
  return uri;
}

You might be thinking of observedAttributes now, but that doesn’t really make things easier. Plus updating root-dir at runtime seems like something we’re never going to need.

Now we can — and must — configure our elements directory: <ce-autoloader root-dir="/components">.

With this, our auto-loader can do its job. Except it only works once, for elements that already exist when the auto-loader is initialized. We’ll probably want to account for dynamically added elements as well. That’s where MutationObserver comes into play:

connectedCallback() {
  let scope = this.parentNode;
  defer(() => {
    this.discover(scope);
  });
  let observer = this._observer = new MutationObserver(mutations => {
    for(let { addedNodes } of mutations) {
      for(let node of addedNodes) {
        defer(() => {
          this.discover(node);
        });
      }
    }
  });
  observer.observe(scope, { subtree: true, childList: true });
}

disconnectedCallback() {
  this._observer.disconnect();
}

This way, the browser notifies us whenever a new element appears in the DOM — or rather, our respective subtree — which we then use to restart the discovery process. (You might argue we’re re-inventing custom elements here, and you’d be kind of correct.)

Our auto-loader is now fully functional. Future enhancements might look into potential race conditions and investigate optimizations. But chances are this is good enough for most scenarios. Let me know in the comments if you have a different approach and we can compare notes!


An Approach to Lazy Loading Custom Elements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/feed/ 4 376991
How to Show Images on Click https://css-tricks.com/how-to-show-images-on-click/ https://css-tricks.com/how-to-show-images-on-click/#comments Thu, 27 May 2021 14:25:29 +0000 https://css-tricks.com/?p=341020 Most images on the web are superfluous. If I might be a jerk for a bit, 99% of them aren’t even that helpful at all (although there are rare exceptions). That’s because images don’t often complement the text they’re …


How to Show Images on Click originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Most images on the web are superfluous. If I might be a jerk for a bit, 99% of them aren’t even that helpful at all (although there are rare exceptions). That’s because images don’t often complement the text they’re supposed to support and instead hurt users, taking forever to load and blowing up data caps like some sort of performance tax.

Thankfully, this is mostly a design problem today because making images performant and more user-friendly is so much easier than it once was. We have better image formats like WebP (and soon, perhaps, JPEG XL). We have the magic of responsive images of course. And there are tons of great tools out there, like ImageOptim, as well as resources such as Addy Osmani’s new book.

Although perhaps my favorite way to improve image performance is with lazy loading:

<img href="image.webp" alt="Image description" loading="lazy">

This image will only load when a user scrolls down the page so it’s visible to the user — which removes it from the initial page load and that’s just great! Making that initial load of a webpage lightning fast is a big deal.

But maybe there are images that should never load at all. Perhaps there are situations where it’d be better if a person could opt-into seeing it. Here’s one example: take the text-only version of NPR and click around for a bit. Isn’t it… just so great?! It’s readable! There’s no junk all over the place, it respects me as a user and — sweet heavens — is it fast.

Did I just show you an image in a blog post that insults the very concept of images? Yep! Sue me.

So! What if we could show images on a website but only once they are clicked or tapped? Wouldn’t it be neat if we could show a placeholder and swap it out for the real image on click? Something like this:

Well, I had two thoughts here as to how to build this chap (the golden rule is that there’s never one way to build anything on the web).

Method #1: Using <img> without a src attribute

We can remove the src attribute of an <img> tag to hide an image. We could then put the image file in an attribute, like data-src or something, just like this:

<img data-src="image.jpg" src="" alt="Photograph of hot air balloons by Musab Al Rawahi. 144kb">

By default, most browsers will show a broken image icon that you’re probably familiar with:

Okay, so it’s sort of accessible. I guess? You can see the alt tag rendered on screen automatically, but with a light dash of JavaScript, we can then swap out the src with that attribute:

document.querySelectorAll("img").forEach((item) => {
  item.addEventListener("click", (event) => {
    const image = event.target.getAttribute("data-src");
    event.target.setAttribute("src", image);
  });
});

Now we can add some styles and ugh, oh no:

Ugh. In some browsers there’ll be a tiny broken image icon in the bottom when the image hasn’t loaded. The problem here is that browsers don’t give you a way to remove the broken image icon with CSS (and we probably shouldn’t be allowed to anyway). It’s a bit annoying to style the alt text, too. But if we remove the alt attribute altogether, then the broken image icon is gone, although this makes the <img> unusable without JavaScript. So removing that alt text is a no-go.

As I said: Ugh. I don’t think there’s a way to make this method work (although please prove me wrong!).

The other option we have is to start with the humble hyperlink, like this:

<a href="image.jpg">Photograph of hot air balloons by Musab Al Rawahi. 144kb<a>

Which, yep, nothing smart happening yet — this will just render a link to an image:

That works accessibility-wise, right? If we don’t have any JavaScript, then all we have is just a link that folks can optionally click. Performance-wise, it can’t get much faster than plain text!

But from here, we can reach for JavaScript to stop the link from loading on click, grab the href attribute within that link, create an image element and, finally, throw that image on the page and remove the old link once we’re done:

document.querySelectorAll(".load-image").forEach((item) => {
  item.addEventListener("click", (event) => {
    const href = event.target.getAttribute("href");
    const newImage = document.createElement("img");
    event.preventDefault();
    newImage.setAttribute("src", href);
    document.body.insertBefore(newImage, event.target);
    event.target.remove();
  });
});

We could probably style this placeholder link to make it look a bit nicer than what I have below. But this is just an example. Go ahead and click the link to load the image again:

And there you have it! It isn’t groundbreaking or anything, and I’m sure someone’s done this before at some point or another. But if we wanted to be extremely radical about performance beyond the first paint and initial load, then I think this is an okay-ish solution. If we’re making a text-only website then I think this is definitely the way to go.

Perhaps we could also make a web component out of this, or even detect if someone has prefers-reduced-data and then only load images if someone has enough data or something. What do you think?


How to Show Images on Click originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-show-images-on-click/feed/ 11 341020
A Bare-Bones Approach to Versatile and Reusable Skeleton Loaders https://css-tricks.com/a-bare-bones-approach-to-versatile-and-reusable-skeleton-loaders/ https://css-tricks.com/a-bare-bones-approach-to-versatile-and-reusable-skeleton-loaders/#comments Wed, 03 Mar 2021 15:51:32 +0000 https://css-tricks.com/?p=335002 UI components like spinners and skeleton loaders make waiting for a page load less frustrating and might even affect how loading times are perceived when used correctly. They won’t completely prevent users from abandoning the website, but they might encourage …


A Bare-Bones Approach to Versatile and Reusable Skeleton Loaders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
UI components like spinners and skeleton loaders make waiting for a page load less frustrating and might even affect how loading times are perceived when used correctly. They won’t completely prevent users from abandoning the website, but they might encourage them to wait a bit longer. Animated spinners are used in most cases since they are easy to implement and they generally do a good enough job. Skeleton loaders have a limited use-case and might be complex to implement and maintain, but they offer an improved loading experience for those specific use-cases.

I’ve noticed that developers are either unsure when to use skeleton loaders to enhance the UX or do not know how to approach the implementation. More common examples of skeleton loaders around the web are not all that reusable or scalable. They are usually tailor-made for a single component and cannot be applied to anything else. That is one of the reasons developers use regular spinners instead and avoid the potential overhead in the code. Surely, there must be a way to implement skeleton loaders in a more simple, reusable, and scalable way.

Spinner elements and skeleton loaders

A spinner (or progress bar) element is the simplest and probably most commonly used element to indicate a loading state. A spinner might look better than a blank page, but it won’t hold user’s attention for long. Spinners tell the user that something will load eventually. Users have to passively wait for content to load, meaning that they are unable to interact with other elements on the page or consume any other content on the page. Spinners take up the entire screen space and no content is available to the user.

The spinner element is displayed and covers the entire screen until all content has finished loading.

However, skeleton loaders (or skeleton screens) tell the user that the content is about to load and they might provide a better loading UX than a simple spinner. Empty boxes (with a solid color or gradient background) are used as a placeholder for the content that is being loaded. In most cases, content is gradually being loaded which allows users to maintain a sense of progress and perception that a page load is faster than it is. Users are actively waiting, meaning that they can interact with the page or consume at least some part of the content while the rest is loading.

Empty boxes (with a solid color or gradient background) are used as a placeholder while content is being gradually loaded. Text content is loaded and displayed first, and images are loaded and displayed after that.

It’s important to note that loading components should not be used to address performance issues. If a website is experiencing performance issues due to the problem that can be addressed (un-optimized assets or code, back-end performance issues, etc.), they should be fixed first. Loading elements won’t prevent users from abandoning websites with poor performance and high loading times. Loading elements should be used as a last resort when waiting is unavoidable and when loading delay is not caused by unaddressed performance issues.

Using skeleton loaders properly

Skeleton loaders shouldn’t be treated as a replacement for full-screen loading elements but instead when specific conditions for content and layout have been met. Let’s take this step-by-step and see how to use loading UI components effectively and how to know when to go with skeleton loaders instead of regular spinners.

Is loading delay avoidable?

The best way to approach loading in terms of UX is to avoid it altogether. We need to make sure that loading delay is unavoidable and is not the result of the aforementioned performance issues that can be fixed. The main priority should always be performance improvements and reducing the time needed to fetch and display the content.

Is loading initiated by the user and is the feedback required?

In some cases, user actions might initiate additional content to load. Some examples include lazy-loading content (e.g. images) in the user’s viewport while scrolling, loading content on a button click, etc. We need to include a loading element for cases where a user needs to get some kind of feedback for their actions that have initiated the loading process.

As seen in the following mockup, without a loading element to provide feedback, a user doesn’t know that their actions have initiated any loading process that is happening in the background.

We are asynchronously loading the content in a modal when the button is clicked. In the first example, no loading element is displayed and users might think that their click hasn’t been registered. In the second example, users get the feedback that their click has been registered and that the content is being loaded.

Is the layout consistent and predictable?

If we’ve decided to go with a loader element, we now need to choose what type of loader element best fits our use-case. Skeleton loaders are most effective in cases when we can predict the type and layout of the content that is being loaded in. If the skeleton loader layout doesn’t accurately represent the loaded content’s layout to some degree, the sudden change may cause layout shift and leave the user confused and disoriented. Use skeleton loaders for elements with predictable content for consistent layouts.

The grid layout on the left (taken from discogs.com) represents an ideal use-case for skeleton loaders, while the comments example on the right (taken from CSS-Tricks) is an ideal use-case for spinners.

Is there content on the page that is immediately available to the user?

Skeleton loaders are most effective when there are sections or page elements already present on the page while the skeleton loader is active and additional content is actively loading. Gradually loading in content means that static content is available on page load and asynchronously-loaded content is displayed as it becomes available (for example, the first text is loaded and images after that). This approach ensures that the user maintains a sense of progression and is expecting the content to finish loading at any moment. Having the entire screen covered in skeleton loaders without any content present and without gradual content loading is not significantly better than having the screen covered by a full-page spinner or progress bar.

The mockup on the left shows a skeleton loader covering all elements until everything has loaded. The mockup on the right shows a skeleton loader covering only content that is being asynchronously loaded. The page is usable since they have a part of the website’s content displayed and the user maintains a sense of progression.

Creating robust skeleton loaders

Now that we know when to use skeleton loaders and how to use them properly, we can finally do some coding! But first, let me tell you how we are going to approach this.

Most skeleton loading examples from around the web are, in my opinion, over-engineered and high-maintenance. You might have seen one of those examples where skeleton screens are created as a separate UI component with separate CSS styles or created with elaborate use of CSS gradients to simulate the final layout. Creating and maintaining a separate skeleton loader or skeleton styles for each UI component can become serious overhead in development with such a highly-specific approach. This is especially true when we look at scalability, as any change to the existing layout also involves updating the skeleton layout or styles.

Let’s try and find a bare-bones approach to implementing skeleton loading that should work for most use-cases and will be easy to implement, reuse and maintain!

Card grid component

We’ll use regular HTML, CSS, and JavaScript for implementation, but the overall approach can be adapted to work with most tech stacks and frameworks.

We are going to create a simple grid of six card elements (three in each row) as an example, and simulate asynchronous content loading with a button click.

We’ll use the following markup for each card. Notice that we are setting width and height on our images and using a 1px transparent image as a placeholder. This will ensure that the image skeleton loader is visible until the image has been loaded.

<div class="card">
  <img width="200" height="200" class="card-image" src="..." />
  <h3 class="card-title"></h3>
  <p class="card-description"></p>
  <button class="card-button">Card button</button>
</div>

Here is our card grid example with some layout and presentation styles applied to it. Content nodes are added or removed from the DOM depending on the loading state using JavaScript to simulate asynchronous loading.

Skeleton loader styles

Developers usually implement skeleton loaders by creating replacement skeleton components (with dedicated skeleton CSS classes) or by recreating entire layouts with CSS gradients. Those approaches are inflexible and not reusable at all since individual skeleton loaders are tailor-made for each layout. Considering that layout styles (spacings, grid, inline, block and flex elements, etc.) are already present from the main component (card) styles, skeleton loaders just need to replace the content, not the entire component!

With that in mind, let’s create skeleton loader styles that become active only when a parent class is set and use CSS properties that only affect the presentation and content. Notice that these styles are independent from the layout and content of the element they’re being applied to, which should make them highly reusable.

.loading .loading-item {
  background: #949494 !important; /* Customizable skeleton loader color */
  color: rgba(0, 0, 0, 0) !important;
  border-color: rgba(0, 0, 0, 0) !important;
  user-select: none;
  cursor: wait;
}

.loading .loading-item * {
  visibility: hidden !important;
}

.loading .loading-item:empty::after,
.loading .loading-item *:empty::after {
  content: "\00a0";
}

Base parent class .loading is used to activate the skeleton loading styles. The .loading-item class is used to override element’s presentational styles to display a skeleton element. This also ensures that the layout and dimensions of the element are preserved and inherited by the skeleton. Additionally, .loading-item makes sure that all child elements are hidden and have at least an empty space character (\00a0) inside it so that element is displayed and its layout is rendered.

Let’s add skeleton loader CSS classes to our markup. Notice how no additional HTML elements have been added, we are only applying additional CSS classes.

<div class="card loading">
  <img width="200" height="200" class="card-image loading-item" src="..." />
  <h3 class="card-title loading-item"></h3>
  <p class="card-description loading-item"></p>
  <button class="card-button loading-item">Card button</button>
</div>

Once the content has loaded, we only need to remove loading CSS class from the parent component to hide the skeleton loader styles.

These few lines should work for most, if not all, use cases depending on your custom CSS since these skeleton loaders inherit the layout from the main (content) styles and create a solid box that replaces the content by filling out the empty space left in the layout. We’re also applying these classes to non-empty elements (button with text) and replacing it with a skeleton. A button might have the text content ready from the start, but it might be missing additional data that is required for it to function correctly, so we should also hide it while that data is loaded in.

This approach can also adapt to most changes in the layout and markup. For example, if we were to remove the description part of the card or decide to move the title above the image, we wouldn’t need to make any changes to the skeleton styles, since skeleton responds to all changes in the markup.

Additional skeleton loading override styles can be applied to a specific element simply by using the .loading .target-element selector.

.loading .button,
.loading .link {
  pointer-events: none;
}

Multi-line content and layout shifts

As you can see, the previous example works great with cards and the grid layout we’re using, but notice that the page content slightly jumps the moment it is loaded. This is called a layout shift. Our .card-description component has a fixed height with three lines of text, but the skeleton placeholder spans only one line of text. When the extra content is loaded, the container dimensions change and the overall layout is shifted as a result. Layout shift is not bad in this particular case, but might confuse and disorient the user in more severe cases.

This can be easily fixed directly in the placeholder element. Placeholder content is going to get replaced by the content that is being loaded anyway, so we can add anything we need inside it. So, let’s add a few <br /> elements to simulate multiple lines of text.

<div class="card loading">
  <img width="200" height="200" class="card-image loading-item" src="..." />
  <h3 class="card-title loading-item"></h3>
  <p class="card-description loading-item"><br/><br/><br/></p>
  <button class="card-button loading-item">Card button</button>
</div>

We’re using basic HTML to shape the skeleton and change the number of lines inside it. Other examples on the web might achieve this using CSS padding or some other way, but this introduces overhead in the code. After all, content can span any number of lines and we would want to cover all those cases.

As an added benefit of using <br /> elements, they inherit the CSS properties that affect the content dimensions (e.g. the line height, font size, etc.). Similarly, &nbsp characters can be used to add additional spacing to the inline placeholder elements.

With a few lines of CSS, we’ve managed to create versatile and extensible skeleton loader styles that can be applied to a wide range of UI components. We’ve also managed to come up with a simple way of vertically extending the skeleton boxes to simulate content that spans multiple lines of text.

To further showcase how versatile this skeleton loader CSS snippet is, I’ve created a simple example where I’ve added the snippet to a page using Bootstrap CSS framework without any additional changes or overrides. Please note that in this example no text content will be displayed or simulated, but it will work as in previous examples. This is just to showcase how styles can be easily integrated with other CSS systems.

Here is an additional example to showcase how these styles can be applied to various elements, including input, label and a elements.

Accessibility requirements

We should also take accessibility (a11y) requirements into account and make sure that the content is accessible to all users. Skeleton loaders without a11y features might disorientate and confuse users that have visual disabilities or browse the web using screen readers.

Contrast

You might have noticed that the skeleton loaders in our example have a high contrast and they look more prominent compared to the common low-contrast skeleton loaders in the wild. Some users might experience difficulties perceiving and using low-contrast UI components. That is why Web Content Accessibility Guidelines (WCAG) specify a 3:1 minimum contrast for non-text UI components.

The upcoming “Media queries level 5” draft contains a prefers-contrast media query that will enable us to detect user contrast preferences. This will give us more flexibility by allowing us to assign a high-contrast background color to skeleton loaders for users that request a high-contrast version, and have a subtle low-contrast background color for others. I would suggest implementing high-contrast skeleton loaders by default until the prefers-contrast media query becomes more widely supported.

/* NOTE: as of the time of writing this article, this feature is not supported in browsers, so this code won't work */

.loading .loading-item {
/* Default skeleton loader styles */
}

@media (prefers-contrast: high) {
  .loading .loading-item {
    /* High-contrast skeleton loader styles */
  }
}

Animations

Depending on the design and the implementation of animated skeleton loaders, users suffering from visual disorders might feel overwhelmed by the animations and find the site unusable. It’s always a good idea to prevent animations from firing for users that prefer reduced motion. This media query is widely-supported in modern browsers and can be used without any caveats.

.loading .loading-item {
  animation-name: skeleton;
  background: /* animated gradient background */;
}

@media (prefers-reduced-motion) {
  .loading .loading-item {
    animation: none !important;
    background: /* solid color */;
  }
}

Screen readers

To better support screen readers, we need to update our HTML with ARIA (Accessible Rich Internet Applications) markup. This markup won’t affect our content or presentation, but it will allow users using screen readers to better understand and navigate around our website content, including our skeleton loaders.

Adrian Roselli has very detailed research on the topic of accessible skeleton loaders for cases when skeleton loaders are implemented as separate UI components. For our example, I’ll use the aria-hidden attribute in combination with visually hidden text to give screen readers a hint that content is in the process of loading. Screen readers will ignore the content with aria-hidden="true", but they’ll use the visually-hidden element to indicate the loading state to the user.

Let’s update our cards with the ARIA markup and loading indicator element.

<div class="card loading">
  <span aria-hidden="false" class="visually-hidden loading-text">Loading... Please wait.</span>
  <img width="200" height="200" class="card-image loading-item" aria-hidden="true" src="..." />
  <h3 class="card-title loading-item" aria-hidden="true"></h3>
  <p class="card-description loading-item" aria-hidden="true"><br/><br/><br/></p>
  <button class="card-button loading-item" aria-hidden="true">Card button</button>
</div>

We also could have applied aria-hidden to the grid container element and add a single visually hidden element before the container markup, but I wanted to keep the markup examples focused on a single card element rather than on the full grid, so I went with this version.

When the content has finished loading and is displayed in the DOM, we need to toggle aria-hidden to false for content containers and toggle aria-hidden to true on a visually hidden loading text indicator.

Here’s the finished example

That’s a wrap

Implementing skeleton loaders requires a slightly different approach than implementing regular loading elements, like spinners. I’ve seen numerous examples around the web that implement skeleton loaders in a way that severely limits their reusability. These over-engineered solutions usually involve creating separate skeleton loader UI components with dedicated (narrow-scope) skeleton CSS markup or recreating the layout with CSS gradients and magic numbers. We’ve seen that only the content needs to be replaced with the skeleton loaders, and not the entire component.

We’ve managed to create simple, versatile, and reusable skeleton loaders that inherit the layout from the default styles and replace the content inside the empty containers with solid boxes. With just two CSS classes, these skeleton loaders can easily be added to virtually any HTML element and extended, if needed. We’ve also made sure that this solution is accessible and doesn’t bloat the markup with additional HTML elements or duplicated UI components.

Thank you for taking the time to read this article. Let me know your thoughts on this approach and let me know how did you approach creating skeleton loaders in your projects.


A Bare-Bones Approach to Versatile and Reusable Skeleton Loaders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-bare-bones-approach-to-versatile-and-reusable-skeleton-loaders/feed/ 4 335002
Native Image Lazy Loading in Chrome Is Way Too Eager https://css-tricks.com/native-image-lazy-loading-in-chrome-is-way-too-eager/ https://css-tricks.com/native-image-lazy-loading-in-chrome-is-way-too-eager/#comments Wed, 05 Feb 2020 21:19:30 +0000 https://css-tricks.com/?p=303044 Interesting research from Aaron Peters on <img loading="lazy" ... >:

On my 13 inch macbook, with Dock positioned on the left, the viewport height in Chrome is 786 pixels so images with loading="lazy" that are more than 4x the


Native Image Lazy Loading in Chrome Is Way Too Eager originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Interesting research from Aaron Peters on <img loading="lazy" ... >:

On my 13 inch macbook, with Dock positioned on the left, the viewport height in Chrome is 786 pixels so images with loading="lazy" that are more than 4x the viewport down the page are eagerly fetched by Chrome on page load.

In my opinion, that is waaaaay too eager. Why not use a lower threshold value like 1000 pixels? Or even better: base the threshold value on the actual viewport height.

My guess is they chose not to over-engineer the feature by default and will improve it over time. By choosing a fairly high threshold, they ran a lower risk of it annoying users with layout shifts on pages with images that don’t use width/height attributes.

I think this unmerged Pull Request is the closest thing we have to a spec and it uses language like “scrolled into the viewport” which suggests no threshold at all.

To Shared LinkPermalink on CSS-Tricks


Native Image Lazy Loading in Chrome Is Way Too Eager originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/native-image-lazy-loading-in-chrome-is-way-too-eager/feed/ 3 303044
Lazy load embedded YouTube videos https://css-tricks.com/lazy-load-embedded-youtube-videos/ https://css-tricks.com/lazy-load-embedded-youtube-videos/#comments Tue, 20 Aug 2019 14:41:13 +0000 https://css-tricks.com/?p=294443 This is a very clever idea via Arthur Corenzan. Rather than use the default YouTube embed, which adds a crapload of resources to a page whether the user plays the video or not, use the little tiny placeholder webpage that …


Lazy load embedded YouTube videos originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is a very clever idea via Arthur Corenzan. Rather than use the default YouTube embed, which adds a crapload of resources to a page whether the user plays the video or not, use the little tiny placeholder webpage that is just an image you can click that is linked to the YouTube embed.

It still behaves essentially exactly the same: click, play video in place.

The trick is rooted in srcdoc, a feature of <iframe> where you can put the entire contents of an HTML document in the attribute. It’s like inline styling but an inline-entire-documenting sort of thing. I’ve used it in the past when I embedded MailChimp-created newsletters on this site. I’d save the email into the database as a complete HTML document, retrieve it as needed, and chuck it into an <iframe> with srcdoc.

Arthur credits Remy for a tweak to get it working in IE 11 and Adrian for some accessibility tweaks.

I also agree with Hugh in the comments of that post. Now that native lazy loading has dropped in Chrome (see our coverage) we might as well slap loading="lazy" on there too, as that will mean no requests at all if it renders out of the viewport.

I’ll embed a demo here too:

Reader Tracey Rich said they needed to use &#x25BA; instead of that Unicode play button.

To Shared LinkPermalink on CSS-Tricks


Lazy load embedded YouTube videos originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/lazy-load-embedded-youtube-videos/feed/ 13 294443
Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM https://css-tricks.com/weekly-platform-news-html-loading-attribute-the-main-aria-specifications-and-moving-from-iframe-to-shadow-dom/ https://css-tricks.com/weekly-platform-news-html-loading-attribute-the-main-aria-specifications-and-moving-from-iframe-to-shadow-dom/#comments Thu, 15 Aug 2019 21:27:57 +0000 https://css-tricks.com/?p=294405 In this week’s roundup of platform news, Chrome introduces a new attribute for loading, accessibility specifications for web developers, and the BBC moves visualizations to the Shadow DOM.

Chrome ships the loading attribute

The HTML loading attribute for lazy-loading images …


Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
In this week’s roundup of platform news, Chrome introduces a new attribute for loading, accessibility specifications for web developers, and the BBC moves visualizations to the Shadow DOM.

Chrome ships the loading attribute

The HTML loading attribute for lazy-loading images and iframes is now supported in Chrome. You can add loading="lazy" to defer the loading of images and iframes that are below the viewport until the user scrolls near them.

Google suggests either treating this feature as a progressive enhancement or using it on top of your existing JavaScript-based lazy-loading solution.

This feature has not yet been added to the HTML Standard (but there is an open pull request), and multiple links to Google’s documentation are listed on its Chrome Status page. (via web.dev)


Overview of ARIA specifications

The main accessibility specifications for web developers:

NameDescription
ARIA in HTMLDefines which ARIA role, state, and property attributes are allowed on which HTML elements (the implicit ARIA semantics are defined here)
Using ARIAProvides practical advice on how to use ARIA in HTML, with an emphasis on dynamic content and advanced UI controls (the “five rules of ARIA use” are defined here)
ARIA (Accessible Rich Internet Applications)Defines the ARIA roles, states, and properties
ARIA Authoring PracticesProvides general guidelines on how to use ARIA to create accessible apps (includes ARIA implementation patterns for common widgets)
HTML Accessibility API MappingsDefines how browsers map HTML elements and attributes to the operating system’s accessibility APIs
WCAG (Web Content Accessibility Guidelines)Provides guidelines for making web content more accessible (success criteria for WCAG conformance are defined here)

Related: “Contributing to the ARIA Authoring Practices Guide” by Simon Pieters and Valerie Young


Shadow DOM on the BBC website

The BBC has moved from <iframe>to Shadow DOM for the embedded interactive visualizations on its website. This has resulted in significant improvements in load performance (“more than 25% faster”).

The available Shadow DOM polyfills didn’t reliably prevent styles from leaking across the Shadow DOM boundary, so they decided to instead fall back to <iframe> in browsers that don’t support Shadow DOM.

Shadow DOM […] can deliver content in a similar way to iframes in terms of encapsulation but without the negative overheads […] We want encapsulation of an element whose content will appear seamlessly as part of the page. Shadow DOM gives us that without any need for a custom element.

One major drawback of this new approach is that CSS media queries can no longer be used to conditionally apply styles based on the content’s width (since the content no longer loads in a separate, embedded document).

With iframes, media queries would give us the width of our content; with Shadow DOM, media queries give us the width of the device itself. This is a huge challenge for us. We now have no way of knowing how big our content is when it’s served.

(via Toby Cox)


In other news…

  • The next version of Chrome will introduce the Largest Contentful Paint performance metric; this new metric is a more accurate replacement for First Meaningful Paint, and it measures when the largest element is rendered in the viewport (usually, the largest image or paragraph of text) (via Phil Walton)
  • Microsoft has created a prototype of a new tool for viewing a web page’s DOM in 3D; this tool is now experimentally available in the preview version of Edge (via Edge DevTools)
  • Tracking prevention has been enabled by default in the preview versions of Edge; it is set to balanced by default, which “blocks malicious trackers and some third-party trackers” (via Techdows)

Read more news in my new, weekly Sunday issue. Visit webplatform.news for more information.


Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/weekly-platform-news-html-loading-attribute-the-main-aria-specifications-and-moving-from-iframe-to-shadow-dom/feed/ 7 294405
Get Peak WordPress Performance with Jetpack https://css-tricks.com/get-peak-wordpress-performance-with-jetpack/ Thu, 18 Jul 2019 15:09:00 +0000 https://css-tricks.com/?p=292998 (This is a sponsored post.)

The irony of web performance is that the average page weight of a site continues to go up year after year, despite us being more aware of the problem and having more tools at …


Get Peak WordPress Performance with Jetpack originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
(This is a sponsored post.)

The irony of web performance is that the average page weight of a site continues to go up year after year, despite us being more aware of the problem and having more tools at our disposal to fight it than ever.

To paraphrase Seinfeld, “we know how to fight page weight issues; we just don’t use the tools we have to fight page weight issues.”

That’s why Jetpack provides powerful features for all users at any plan level. They made it so that performance is integrated right into managing content on a WordPress site.

One of those things is lazy loading images. Lazy loading is an excellent technique to defer loading images until they are actually needed. So, an image never loads until the user actually scrolls to where it comes into display. That could potentially save a ton of server requests and precious bytes when waiting for a page to load. Jetpack includes lazy loading, even on free plans, so everyone has access to this performance boost.


And what’s the point of lazy loading anything if you don’t have somewhere to host the files? Well, Jetpack also offers unlimited static file and image hosting for every plan level. No more wondering how much storage is left on your server! You get an unlimited amount of space to store anything you need. That’s pretty awesome!

It gets even more awesome. That’s because the unlimited storage is part of a CDN that is designed to serve images from high-speed dedicated data centers that make downloads as fast and smooth as possible. Again, that’s free to everyone!

That makes Jetpack a super resource for combatting performance issues on a WordPress site. Hey, we use Jetpack here at CSS-Tricks and it’s a linchpin for so much of how this site works and operates. The performance benefits are a nice perk but it’s worth checking out everything it has to offer because there’s likely so much more you can leverage.

Get Jetpack

Hey it doesn’t hurt there is a 20% discount right now with DISCOUNT20 until July 31st, 2019


Get Peak WordPress Performance with Jetpack originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
292998
Tips for rolling your own lazy loading https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/ https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/#comments Mon, 01 Jul 2019 14:16:24 +0000 http://css-tricks.com/?p=291777 You may have heard (or even issued the call) that “we can just use lazy loading!” when looking for a way to slim down a particularly heavy web page.

Lazy loading is a popular technique for gradually requesting images as …


Tips for rolling your own lazy loading originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
You may have heard (or even issued the call) that “we can just use lazy loading!” when looking for a way to slim down a particularly heavy web page.

Lazy loading is a popular technique for gradually requesting images as they come into view, rather than all at once after the HTML of the page has been parsed. It can reduce the initial page weight, and help us hit our performance budgets by requesting images when they’re needed.

It can be effective. But it also comes with some baggage of its own. We’ll get to that! In fact, Rahul Nanwani did an extensive write-up that hits several lazy-loading methods and illustrates just how complex some are.

In this post, we’ll look at an implementation that’s already been covered in brief detail in this post by Preethi. We’re going to expand on that so you can add your own implementation of lazy loading to your site as I’ve done on this little demo site.

We’ll cover these topics along the way:

Here’s what we’ll be building:

Why not native lazy loading?

Currently, lazy loading is not something browsers can do for us natively. Although that looks set to change soon for some browsers with the launch of Chrome 75 which aims to bring lazy loading support for images and iframes. Until that time (and beyond, if we are to play nicely with other browsers – which we should) lazy loading is implemented using JavaScript. There are a number of libraries and frameworks out there to help.

Some static site generators, libraries, and frameworks include utilities to provide this capability “out of the box”, which proves popular as people look for built-in ways to include this feature in their sites. But I’ve also noticed a trend where some chose to adopt entire libraries or frameworks in order to gain access to just this feature. As a thrifty, performance and inclusivity blow-hard, I’m a bit cautious about this. So let’s look at how you could implement this yourself without the need for a specific framework or library.

lazy loading example

The typical mechanics for lazy loading

Most approaches follow a pattern like this:

First, some HTML to define our lazily loaded images

<!-- 
Don't include a src attribute in images you wish to load lazily.
Instead specify their src safely in a data attribute
-->
<img data-src="lighthouse.jpg" alt="A snazzy lighthouse" class="lazy" />

When should image loading take place?

Next, we use some sort of JavaScript magic to correctly set the src attribute when the image comes into view. This was once an expensive JavaScript operation, involving listening for window scroll and resize events, but IntersectionObserver has come to the rescue.

Creating an intersection observer looks like this:

// Set up an intersection observer with some options
var observer = new IntersectionObserver(lazyLoad, {

  // where in relation to the edge of the viewport, we are observing
  rootMargin: "100px",

  // how much of the element needs to have intersected 
  // in order to fire our loading function
  threshold: 1.0

});

We’ve told our new observer to call a function named lazyLoad when those observable conditions are met. The elements that satisfy those conditions will be passed to that function so that we can manipulate them… like actually load and display them.

function lazyLoad(elements) {
  elements.forEach(image => {
    if (image.intersectionRatio > 0) {

      // set the src attribute to trigger a load
      image.src = image.dataset.src;

      // stop observing this element. Our work here is done!
      observer.unobserve(item.target);
    };
  });
};

Great. Our images will have the correct src assigned to them as they come into view, which will cause them to load. But which images? We need to tell the Intersection Observer API which elements we care about. Luckily, we assigned each one with a CSS class of .lazy for just this purpose.

// Tell our observer to observe all img elements with a "lazy" class
var lazyImages = document.querySelectorAll('img.lazy');
lazyImages.forEach(img => {
  observer.observe(img);
});

Nice. But perfect?

This seems to be working nicely, but there are some drawbacks to consider:

  1. Until (or unless) JavaScript comes along and successfully runs, we have a bunch of image elements on our page that will not work. We deliberately nixed them by removing the src attribute. That’s the result we wanted, but now we are dependent on JavaScript for these images to load. While it’s true that JavaScript is pretty well ubiquitous on the web theses days — with the web reaching such a broad spectrum of devices and network conditions — JavaScript can become an expensive addition to our performance budgets, particularly if it is involved in the delivery and rendering of content. As Jake Archibald once pointed out, all your users are non-JS while they’re downloading your JS. In other words, this is not to be taken lightly.
  2. Even when this works successfully, we have empty elements on our page which might give a bit of a visual jolt when they load in. Perhaps we can hint at the image first and do something fancy. We’ll get to that shortly.

The planned native lazy loading implementation by Chrome should help to address our first point here. If the element has been given a loading attribute, Chrome can honor the src attribute specified at the right time, rather than requesting it eagerly the moment it sees it in the HTML.

The editor’s draft of the specification includes support for different loading behaviors:

  • <img loading="lazy" />: Tell the browser to load this image lazily when needed.
  • <img loading="eager" />: Tell the browser to load this image immediately.
  • <img loading="auto" />: Let the browser make its own assessment.

Browsers without this support would be able to load the image as normal thanks to the resilient nature of HTML and browsers ignoring HTML attributes that they don’t understand.

But… sound the loud caution klaxon! This feature has yet to land in Chrome, and there is also uncertainty about if and when other browsers might choose to implement it. We can use feature detection to decide which method we use, but this still doesn’t give a solid progressive enhancement approach where the images have no dependency on JavaScript.

<img data-src="lighthouse.jpg" alt="A snazzy lighthouse" loading="lazy" class="lazy" />
// If the browser supports lazy loading, we can safely assign the src
// attributes without instantly triggering an eager image load.
if ("loading" in HTMLImageElement.prototype) {
  const lazyImages = document.querySelectorAll("img.lazy");
  lazyImages.forEach(img => {
    img.src = img.dataset.src;
  });
}
else {
  // Use our own lazyLoading with Intersection Observers and all that jazz
}

As a companion to responsive images

Assuming that we are comfortable with the fact that JavaScript is a dependency for the time being, let’s turn our attention to a related topic: responsive images.

If we’re going through the trouble of delivering images into the browser only when needed, it seems fair that we might also want to make sure that we are also delivering them in the best size for how they’ll be displayed. For example, there’s no need to download the 1200px-wide version of an image if the device displaying it will only give it a width of 400px. Let’s optimize!

HTML gives us a couple of ways to implement responsive images which associate different image sources to different viewport conditions. I like to use the picture element like this:

<picture>
  <source srcset="massive-lighthouse.jpg" media="(min-width: 1200px)">
  <source srcset="medium-lighthouse.jpg" media="(min-width: 700px)">
  <source srcset="small-lighthouse.jpg" media="(min-width: 300px)">
  <img src="regular-lighthouse.jpg" alt="snazzy lighthouse" />
</picture>

You’ll notice that each source element has a srcset attribute which specifies an image URL, and a media attribute that defines the conditions under which this source should be used. The browser selects the most suitable source from the list according to the media conditions with a standard img element acting as a default/fallback.

Can we combine these two approaches to make lazy-loading responsive images?

Of course, we can! Let’s do it.

Instead of having an empty image until we do our lazy load, I like to load a placeholder image that has a tiny file size. This does incur the overhead of making more HTTP requests, but it also gives a nice effect of hinting at the image before it arrives. You might have seen this effect on Medium or as a result of a site using Gatsby’s lazy loading mechanics.

We can achieve that by initially defining the image sources in our picture element as tiny versions of the same asset and then using CSS to scale them to the same size as their higher-resolution brothers and sisters. Then, through our intersection observer, we can update each of the specified sources to point at the correct image sources.

So, initially our picture element might look like this:

<picture class="lazy">
  <source srcset="tiny-lighthouse.jpg" media="(min-width: 1200px)">
  <source srcset="tiny-lighthouse.jpg" media="(min-width: 700px)">
  <source srcset="tiny-lighthouse.jpg" media="(min-width: 300px)">
  <img src="tiny-lighthouse.jpg" alt="snazzy cake" />
</picture>

No matter what viewport size is applied, we’ll display a tiny 20px image. We’re going to blow it up with CSS next.

Previewing the image with style

The browser can scale up the tiny preview image for us with CSS so that it fits the entire picture element rather than a mere 20px of it. Things are going to get a little… pixelated, as you may imagine when a low-resolution image is blown up to larger dimensions.

picture {
  width: 100%; /* stretch to fit its containing element */
  overflow: hidden;
}

picture img {
  width: 100%; /* stretch to fill the picture element */
}
blurred image

For good measure, we can soften that pixelation introduced by scaling up the image by using a blur filter.

picture.lazy img {
  filter: blur(20px);
}
more fully blurred image

Switching sources with JavaScript

With a little adaptation, we can use the same technique as before to set the correct URLs for our srcset and src attributes.

function lazyLoad(elements) {

  elements.forEach(picture => {
    if (picture.intersectionRatio > 0) {

      // gather all the image and source elements in this picture
      var sources = picture.children;

      for (var s = 0; s < sources.length; s++) {
        var source = sources[s];

        // set a new srcset on the source elements 
        if (sources.hasAttribute("srcset")) {
          source.setAttribute("srcset", ONE_OF_OUR_BIGGER_IMAGES);
        }
        // or a new src on the img element
        else {
          source.setAttribute("src", ONE_OF_OUR_BIGGER_IMAGES);
        }
      }

      // stop observing this element. Our work here is done!
      observer.unobserve(item.target);
    };
  });

};

One last step to complete the effect: remove that blur effect from the image once the new source has loaded. A JavaScript event listener waiting for the load event on each new image resource can do that for us.

// remove the lazy class when the full image is loaded to unblur
source.addEventListener('load', image => {
  image.target.closest("picture").classList.remove("lazy")
}, false);

We can make a nice transition that eases away the blur away, with a sprinkle of CSS.

picture img {
  ...
  transition: filter 0.5s,
}

A little helper from our friends

Great. With just a little JavaScript, a few lines of CSS and a very manageable dollop of HTML, we’ve created a lazy loading technique which also caters for responsive images. So, why aren’t we happy?

Well, we’ve created two bits of friction:

  1. Our markup for adding images is more complex than before. Life used to be simple when all we needed was a single img tag with a good old src attribute.
  2. We’ll also need to create multiple versions of each image assets to populate each viewport size and the pre-loaded state. That’s more work.

Never fear. We can streamline both of these things.

Generating the HTML elements

Let’s look first at generating that HTML rather than authoring it by hand each time.

Whatever tool you use to generate your HTML, chances are that it includes a facility to use includes, functions, shortcodes, or macros. I’m a big fan of using helpers like this. They keep more complex or nuanced code fragments consistent and save time from having to write lengthy code. Most static site generators have this sort of ability.

  • Jekyll lets you create custom Plugins
  • Hugo gives you custom shortcodes
  • Eleventy has shortcodes for all of the template engines it supports
  • There are many more…

As an example, I made a shortcode called lazypicture in my example project built with Eleventy. The shortcode gets used like this:

{% lazypicture lighthouse.jpg "A snazzy lighthouse" %}

To generate the HTML that we need at build time:

<picture class="lazy">
  <source srcset="/images/tiny/lighthouse.jpg" media="(min-width: 1200px)">
  <source srcset="/images/tiny/lighthouse.jpg" media="(min-width: 700px)">
  <source srcset="/images/tiny/lighthouse.jpg" media="(min-width: 300px)">
  <img src="/images/tiny/lighthouse.jpg" alt="A snazzy lighthouse" />
</picture>

Generating the image assets

The other bit of work we have created for ourselves is generating differently sized image assets. We don’t want to manually create and optimize each and every size of every image. This task is crying out for some automation.

The way you choose to automate this should take into account the number of images assets you need and how regularly you might add more images to that set. You might chose to generate those images as part of each build. Or you could make use of some image transformation services at request time. Let’s look a little at both options.

Option 1: Generating images during your build

Popular utilities exist for this. Whether you run your builds with Grunt, Gulp, webpack, Make, or something else, chances are there is utility for you.

The example below is using gulp-image-resize in a Gulp task as part of a Gulp build process. It can chomp through a directory full of image assets and generate the variants you need. It has a bunch of options for you to control, and you can combine with other Gulp utilities to do things like name the different variants according to the conventions you choose.

var gulp = require('gulp');
var imageResize = require('gulp-image-resize');

gulp.task('default', function () {
  gulp.src('src/**/*.{jpg,png}')
    .pipe(imageResize({
      width: 100,
      height: 100
    }))
    .pipe(gulp.dest('dist'));
});

The CSS-Tricks site uses a similar approach (thanks to the custom sizes feature in WordPress) to auto-generate all of its different image sizes. (Oh yeah! CSS-Tricks walks the walk!) ResponsiveBreakpoints.com provides a web UI to experiment with different settings and options for creating images sets and even generates the code for you.

Or, you can use it programmatically as Chris mentioned on Twitter.

When you have as many image files as CSS-Tricks, though, doing this work as part of a build step can become cumbersome. Good caching in your build and other file management tasks can help, but it can be easy to end up with a lengthy build process that heats up your computer as it performs all of the work.

An alternative is to transform these resources at request time rather than during a build step. That’s the second option.

Option 2: On-demand image transformations

I’m a loud advocate of pre-rendering content. I’ve shouted about this approach (often referred to as JAMstack) for quite some time, and I believe that it has numerous performance, security and simplicity benefits. (Chris summed this up nicely in a post about static hosting and JAMstack.)

That said, the idea of generating different image sizes at request time might seem to be contrary to my lazy loading objectives. In fact, there are a number of services and companies now, who specialize in this and they do it in a very powerful and convenient way.

Combining image transformations with powerful CDN and asset caching capabilities by companies like Netlify, Fastly, and Cloudinary can rapidly generate images with the dimensions you pass to them via a URL. Each service has significant processing power to perform these transformations on the fly, then cache the generated images for future use. This makes for seamless rendering for subsequent requests.

Since I work at Netlify, I’ll illustrate this with an example using Netlify’s service. But the others I mentioned work in similar ways.

Netlify’s Image Transformation service builds on top of something called Netlify Large Media. This is a feature created to help manage large assets in your version control. Git is not very good at this by default, but Git Large File Storage can extend Git to make it possible to include large assets in your repos without clogging them up and making them unmanageable.

You can read more on the background of that approach for managing large assets if you are interested.

Placing images under version control in our Git repositories is an added bonus, but for our purposes, we are more interested in enjoying the benefits of making on-the-fly transformations of those images.

Netlify looks for querystring parameters when transforming images. You can specify the height, width and the type of crop you’d like to perform. Like this:

  • A raw image with no transformations:
    /images/apple3.jpg
  • An image resized to be 300px wide:
    /images/apple3.jpg?nf_resize=fit&w=300
  • An image cropped to be 500px by 500px with automated focal point detection:
    /images/apple3.jpg?nf_resize= smartcrop&w=500&h=500

Knowing that we can create and deliver any image sizes from a single source image in our version control means that the JavaScript we use to update the image sources only need to include the size parameters we choose.

The approach can drastically speed up your site build processes because the work is now outsourced and not performed at build time.

Wrapping it all up

We’ve covered a lot of ground here. There are a lot of very achievable options for implementing responsive images with lazy loading. Hopefully, this will give enough info to make you think twice about reaching for the nearest available framework to gain access to this sort of functionality.

This demo site pulls together a number of these concepts and uses Netlify’s image transformation service.

One last time, to summarize the flow

  • A static site generator with a shortcode eases the task of creating the picture elements
  • Netlify Large Media hosts and transforms the images, then serves them as tiny 20px-wide versions before the larger files are loaded as needed.
  • CSS scales up the tiny images and blurs them to create the preview placeholder images.
  • The Intersection Observer API detects when to swap the image assets for the appropriate larger versions.
  • JavaScript detects the load event for the larger images and removes out the blur effect to reveal the higher-resolution rendering.

Tips for rolling your own lazy loading originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/feed/ 12 291777
A Deep Dive into Native Lazy-Loading for Images and Frames https://css-tricks.com/a-deep-dive-into-native-lazy-loading-for-images-and-frames/ https://css-tricks.com/a-deep-dive-into-native-lazy-loading-for-images-and-frames/#comments Wed, 15 May 2019 15:04:40 +0000 http://css-tricks.com/?p=287494 Today’s websites are packed with heavy media assets like images and videos. Images make up around 50% of an average website’s traffic. Many of them, however, are never shown to a user because they’re placed way below the fold


A Deep Dive into Native Lazy-Loading for Images and Frames originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Today’s websites are packed with heavy media assets like images and videos. Images make up around 50% of an average website’s traffic. Many of them, however, are never shown to a user because they’re placed way below the fold.

What’s this thing about images being lazy, you ask? Lazy-loading is something that’s been covered quite a bit here on CSS-Tricks, including a thorough guide with documentation for different approaches using JavaScript. In short, we’re talking about a mechanism that defers the network traffic necessary to load content when it’s needed — or rather when trigger the load when the content enters the viewport.

The benefit? A smaller initial page that loads faster and saves network requests for items that may not be needed if the user never gets there.

If you read through other lazy-loading guides on this or other sites, you’ll see that we’ve had to resort to different tactics to make lazy-loading work. Well, that’s about to change when lazy-loading will be available natively in HTML as a new loading attribute… at least in Chrome which will hopefully lead to wider adoption. Chrome is currently developing and testing support for native lazy-loading and is expected to enable it in Chrome 76, which is slated to release on 30th July 2019.

Eager cat loaded lazily (but still immediately because it's above the fold)

The pre-native approach

Until now, developers like ourselves have had to use JavaScript (whether it’s a library or something written from scratch) in order to achieve lazy-loading. Most libraries work like this:

  • The initial, server-side HTML response includes an img element without the src attribute so the browser does not load any data. Instead, the image’s URL is set as another attribute in the element’s data set, e. g. data-src.
  • <img data-src="https://tiny.pictures/example1.jpg" alt="...">
  • Then, a lazy-loading library is loaded and executed.
  • <script src="LazyLoadingLibrary.js"></script>
    <script>LazyLoadingLibrary.run()</script>
  • That keeps track of the user’s scrolling behavior and tells the browser to load the image when it is about to be scrolled into view. It does that by copying the data-src attribute’s value to the previously empty src attribute.
  • <img src="https://tiny.pictures/example1.jpg" data-src="https://tiny.pictures/example1.jpg" alt="...">

This has worked for a pretty long time now and gets the job done. But it’s not ideal for good reasons.

The obvious problem with this approach is the length of the critical path for displaying the website. It consists of three steps, which have to be carried out in sequence (after each other):

  1. Load the initial HTML response
  2. Load the lazy-loading library
  3. Load the image file

If this technique is used for images above the fold the website will flicker during loading because it is first painted without the image (after step 1 or 2, depending on if the script uses defer or async) and then — after having been loaded — include the image. It will also be perceived as loading slowly.

In addition, the lazy-loading library itself puts an extra weight on the website’s bandwidth and CPU requirements. And let’s not forget that a JavaScript approach won’t work for people who have JavaScript disabled (although we shouldn’t really care about them in 2019, should we?).

Oh, and what about sites that rely on RSS to distribute content, like CSS-Tricks? The initial image-less render means there are no images in the RSS version of content as well.

And so on.

Native lazy-loading to the rescue!

Lazy cat loaded lazily

As we noted at the start, Chromium and Google Chrome will ship a native lazy-loading mechanism in the form of a new loading attribute, starting in Chrome 75. We’ll go over the attribute and its values in just a bit, but let’s first get it working in our browsers so we can check it out together.

Enable native lazy-loading

In Chrome versions starting from 75, we can enable lazy-loading manually by switching two flags. Chrome is expected to enable this feature by default from version 76 (release planned for 30th July 2019).

  1. Open chrome://flags in Chromium or Chrome Canary.
  2. Search for lazy.
  3. Enable both the “Enable lazy image loading” and the “Enable lazy frame loading” flag.
  4. Restart the browser with the button in the lower right corner of the screen.
Native lazy-loading flags in Google Chrome

You can check if the feature is properly enabled by opening your JavaScript console (F12). You should see the following warning:

[Intervention] Images loaded lazily and replaced with placeholders. Load events are deferred.”

All set? Now we get to dig into the loading attribute.

The loading attribute

Both the img and the iframe elements will accept the loading attribute. It’s important to note that its values will not be taken as a strict order by the browser but rather as a hint to help the browser make its own decision whether or not to load the image or frame lazily.

The attribute can have three values which are explained below. Next to the images, you’ll find tables listing your individual resource loading timings for this page load. Range response refers to a kind of partial pre-flight request made to determine the image’s dimensions (see How it works) for details). If this column is filled, the browser made a successful range request.

Please note the startTime column, which states the time image loading was deferred after the DOM had been parsed. You might have to perform a hard reload (CTRL + Shift + R) to re-trigger range requests.

The auto (or unset) value

<img src="auto-cat.jpg" loading="auto" alt="...">
<img src="auto-cat.jpg" alt="...">
<iframe src="https://css-tricks.com/" loading="auto"></iframe>
<iframe src="https://css-tricks.com/"></iframe>
Auto cat loaded automatically
Auto cat loaded automatically

Setting the loading attribute to auto (or simply leaving the value blank, as in loading="") lets the browser decide whether or not to lazy-load an image. It takes many things into consideration to make that decision, like the platform, whether Data Saver mode is enabled, network conditions, image size, image vs. iframe, the CSS display property, among others. (See How it works) for info about why all this is important.)

The eager value

<img src="auto-cat.jpg" loading="eager" alt="...">
<iframe src="https://css-tricks.com/" loading="eager"></iframe>
Eager cat loaded eagerly
Eager cat loaded eagerly

The eager value provides a hint to the browser that an image should be loaded immediately. If loading was already deferred (e. g. because it had been set to lazy and was then changed to eager by JavaScript), the browser should start loading the image immediately.

The lazy value

<img src="auto-cat.jpg" loading="lazy" alt="...">
<iframe src="https://css-tricks.com/" loading="lazy"></iframe>
Lazy cat loaded lazily
Lazy cat loaded lazily

The lazy value provides a hints to the browser that an image should be lazy-loaded. It’s up to the browser to interpret what exactly this means, but the explainer document states that it should start loading when the user scrolls “near” the image such that it is probably loaded once it actually comes into view.

How the loading attribute works

In contrast to JavaScript lazy-loading libraries, native lazy-loading uses a kind of pre-flight request to get the first 2048 bytes of the image file. Using these, the browser tries to determine the image’s dimensions in order to insert an invisible placeholder for the full image and prevent content from jumping during loading.

The image’s load event is fired as soon as the full image is loaded, be it after the first request (for images smaller than 2 KB) or after the second one. Please note that the load event may never be fired for certain images because the second request is never made.

In the future, browsers might make twice as many image requests as there would be under the current proposal. First the range request, then the full request. Make sure your servers support the HTTP Range: 0-2047 header and respond with status code 206 (Partial Content) to prevent them from delivering the full image twice.

Due to the higher number of subsequent requests made by the same user, web server support for the HTTP/2 protocol will become more important.

Let’s talk about deferred content. Chrome’s rendering engine Blink uses heuristics to determine which content should be deferred and for how long to defer it. You can find a comprehensive list of requirements in Scott Little’s design documentation. This is a short breakdown of what will be deferred:

  • Images and frames on all platforms which have loading="lazy" set
  • Images on Chrome for Android with Data Saver enabled and that satisfy all of the following:
    • loading="auto" or unset
    • no width and height attributes smaller than 10px
    • not created programmatically in JavaScript
  • Frames which satisfy all of the following:
    • loading="auto" or unset
    • is from a third-party (different domain or protocol than the embedding page)
    • larger than 4 pixels in height and width (to prevent deferring tiny tracking frames)
    • not marked as display: none or visibility: hidden (again, to prevent deferring tracking frames)
    • not positioned off-screen using negative x or y coordinates

Responsive images with srcset

Native lazy-loading also works with responsive img elements using the srcset attribute. This attribute offers a list of image file candidates to the browser. Based on the user’s screen size, display pixel ratio, network conditions, etc., the browser will choose the optimal image candidate for the occasion. Image optimization CDNs like tiny.pictures are able to provide all image candidates in real-time without any back end development necessary.

<img src="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg" srcset="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=400 400w, https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=800 800w" loading="lazy" alt="...">

Browser support

At the time of this writing, no browser supports native-loading by default. However, Chrome will enable the feature, as we’ve covered, starting in Chrome 76. No other browser vendor has announced support so far. (Edge being a kind of exception because it will soon make the switch to Chromium.)

You can detect the feature with a few lines of JavaScript:

if ("loading" in HTMLImageElement.prototype) {
  // Support.
} else {
  // No support. You might want to dynamically import a lazy-loading library here (see below).
}

See the Pen
Native lazy-loading browser support
by Erk Struwe (@erkstruwe)
on CodePen.

Automatic fallback to JavaScript solution with low-quality image placeholder

One very cool feature of most JavaScript-based lazy-loading libraries is the low-quality image placeholder (LQIP). Basically, it leverages the idea that browsers load (or perhaps I should say used to load) the src of an img element immediately, even if it gets later replaced by another URL. This way, it’s possible to load a tiny file size, low-quality image file on page load and later replace it with a full-sized version.

We can now use this to mimic the native lazy-loading’s 2 KB range requests in browsers that do not support this feature in order to achieve the same result, namely a placeholder with the actual image dimensions and a tiny file size.

See the Pen
Native lazy-loading with JavaScript library fallback and low-quality image placeholder
by Erk Struwe (@erkstruwe)
on CodePen.

Conclusion

I’m really excited about this feature. And frankly, I’m still wondering why it hasn’t got much more attention until now, given the fact that its release is imminent and the impact on global internet traffic will be remarkable, even if only small parts of the heuristics are changed.

Think about it: After a gradual roll-out for the different Chrome platforms and with auto being the default setting, the world’s most popular browser will soon lazy-load below-the-fold images and frames by default. Not only will the traffic amount of many badly-written websites drop significantly, but web servers will be hammered with tiny requests for image dimension detection.

And then there’s tracking: Assuming many unsuspecting tracking pixels and frames will be prevented from being loaded, the analytics and affiliate industry will have to act. We can only hope they don’t panic and add loading="eager" to every single image, rendering this great feature useless for their users. They should rather change their code to be recognized as tracking pixels by the heuristics described above.

Web developers, analytics and operations managers should check their website’s behavior with this feature and their servers’ support for Range requests and HTTP/2 immediately.

Image optimization CDNs could help out in case there are any issues to be expected or if you’d like to take image delivery optimization to the max (including automatic WebP support, low-quality image placeholders, and much more). Read more about tiny.pictures!

References


A Deep Dive into Native Lazy-Loading for Images and Frames originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-deep-dive-into-native-lazy-loading-for-images-and-frames/feed/ 11 287494
Native Lazy Loading https://css-tricks.com/native-lazy-loading/ https://css-tricks.com/native-lazy-loading/#comments Mon, 08 Apr 2019 22:28:11 +0000 http://css-tricks.com/?p=286080 IntersectionObserver has made lazy loading a lot easier and more efficient than it used to be, but to do it really right you still gotta remove the src and such, which is cumbersome. It’s definitely not as easy as:

<img 


Native Lazy Loading originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
IntersectionObserver has made lazy loading a lot easier and more efficient than it used to be, but to do it really right you still gotta remove the src and such, which is cumbersome. It’s definitely not as easy as:

<img src="celebration.jpg" loading="lazy" alt="..." />

Addy Osmani says it’s coming in Chrome 75:

The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values:

  • lazy: is a good candidate for lazy loading.
  • eager: is not a good candidate for lazy loading. Load right away.
  • auto: browser will determine whether or not to lazily load.

I’ll probably end up writing a WordPress content filter for this site that adds that attribute for every dang image on this site. Hallelujah, I say, and godspeed other browsers.

Easy lazy loading of images will have the biggest impact on the site as a whole, but lazy loaded iframes will be even bigger for the individual sites that use them. I’m into it.

I hope this pushes along the need for native aspect ratios as well, since a major reason for that is preventing content reflow from things loading later. We do have ways now, though.

If you’re looking to add lazy loading to your site’s media right now, you’d be smart to include these native lazy loading attributes, but as of this update (July 2019), support in stable browsers in non-existent still. You’ll want to look at doing more:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
77119No7916.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
11511511516.4


Native Lazy Loading originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/native-lazy-loading/feed/ 11 286080
Preventing Content Reflow From Lazy-Loaded Images https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/#comments Thu, 29 Nov 2018 15:03:05 +0000 http://css-tricks.com/?p=279419 You know the concept of lazy loading images. It prevents the browser from loading images until those images are in (or nearly in) the browser’s viewport.

There are a plethora of JavaScript-based lazy loading solutions. GitHub has over 3,400


Preventing Content Reflow From Lazy-Loaded Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
You know the concept of lazy loading images. It prevents the browser from loading images until those images are in (or nearly in) the browser’s viewport.

There are a plethora of JavaScript-based lazy loading solutions. GitHub has over 3,400 different lazy load repos, and those are just the ones with “lazy load” in a searchable string! Most of them rely on the same trick: Instead of putting an image’s URL in the src attribute, you put it in data-src — which is the same pattern for responsive images:

  • JavaScript watches the user scroll down the page
  • When the use encounters an image, JavaScript moves the data-src value into src where it belongs
  • The browser requests the image and it loads into view

The result is the browser loading fewer images up front so that the page loads faster. Additionally, if the user never scrolls far enough to see an image, that image is never loaded. That equals faster page loads and less data the user needs to spend.

“This is amazing!” you may be thinking. And, you’re right… it is amazing!

That said, it does indeed introduce a noticeable problem: images not containing the src attribute (including when it’s empty or invalid) have no height. This means that they’re not the right size in the page layout until they’re lazy-loaded.

Update! Times changed fast here, and to avoid lazy-load jank, all you have to do is put the correct natural width and height attributes on images and they will load nicely, even if CSS makes the image fluid with. So do it like: <img src="image.jpg" width="800" height="600">

When a user scrolls and images are lazy-loaded, those img elements go from a height of 0 pixels to whatever they need to be. This causes reflow, where the content below or around the image gets pushed to make room for the freshly loaded image. Reflow is a problem because it’s a user-blocking operation. It slows down the browser by forcing it to recalculate the layout of any elements that are affected by that image’s shape. The CSS scroll-behavior property may help here at some point, but its support needs to improve before it’s a viable option.

Lazy loading doesn’t guarantee that the image will fully load before it enters the viewport. The result is a perceived janky experience, even if it’s a big performance win.

There are other issues with lazy loading images that are worth mentioning but are outside the scope of this post. For example, if JavaScript fails to run at all, then no images will load on the page. That’s a common concern for any JavaScript-based solution but this article only concerned with solving the problems introduced by reflow.

If we could force pre-loaded images to maintain their normal width and height (i.e. their aspect ratio), we could prevent reflow problems while still lazy loading them. This is something I recently had to solve building a progressive web app at DockYard where I work.

For future reference, there’s an HTML attribute called intrinsicsize that’s designed to preserve the aspect ratio, but right now, that’s just experimental in Chrome.

Here’s how we did it.

Maintaining aspect ratio

There are many ways to go about the way we can maintain aspect ratios. Chris once rounded up an exhaustive list of options, but here’s what we’re looking at for image-specific options.

The image itself

The image src provides a natural aspect ratio. Even when an image is resized responsively, its natural dimensions still apply. Here’s a pretty common bit of responsive image CSS:

img {
  max-width: 100%;
  height: auto;
}

That CSS is telling images not to exceed the width of the element that contains them, but to scale the height properly so that there’s no “stretching” or “squishing” as the image is resized. Even if the image has inline height and width attributes, this CSS will keep them behaving nicely on small viewports.

However, that “natural aspect ratio” behavior breaks down if there’s no src yet. Browsers don’t care about data-src and don’t do anything with it, so it’s not really a viable solution for lazy loading reflow; but it is important to help understand the “normal” way images are laid out once they’ve loaded.

A pseudo-element

Many developers — including myself — have been frustrated trying to use pseudo-elements (e.g. ::before and ::after) to add decorations to img elements. Browsers don’t render an image’s pseudo-elements because img is a replaced element, meaning its layout is controlled by an external resource.

However, there is an exception to that rule: If an image’s src attribute is invalid, browsers will render its pseudo-elements. So, if we store the src for an image in data-src and the src is empty, then we can use a pseudo-element to set an aspect ratio:

[data-src]::before {
  content: '';
  display: block;
  padding-top: 56.25%;
}

That’ll set a 16:9 aspect ratio on ::before for any element with a data-src attribute. As soon as the data-src becomes the src, the browser stops rendering ::before and the image’s natural aspect ratio takes over.

Here’s a demo:

See the Pen Image Aspect Ratio: ::before padding by James Steinbach (@jdsteinbach) on CodePen.

There are a couple drawbacks to this solution, however. First, it relies on CSS and HTML working together. Your stylesheet needs to have a declaration for each image aspect ratio you need to support. It would be much better if the template could insert an image without needing CSS edits.

Second, it doesn’t work in Safari 12 and below, or Edge, at the time of writing. That’s a pretty big traffic swatch to send poor layouts. To be fair, maintaining the aspect ratio is a bit of a progressive enhancement — there’s nothing “broken” about the final rendered page. Still, it’s much more ideal to solve the reflow problem and for images to render as expected.

Data URI (Base64) PNGs

Another way we attempted to preserve the aspect ratio was to inline data URI for the src. as PNG. Using png-pixel.com will help with the lift of all that base64-encoding with any dimensions and colors. This can go straight into the image’s src attribute in the HTML:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAQAAAA3fa6RAAAADklEQVR42mNkAANGCAUAACMAA2w/AMgAAAAASUVORK5CYII=" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

The inline PNG there has a 3:2 aspect ratio (the same aspect ratio as the final image). When src is replaced with the data-src value, the image will maintain its aspect ratio exactly like we want!

Here’s a demo:

See the Pen Image Aspect Ratio: inline base64 PNG by James Steinbach (@jdsteinbach) on CodePen.

And, yes, this approach also comes with some drawbacks. Although the browser support is much better, it’s complicated to maintain. We need to generate a base64 string for each new image size, then make that object of strings available to whatever templating tool that’s being used. It’s also not the most efficient way to represent this data.

I kept exploring and found a smaller way.

Combine SVG with base64

After exploring the inline PNG option, I wondered if SVG might be a smaller format for inline images and here’s what I found: An SVG with a viewBox declaration is a placeholder image with an easily editable native aspect ratio.

First, I tried base64-encoding an SVG. Here’s an example of what that looked like in my HTML:

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzIDInPjwvc3ZnPg==" data-src="//picsum.photos/900/600" alt="Lazy loading test image">

On small, simple aspect ratios, this is roughly equivalent in size to the base64 PNGs. A 1:1 ratio would be 114 bytes with base64 PNG and 106 bytes with base64 SVG. A 2:3 ratio is 118 bytes with base64 PNG and 106 bytes with base64 SVG.

However, using base64 SVG for larger, more complex ratios stay small, which is a real winner in file size. A 16:9 ratio is 122 bytes in base64 PNG and 110 bytes in base64 SVG. A 923:742 ratio is 3,100 bytes in base64 PNG but only 114b in base64 SVG! (That’s not a common aspect ratio, but I needed to test with custom dimensions with my client’s use case.)

Here’s a table to see those comparisons more clearly:

Aspect Ratio base64 PNG base64 SVG
1:1 114 bytes 106 bytes
2:3 118 bytes 106 bytes
16:9 122 bytes 110 bytes
923:742 3,100 bytes 114 bytes

The differences are negligible with simple ratios, but you can see how extremely well SVG scales as ratios become complex.

We’ve got much better browser support now. This technique is supported by all the big players, including Chrome, Firefox, Safari, Opera, IE11, and Edge, but also has great support in mobile browsers, including Safari iOS, Chrome for Android, and Samsung for Android (from 4.4 up).

Here’s a demo:

See the Pen Image Aspect Ratio: inline base64 SVG by James Steinbach (@jdsteinbach) on CodePen.

🏆 We have a winner!

Yes, we do, but stick with me as we improve this approach even more! I remembered Chris suggesting that we should not use base64 encoding with SVG inlined in CSS background-images and thought that advice might apply here, too.

In this case, instead of base64-encoding the SVGs, I used the “Optimized URL-encoded” technique from that post. Here’s the markup:

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3C/svg%3E" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

This is just a tad smaller than base64 SVG. The 1:1 is 106 bytes in base64 and 92 bytes when URL-encoding. 16:9 outputs 110 bytes in base64 and 97 bytes when URL-encoded.

If you’re interested in more data size by file and encoding format, this demo compares different byte sizes between all of these techniques.

However, the real benefits that make the URL-encoded SVG a clear winner are that its format is human-readable, easily template-able, and infinitely customizable!

You don’t need to create a CSS block or generate a base64 string to get a perfect placeholder for images where the dimensions are unknown! For example, here’s a little React component that uses this technique:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`

const lazyImage = ({url, width, height, alt}) => {
  return (
    <img
      src={placeholderSrc(width, height)}
      data-src={url}
      alt={alt} />
  )
}

See the Pen React LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

Or, if you prefer Vue:

See the Pen Vue LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

I’m happy to report that browser support hasn’t changed with this improvement — we’ve still got the full support as base64 SVG!

Conclusion

We’ve explored several techniques to prevent content reflow by preserving the aspect ratio of a lazy-loaded image before the swap happens. The best technique I was able to find is inlined and optimized URL-encoded SVG with image dimensions defined in the viewBox attribute. That can be scripted with a function like this:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`

There are several benefits to this technique:

  • Solid browser support across desktop and mobile
  • Smallest byte size
  • Human-readable format
  • Easily templated without run-time encoding calls
  • Infinitely extensible

What do you think of this approach? Have you used something similar or have a completely different way of handling reflow? Let me know!


Preventing Content Reflow From Lazy-Loaded Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/feed/ 25 279419
The Complete Guide to Lazy Loading Images https://css-tricks.com/the-complete-guide-to-lazy-loading-images/ https://css-tricks.com/the-complete-guide-to-lazy-loading-images/#comments Tue, 25 Sep 2018 14:32:56 +0000 http://css-tricks.com/?p=276700 Wondering how lazy loading images works? It's an important thing to understand as it can really speed up the loading of your page and reduce bandwidth usage both for your site and the user.


The Complete Guide to Lazy Loading Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Images are critical. Whether it is marketing banners, product images, or logos, it is impossible to imagine a website without images. Sadly though, images are often heavy files making them the single biggest contributor to the page bloat. According to the HTTP Archive’s State of Images report, the median page size on desktops is 1511 KB and images account for nearly 45% (650 KB) of that total.

That said, it’s not like we can simply do away with images. They’re too important to the overall user experience. Instead, we need to make our web pages load really fast with them. In this guide, we will cover all of the ins and outs of lazy loading images, a technique that helps improve the time it takes for a web page to load by deferring image loads until they are needed.

This post does a great job of covering the subject of lazy loading in detail and all the considerations, tooling, tech, etc. But it was written just before native lazy loading start becoming a thing, so when figuring out your solution to implement, you’d probably be smart to incorporate native lazy loading into it:

Before we dive right in, here is a sample video that demonstrates the concept. In short, a gray placeholder box is rendered on the page until it scrolls into view—at which point the actual image loads in place of the box.

Chapter 1: What is Lazy Loading?

We often associate the word “lazy” with avoiding work as long as possible, or the sheer act of wanting to do nothing at all.

Similarly, lazy loading defers the loading of resources on the page as long as they are not needed. Instead of loading them right away, which is what normally happens, we allow them to load later.

Lazy Loading is a set of techniques in web and application development that defers the loading of resources on a page to a later point in time—when those resources are actually needed instead of loading them up front. These techniques help in improving performance, better utilization of the device’s resources and reducing associated costs.

The technique of lazy loading can be applied to just about any resources on a page. For example, even a JavaScript file can be held back if it is best not to load it initially. Same deal for an image—load it when we need it.

We will stick to lazy loading images in this guide, but it’s good to know it can be applied to other assets.

Chapter 2: Why Lazy Load at All?

If the user never scrolls to the point of the page that contains the image, then the user will never see that image. It also never loads in the first place because, hey, it was never needed.

You may already start to see how this benefits both you and the user. Here are two of the advantages we get with lazy loading.

Performance Gains

The obvious benefit is that we get smaller web pages that load faster. Lazy loading reduces the number of images that need to be loaded on a page upfront. Fewer image requests mean fewer bytes to download. And fewer bytes to download means the page renders faster than if those bytes and requests were being made.

This ensures that any device on any network is able to download and process the remaining resources much faster. Hence, the time from request to render becomes smaller and the page becomes usable much earlier. Win-win!

Cost reduction

The second benefit is for you as a website administrator. Cloud hosting services, like Content Delivery Networks (CDNs) or web servers or storages, deliver images (or any asset for that matter) at a cost based on the number of bytes transferred. A lazy-loaded image may never get loaded if the user never reaches it. Thus, you may reduce the total bytes delivered on the page and ultimately save yourself a few pennies in the process. This is especially true for users that instantly bounce off a page or interact only with the top portion of the content.

The reduction in bytes transferred from your delivery network or server reduces delivery costs. This will become more apparent as we explore lazy loading in the coming sections.

Just how much will you save? You can find out which images are a candidate for lazy loading and how many bytes you can save on the initial page load by using the Google Lighthouse audit tool. This has a section dedicated for offscreen images. You can also use ImageKit’s website analyzer to identify if your website uses lazy loading or not apart from other critical image-related optimizations on your page.

Lazy loading is critical not only to good performance but also to deliver a good user experience. Since combining performance and user experience with lazy loading is important and challenging, we will continue to address this topic in more detail throughout this guide after we have looked at different ways to lazy load images.

Chapter 3: Lazy Loading Techniques for Images

There are two common ways that we load images to a page: the <img> tag and the CSS background-image property. We will first look at the more common of the two, the <img> tag and then move to CSS background images.

Lazy loading images in an image tag

Let’s start with the typical HTML markup for an image:

<img src="/path/to/some/image.jpg">

The markup for lazy loading images is pretty similar.

Step one is to prevent the image load upfront. The browser uses the src attribute of the tag to trigger the image load. It doesn’t matter if it is the first or the 1,000th image in your HTML. If the browser gets the src attribute, it will trigger the image to be downloaded, regardless of whether it is in or out of current view.

To defer the load, put the image URL in an attribute other than src. Let’s say we specify the image URL in the data-src attribute of the image tag. Now that src is empty and the browser won’t trigger the image load:

<img data-src="https://ik.imagekit.io/demo/default-image.jpg">

Now that we’re preventing the image from loading, we need to tell the browser when to load it. Otherwise, it will never happen. For this, we check that as soon as the image (i.e. its placeholder) enters the viewport, we trigger the load.

There are two ways to check when an image enters the viewport. Let’s look at both of them with working code samples.

Method 1: Trigger the image load using Javascript events

This technique uses event listeners on the scroll, resize and orientationChange events in the browser. The scroll event is pretty clear cut because it watches where the user is on a page as scrolling occurs. The resize and orientationChange events are equally important. The resize event occurs when the browser window size changes, whereas orientationChange gets triggered when the device is rotated from landscape to portrait, or vice versa.

We can use these three events to recognize a change in the screen and determine the number of images that become visible on the screen and trigger them to load accordingly.

When any of these events occur, we find all the images on the page that are deferred and, from these images, we check which ones are currently in the viewport. This is done using an image’s top offset, the current document top position, and window height. If an image has entered the viewport, we pick the URL from the data-src attribute and move it to the src attribute and the image will load as a result.

Note that we will ask JavaScript to select images that contain a lazy class. Once the image has loaded, we’ll remove the class because it no longer needs to trigger an event. And, once all the images are loaded, we remove the event listeners as well.

When we scroll, the scroll event triggers multiple times rapidly. Thus, for performance, we are adding a small timeout to our script that throttles the lazy loading function execution so it doesn’t block other tasks running in the same thread in the browser.

Here is a working example of this approach.

Note that the first three images in this example are loaded up front. The URL is present directly in the src attribute instead of the data-src attribute. This is essential for a good user experience. Since these images are at the top of the page, they should be made visible as soon as possible. There’s no need to wait for JavaScript to load them.

Method 2: Trigger the image load using the Intersection Observer API

The Intersection Observer API is relatively new. It makes it simple to detect when an element enters the viewport and take an action when it does. In the previous method, we had to bind events, keep performance in mind and implement a way to calculate if the element was in the viewport or not. The Intersection Observer API removes all that overhead by avoiding the math and delivering great performance out of the box.

Below is an example using the API to lazy load images. We attach the observer on all the images to be lazy loaded. Once the API detects that the element has entered the viewport, using the isIntersecting property, we pick the URL from the data-src attribute and move it to the src attribute for the browser to trigger the image load. Once this is done, we remove the lazy class from the image and also remove the observer from that image.

If you compare the image loading times for the two methods (event listeners vs. Intersection Observer), you will find that images load much faster using the Intersection Observer API and that the action is triggered quicker as well— and yet the site doesn’t appear sluggish at all, even in the process of scrolling. In the method involving event listeners, we had to add a timeout to make it performant, which has a slightly negative impact on the user experience as the image load is triggered with a slight delay.

However, like any new feature, the support for Intersection Observer API is not available across all browsers.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
5855No1612.1

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
11511511512.2-12.5

So, we need to fall back to the event listener method in browsers where the Intersection Observer API is not supported. We have taken this into account in the example above.

Chapter 4: Lazy Loading CSS Background Images

A common background image in CSS:

.my-class {
  background-image: url('/path/to/some/image.jpg');
  /* more styles */
}

CSS background images are not as straightforward as the image tag. To load them, the browser needs to build the DOM tree as well as the CSSOM tree to decide if the CSS style applies to a DOM node in the current document. If the CSS rule specifying the background image does not apply to an element in the document, then the browser does not load the background image. If the CSS rule is applicable to an element in the current document, then the browser loads the image.

Huh? This may seem complex at first, but this same behavior forms the basis of the technique for lazy loading background images. Simply put, we trick the browser into not applying the background-image CSS property to an element, till that element comes into the viewport.

Here is a working example that lazy loads a CSS background image.

One thing to note here is that the JavaScript code for lazy loading is still the same. We are still using the Intersection Observer API method with a fallback to the event listeners. The “trick” lies in the CSS.

We have an element with ID bg-image that has a background-image. However, when we add the lazy class to the element, we can override the background-image property by setting the value of it to none in the CSS.

Since an element with an ID and a class has higher specificity in CSS than an ID alone, the browser applies the property background-image: none to the element initially. When we scroll down, the Intersection Observer API (or event listeners, depending on which method you choose) detects that the image is in the viewport, it removes the lazy class from the element. This changes the applicable CSS and applies the actual background-image property to the element, triggering the load of the background image.

Chapter 5: Creating a Better User Experience With Lazy Loading

Lazy loading presents a great performance benefit. For an e-commerce company that loads hundreds of product images on a page, lazy loading can provide a significant improvement in initial page loads while decreasing bandwidth consumption.

However, a lot of companies do not opt for lazy loading because they believe it goes against delivering a great user experience (i.e. the initial placeholder is ugly, the load times are slow, etc.).

In this section, we will try to solve some concerns around user experience with lazy loading of images.

Tip 1. Use the Right Placeholder

A placeholder is what appears in the container until the actual image is loaded. Normally, we see developers using a solid color placeholder for images or a single image as a placeholder for all images.

The examples we’ve looked at so far have used a similar approach: a box with a solid light gray background. However, we can do better to provide a more pleasing user experience. Below are some two examples of using better placeholders for our images.

Dominant Color Placeholder

Instead of using a fixed color for the image placeholder, we find the dominant color from the original image and use that as a placeholder. This technique has been used for quite some time by Google in its image search results as well as by Pinterest in its grid design.

Example of Lazy Loading Images. Grid of images loads in with the primary color of the image behind them as a placeholder.
Pinterest uses the dominant color of the image as the background color for image placeholders. (Source)

This might look complex to achieve, but Manuel Wieser has an elegant solution to accomplishing this by scaling down the image to down to a 1×1 pixel and then scale it up to the size of the placeholder—a very rough approximation but a simple, no-fuss way to get a single dominant color. Using ImageKit, the dominant color placeholder can be obtained using a chained transform in ImageKit as shown below.

<!-- Original image at 400x300 -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image"> 

<!-- Dominant color image with same dimensions -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-1,h-1:w-400,h-300" alt="dominant color placeholder">

The placeholder image is just 661 bytes in size compared to the original image that is 12700 bytes—19x smaller. And it provides a nicer transition experience from placeholder to the actual image.

Here is a video demonstrating how this effect works for the user.

Low Quality Image Placeholder (LQIP)

We can extend the above idea of using a dominant color placeholder further. Instead of using a single color, we use a very low-quality, blurred version of the original image as the placeholder. Not only does it look good, but it also gives the user some idea about what the actual image looks like and the perception that the image load is in progress. This is great for improving the perceived loading experience. This technique has been utilized by the likes of Facebook and Medium.

LQIP image URL example using ImageKit:

<!-- Original image at 400x300 --> 
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image">

<!-- Low quality image placeholder with same dimensions --> 
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300,bl-30,q-50" alt="dominant color placeholder">

The LQIP is 1300 bytes in size, still almost 10x smaller than the original image and a significant improvement in terms of visual experience over any other placeholder technique.

Here is a video demonstrating how this effect works for the user.


It is clear that using either dominant color or LQIP placeholders provides a smoother transition from the placeholder to the actual image, gives the user an idea of what is to come in place of that placeholder, and improves loading perception.

Tip 2: Add Buffer Time for Images to Load

When we discussed different methods to trigger image loads, we checked for the point of time where the image enters the viewport, i.e. the image load is triggered when the top edge of the image placeholder coincides with the bottom edge of the viewport.

The problem with this is that users might scroll really fast through the page and the image will need some time to load and appear on the screen. Combined with throttling possibly further delaying the load, the user may wind up waiting a few milliseconds longer for the image to show up. Not great for user experience!

While we can get a pretty good user experience using the Intersection Observer API for performance and LQIP for smoother transitions, there is another simple trick that you can use to ensure that the images are always loaded completely when they enter the viewport : introduce a margin to the trigger point for images.

Instead of loading the image exactly when it enters the viewport, load it when it’s, let’s say, 500px before it enters the viewport. This provides additional time, between the load trigger and the actual entry in the viewport, for the images to load.

With the Intersection Observer API, you can use the root parameter along with the rootMargin parameter (works as standard CSS margin rule), to increase the effective bounding box that is considered to find the intersection. With the event listener method, instead of checking for the difference between the image edge and the viewport edge to be 0, we can use a positive number to add some threshold.

If you watch the following screencast closely, you’ll notice that the fifth image in the sequence is loaded when the third image is in view. Similarly, the sixth image is loaded when the fourth is in view, and so on. This way, we are giving sufficient time for the images to load completely and, in most cases, the user won’t see the placeholder at all.

If you didn’t notice earlier, in all our examples, the third image (image3.jpg) is always loaded upfront, even though it is outside the viewport. This was also done following the same principle:  load slightly in advance instead of loading exactly at the threshold for better user experience.

Tip 3: Avoid Content Reflow

This is another trivial point, which if solved, can help maintain a good user experience.

When there is no image, the browser doesn’t know the size it will take up. And if we do not specify it using CSS, then the enclosing container would have no dimensions, i.e. it will be read as 0x0 pixels.

When the image loads, the browser will drop it into the screen and reflow the content to fit it. This sudden change in the layout causes other elements to move around and it is called content reflow, or shifting. Michael Scharnagl goes into great depth explaining how this creates an unpleasant user experience.

This can be avoided by specifying a height and/or width for the enclosing container so that the browser can paint the image container with a known height and width. Later, when the image loads, since the container size is already specified and the image fits into that perfectly, the rest of the content around that container does not move.

Tip 4: Avoid Lazy Loading Every Image

This is a mistake that developers often make because it’s super tempting to think that deferring image loads is good all the time. But, like life itself, it is possible to have too much of a good thing. Lazy loading might reduce the initial page load, but it also might result in a bad user experience if some images are deferred when they should not be.

We can follow some general principles to identify which images should be lazy-loaded. For example, any image that is present in the viewport, or at the beginning of the webpage, should probably not be lazy-loaded. This applies to any header image, marketing banner, logos, or really anything that the user would see when initially landing on a page. Also, remember that mobile and desktop devices will have different screen sizes and hence a different number of images that will be visible on the screen initially. You’ll want to take the device that’s being used into account and decide which resources to load upfront and which to lazy load.

Another example is any image that is even slightly off the viewport in the initial load should not probably not be lazy-loaded. This is going by the principle discussed above—load slightly in advance. So, let’s say any image that is 500px or a single scroll from the bottom of the viewport can be loaded upfront as well.

One more example is if the page is short. It may be just a single scroll or a couple of scrolls, or perhaps there are less than five images outside the viewport. In these cases, you can probably leave lazy loading out altogether. It would not provide any significant benefit to the end-user in terms of performance and the additional JavaScript that you load on the page to enable lazy loading will offset any potential gain you get from it.

Chapter 5: Lazy Loading’s Dependency on JavaScript

The entire idea of lazy loading is dependent on JavaScript being enabled and available in the user’s browser. While most of your users will likely have JavaScript enabled, it is essential to plan for cases where it is not.

You could either show a message telling users why the images won’t load and encourage them to either use a modern browser or enable JavaScript.

Another route is to use the noscript tag. However, this approach comes with some gotchas. This question thread on Stack Overflow does a great job addressing these concerns and is a recommended read for anyone looking to address this set of users.

Since environments and implementation details can vary across browsers and devices, you might want to consider a tried and tested library for lazy loading rather than spinning something up from scratch.

Here is a list of popular libraries and platform-specific plugins that will allow you to implement lazy loading with minimal effort:

  • Yet Another Lazy Loader: This library uses the Intersection Observer API and falls back to event-based lazy loading for browsers that do not yet support it. This is great for just about any HTML element but unfortunately does not work on background images in CSS. The good news is that it supports IE back to version 11.
  • lazysizes: This is a very popular library with extensive functionality. It includes support for responsive image srcset and sizes attributes and provides superb performance even though it does not make use of the Intersection Observer API.
  • WordPress A3 Lazy Load: There are plenty of lazy loading WordPress plugins out there, but this one comes with a robust set of features, including a fallback when JavaScript is unavailable.
  • jQuery Lazy: A simple library that uses a jQuery implementation.
  • WeltPixel Lazy Loading Enhanced: A Magento 2 extension.
  • Magento Lazy Image Loader: Another Magento extension, for 1.x.
  • Shopify Lazy Image Plugin (paid): Enable lazy loading on a Shopify site.

Chapter 7: Testing Lazy Load

Once you have implemented lazy loading, you will likely want to check that it’s working as intended. The simplest way would be to open up the developer tools in your browser.

From there, go to Network > Images. When you refresh the page for the first time, you should only see loaded images in the list.

Then, as you start scrolling down the page, other image load requests would get triggered and loaded. You can also notice the timings for image load in the waterfall column in this view. It would help you identify image loading issues if any or issues in triggering the image load.

Another way would be to run the Google Chrome Lighthouse audit report on your page after you have implemented the changes and look for suggestions under the “Offscreen images” section.

Conclusion

We have covered a lot of ground about lazy loading images! Lazy loading—if implemented well—can have significant benefits on your site’s performance while reducing the overall page size and delivery costs, thanks to deferring unnecessary resources upfront.

So, what are you waiting for? Get started with lazy loading images now! And, if you need a little reminder of how this works, save a copy of the following infographic.

Lazy loading infographic preview.
Click for full version.

The Complete Guide to Lazy Loading Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-complete-guide-to-lazy-loading-images/feed/ 30 https://www.youtube.com/embed/CPmNHj9a0JI Lazy loading images - Single Color Placeholder nonadult 276700
A Native Lazy Load for the Web https://css-tricks.com/a-native-lazy-load-for-the-web-platform/ https://css-tricks.com/a-native-lazy-load-for-the-web-platform/#comments Mon, 27 Aug 2018 13:38:37 +0000 http://css-tricks.com/?p=275613 A new Chrome feature dubbed “Blink LazyLoad” is designed to dramatically improve performance by deferring the load of below-the-fold images and third-party <iframe>s.

The goals of this bold experiment are to improve the overall render speed of content that …


A Native Lazy Load for the Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A new Chrome feature dubbed “Blink LazyLoad” is designed to dramatically improve performance by deferring the load of below-the-fold images and third-party <iframe>s.

The goals of this bold experiment are to improve the overall render speed of content that appears within a user’s viewport (also known as above-the-fold), as well as, reduce network data and memory usage. ✨

If you’re looking to add lazy loading to your site’s media right now, you’d be smart to include these native lazy loading attributes, but as of this update (July 2019), support in stable browsers in non-existent still. You’ll want to look at doing more:

👨‍🏫 How will it work?

It’s thought that temporarily delaying less important content will drastically improve overall perceived performance.

If this proposal is successful, automatic optimizations will be run during the load phase of a page:

  • Images and iFrames will be analysed to gauge importance.
  • If they’re seen to be non-essential, they will be deferred, or not loaded at all:
    • Deferred items will only be loaded if the user has scrolled to the area nearby.
    • A blank placeholder image will be used until an image is fetched.

The public proposal has a few interesting details:

  • LazyLoad is made up of two different mechanisms: LazyImages and LazyFrames.
  • Deferred images and iFrames will be loaded when a user has scrolled within a given number of pixels. The number of pixels will vary based on three factors:
  • Once the browser has established that an image is located below the fold, it will issue a range request to fetch the first few bytes of an image to establish its dimensions. The dimensions will then be used to create a placeholder.

The loading attribute will allow authors to specify which elements should or should not be lazy loaded. Here’s an example that indicates that this content is non-essential:

<iframe src="ads.html" loading="lazy"></iframe>

There are three options:

  • on – Indicates a strong preference to defer fetching until the content can be viewed.
  • off – Fetch this resource immediately, regardless of view-ability.
  • auto – Let the browser decide (has the same effect as not using the loading attribute at all).

🔒 Implementing a secure lazy loading policy

Feature policy: LazyLoad will provide a mechanism that allows authors to force opting in or out of LazyLoad functionality on a per-domain basis (similar to how Content Security Policies work). There is a yet-to-be-merged pull request that describes how it might work.

🤔 What about backwards compatibility?

At this point, it is difficult to tell if these page optimizations could cause compatibility issues for existing sites.

Third-party iframes are used for a large number of purposes like ads, analytics or authentication. Delaying or not loading a crucial iFrame (because the user never scrolls that far) could have dramatic unforeseeable effects. Pages that rely on an image or iFrame having been loaded and present when onLoad fires could also face significant issues.

These automatic optimizations could silently and efficiently speed up Chrome’s rendering speed without any notable issues for users. The Google team behind the proposal are carefully measuring the performance characteristics of LazyLoad’s effects through metrics that Chrome records.

💻 Enabling Lazy Loading in Chrome

At the time of writing, native lazy loading is only available in Chrome Canary, behind two required flags:

  • chrome://flags/#enable-lazy-image-loading
  • chrome://flags/#enable-lazy-frame-loading

Flags can be enabled by navigating to chrome://flags in a Chrome browser.

📚 References and materials

Browser Support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
77119No7916.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
11511511516.4

👋 In closing

As we embark on welcoming the next billion users to the web, it’s humbling to know that we are only just getting started in understanding the complexity of browsers, connectivity, and user experience.


A Native Lazy Load for the Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-native-lazy-load-for-the-web-platform/feed/ 8 275613
Lazy Loading Gravatars in WordPress https://css-tricks.com/lazy-loading-gravatars-wordpress/ https://css-tricks.com/lazy-loading-gravatars-wordpress/#comments Tue, 04 Apr 2017 16:59:55 +0000 http://css-tricks.com/?p=253401 Most WordPress themes show user Gravatars in the comment threads. It’s a way of showing an image with the user, as associated by the email address used. It’s a nice touch, and almost an expected design pattern these days.

Every …


Lazy Loading Gravatars in WordPress originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Most WordPress themes show user Gravatars in the comment threads. It’s a way of showing an image with the user, as associated by the email address used. It’s a nice touch, and almost an expected design pattern these days.

Every one of those gravatars is an individual HTTP request though, like any other image. A comment thread with 50 comments means 50 HTTP requests, and they aren’t always particularly tiny files. Yeesh.

Let’s lazy load them.

The Concept

Lazy loading is the idea that you don’t even request the image at all (no HTTP request) unless the image is visible. Meaning that, through JavaScript, we’ve determined the image is visible.

Lazy loading means not loading those two images that are outside the browser window, until they become inside the browser window.

In order to stop those HTTP requests for not-yet-seen images, we need to get our hands directly on the markup. If there is an <img src=""> in the HTML, there is essentially no way to stop the browser from downloading that image as soon as it possibly can, seen or unseen. So, we need to remove that src, and put it back when we’re ready.

Woah, There

It’s worth a pause here because we’ve entered some murky territory.

By removing the src of these images, and only ever putting it back with JavaScript, we’ve decided that we’re willing to ship slightly invalid HTML and rely 100% on a script downloading and executing for these images to ever be seen.

I’m OK with that. Mostly because gravatars are just an enhancement anyway. It ain’t no big deal if they never show up. I’m not a hardliner most JavaScript debates, but this seems like a particularly clear case where we can lean on JavaScript without worry.

Altering the HTML

This is the change we’d be making:

<!-- Normal image. No beating the browser preloader. -->
<img src="https://gravatar.whatever..." alt="" />

<!-- Let's change to this, which won't download anything. -->
<img data-src="https://gravatar.whatever..." alt="" />

Although a missing src on the <img> is technically invalid HTML. It almost certainly doesn’t really matter in that it won’t affect how anything works. If the invalid HTML bugs, you could always toss a super minimal blank GIF data URL in there, like:

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" ... />

Using width and height attributes is probably a good idea too, to maintain layout and avoid reflow if and when the images to load.

Altering the HTML… in WordPress

But how do you change the HTML that WordPress spits out as part of a comment thread? Comments are slightly unusual in WordPress in that WordPress core gives you the HTML, it isn’t part of your theme like most of the other HTML is.

Likely, in your `comments.php` file, you’ll see this function:

<?php wp_list_comments(); ?>

Which spits out a pile of <li>‘s with your entire comment thread. Not a lot of opportunity there to be fiddling with the output of images. Except, we can! We can list a callback function in there:

<?php wp_list_comments('callback=csstricks_comment'); ?>

That callback is the name of a function we can toss in our `functions.php` file. Here’s an example of that function, which must return a <li>:

function csstricks_comment($comment, $args, $depth) {

  $GLOBALS['comment'] = $comment; ?>

  <li <?php comment_class(); ?>">

     <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="50" height="50" class="lazyload-gravatar" alt="User Avatar" data-src="<?php echo get_avatar_url(get_comment_author_email($comment_ID), array("size" => 160)); ?>">

     <?php comment_text(); ?>

  <?php # phantom </li> ?>

<?php }

That’s very simplified, but you can see what we’ve done. We replaced the src with the blank GIF, we’ve added a class name we’ll ultimately use in JavaScript to do the lazy loading, we’ve added a data-src to the actual gravatar, and we’re using width and height attributes for placeholding. Here’s my actual complete callback live right now on CSS-Tricks.

If we shipped this right now, sans any JavaScript work, we’d still have a perfectly functional comment thread, just with images that never load.

Now We’re Ready to Lazyload

The hard part is over. We’re perfectly set up to do lazyloading now. If we were to write a script, it would be like:

  1. Figure out the visible area of the browser window
  2. Figure out the position on the page of every image with class .lazyload-gravatar
  3. If any of those images are in the visible area, flop out the src with the value from data-src
  4. If the visible area of the browser window changes in any way, re-evaluate the above

We could set about writing that ourselves. And we could do it! But, and I’m sure you’re not surprised here, it’s a bit tricky and nuanced. Cross-browser concerns, performance concerns, does-it-work-on-mobile concerns, to name a few. This is the kind of thing I’m happy to lean on other’s work for, rather than roll myself.

Again, no surprise, there are loads of options to pick from. In my case, I’m happily using jQuery on CSS-Tricks, and I picked a jQuery-based on that looked pretty good to me:

The API is as simple as can be. After bundled up the lib with the rest of the libs I’m using, I just call:

$('.lazyload-gravatar').Lazy();

Look how nicely it works!

That’s an awful lot of saved HTTP requests and awful good for performance.

Makes you wish web standards and browsers would get together on this and make it a native feature.


Lazy Loading Gravatars in WordPress originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/lazy-loading-gravatars-wordpress/feed/ 10 253401
Lazy-Loading Disqus Comments https://css-tricks.com/lazy-loading-disqus-comments/ https://css-tricks.com/lazy-loading-disqus-comments/#comments Wed, 07 Dec 2016 13:56:38 +0000 http://css-tricks.com/?p=248615 Lately, I’ve been obsessed with optimizing performance through lazy-loading. Recently, I’ve written on how to lazy-load Google Maps and on how to lazy-load responsive Google Adsense. Now it’s time for Disqus, a service for embedding comments on your …


Lazy-Loading Disqus Comments originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Lately, I’ve been obsessed with optimizing performance through lazy-loading. Recently, I’ve written on how to lazy-load Google Maps and on how to lazy-load responsive Google Adsense. Now it’s time for Disqus, a service for embedding comments on your website. It’s a great service. It eliminates the headache of developing your own local commenting system, dealing with spam, etc. Recently, I’ve been working on implementing the widget in one of my projects.

The Problem

Layout-wise, comments usually play a secondary role. In many cases, comments are never seen at all by the visitors, because the don’t scroll down to where they are. But guess what? By default, they get loaded on the website no matter what. The wasted bandwidth is a problem.

Take a look at the technical implementation officially recommended by Disqus:

<div id="disqus_thread"></div>
<script>
  (function() {
    var d = document, s = d.createElement('script');
    s.src = '//username.disqus.com/embed.js';
    s.setAttribute('data-timestamp', +new Date());
    (d.head || d.body).appendChild(s);
  })();
</script>

Here’s what they say: “Place the following code where you’d like Disqus to load”. Say you’re a good developer and you usually insert all of the <script src="..."></script> fragments right before the closing tag </body>. Then, one fine day, you decided to implement Disqus comments on your website and placed the above code somewhere in the middle of the document where the commenting section was meant to be.

What happens? The very first JavaScript file to start downloading is username.disqus.com/embed.js. That does not necessarily mean it will be downloaded first, but it’s the first one in the line of JavaScript files that gets the browser’s attention. Shouldn’t the first be reserved for the main JavaScript file of your website? There are many things (like “sleeping” <button>‘s, etc.) that could go wrong when your main JavaScript file is late to load, especially if you were not following the principles of graceful degradation or progressive enhancement back then when you developed that website.

This also interferes with other external resources on your website, like images and CSS files. Imagine yourself using a smartphone under 2G network conditions and waiting for the comments widget to load because you came for a kitten photo.

I did a test. Turns out the Disqus widget with zero comments weighs 2.49 MB! A bunch of networks requests for JavaScript, CSS, image, and font files that in many cases unreasonably slow down serving the other, perhaps critical parts or functions of your website.

Disqus Comments: No Lazyload

The Solution: Tiny JavaScript Plugin

In order to be able to lazy-load Disqus, I developed a tiny JavaScript plugin which does the job. No matter where the comments zone is, above or below the viewport, it won’t get loaded if there isn’t any reason to:

Disqus Comments: Lazyloaded

The plugin itself is a tiny piece of JavaScript. I made two versions of it: vanilla and jQuery. I called it disqusLoader. You can grab the files here:

Here’s you you set it up. First, you need to insert an element into HTML where you want the comments section to be:

<div class="disqus"></div>

Then, initialize the plugin like this:

// vanilla
disqusLoader( '.disqus', { scriptUrl: '//username.disqus.com/embed.js' });

// jQuery
$.disqusLoader( '.disqus', { scriptUrl: '//username.disqus.com/embed.js' });

“That’s great, but what about Disqus-specific config,” you may ask. Sure, there’s one more argument available which accepts a Disqus-native value. There are also a few more plugin-related options:

var options =
{
  scriptUrl: '//username.disqus.com/embed.js',
  /*
    @type: string (url)
    @default: none
    @required
    URL of Disqus' executive JS file. The value is memorized on the first function call
    and ignored otherwise because Disqus allows only one instance per page at the time.
  */

  laziness: 1,
  /*
    @type: int (>=0)
    @default: 1
    Sets the laziness of loading the widget: (viewport height) * laziness . For example:
    0 - widget load starts when at the least a tiny part of it gets in the viewport;
    1 - widget load starts when the distance between the widget zone and the viewport is no more than the height of the viewport;
    2 - 2x viewports, etc.
  */

  throttle: 250,
  /*
    @type: int (milliseconds)
    @default: 250
    Defines how often the plugin should make calculations during the
    processes such as resize of a browser's window or viewport scroll.
    250 = 4 times in a second.
  */

  /*
    @type: function
    @default: none
    Disqus-native options. Check Disqus' manual for more information.
  */
  disqusConfig: function()
  {
    this.page.title       = 'Page Title';
    this.page.url         = 'http://url.to/your-website';
    this.page.identifier  = 'unique-identifier';
  }
};

// vanilla
disqusLoader( '.disqus', options );

// jQuery
$.disqusLoader( '.disqus', options );

Check it out for yourself:

View Demo

You can also contribute or follow the project on GitHub.

Disqus Callbacks

Callbacks are great because you can react to user’s actions. There’s only one kind of callback officially documented by Disqus. Looking at the source code of `embed.js` file you can see more pre-defined types of callbacks:

Disqus Comments: Callbacks List In The Source Code Of embed.js

However, looks like only two of them are enabled: onNewComment and onReady. Which is enough for a tiny but noticeable improvement: a loading indicator.

Complementary Loading Indication

Loading the Disqus widget usually consists of two parts:

  1. Loading the `embed.js` file
  2. Loading the inner assets and performing other types of network requests

Disqus itself takes care of the 2nd step which they indicate with the animated image. But what about the 1st step? There are many reasons why loading external JavaScript files could take tens of seconds. The catch here is that no matter what the network conditions are, users will still be informed there’s a commenting feature available on your site. User experience is in the details!

Disqus Comments: a Complementary Loading Indication

The technical approach is simple: a new HTML element and JavaScript callback function which helps to hide the element:

<div class="disqus-placeholder">Loading comments...</div>
<div class="disqus"></div>
// vanilla
disqusConfig: function()
{
  this.callbacks.onReady = [function()
  {
    var el = document.querySelector( '.disqus-placeholder' );
    if( el.classList )
      el.classList.add( 'is-hidden' ); // IE 10+
    else
      el.className += ' ' + 'is-hidden'; // IE 8-9
  }];
}

// jQuery
disqusConfig: function()
{
  this.callbacks.onReady = [function()
  {
    $( '.disqus-placeholder' ).addClass( 'is-hidden' );
  }];
}
.disqus-placeholder.is-hidden { display: none; }

You can see this in action on the demo page. But first, clean the browser’s cache and throttle the network speed.

Multiple Instances Or Sites At Once?

While working on the technique, I discovered a couple of important limitations that exist today…

It’s Impossible To Have Multiple Script Sources On a Single Page

Once the script file (e.g. //username.disqus.com/embed.js) loads, the global variable window.DISQUS is created, but only if it wasn’t set previously (which is a bad sign, but let’s dig deeper). So, I’ve done a test. I initialized the widget from the source script A. Then freed up some space for the future variable window.DISQUS = undefined and initialized the widget of the source B. However, the result was a mess: callback functions were fired multiple times, the comments got duplicated, etc. Obviously, the current codebase of Disqus is not designed to support multiple variables and to operate individually with each widget instances.

It’s Impossible To Have Multiple Widgets On a Single Page At Once

There’s a public JavaScript method function reset() available within the DISUQS object. If you’ve had any technical experience with Disqus, you may know that the widget is inserted into an element which has disqus_thread value for the id. I’ve done a test with two elements: loaded the widget within the first element, removed the ID attribute, and appended it to the second element. Finally, I called the reset function, expecting the second instance just to appear next to the first one. However, calling the function to load a new instance also destroys any previously initialized widgets. Unfortunately, today Disqus is designed only for a single instance at the given time.

It’s Possible To Reload Widget In Real-Time

There’s one good thing! Even though it is not possible to have multiple widget instances at once, you can still destroy the old ones and load in new ones. Let’s turn this theory into practice with probably the most typical situation: tabs. All you need to do is call the plugin each time the new tab is activated:

<div class="tabcontent" data-disqus-id="venus" data-disqus-title="Venus"></div>
<div class="tabcontent" data-disqus-id="earth" data-disqus-title="Earth"></div>
<div class="tabcontent" data-disqus-id="mars" data-disqus-title="Mars"></div>
// call this function every time a tab is clicked:

var initDisqus = function( content )
{
  disqusLoader( content,
  {
    scriptUrl:    '//username.disqus.com/embed.js',
    disqusConfig: function()
    {
      this.page.identifier  = content.getAttribute( 'data-disqus-id' );
      this.page.title     = content.getAttribute( 'data-disqus-title' );
    }
  });
}

You can see this in action or view the full code on the demo page.

Closing Thoughts

This post is not about the flaws in Disqus. It’s about the mistakes we developers make. Our world is full of tools and it is up to us how we use them. Even though these tools solve particular problems, they usually bring some others along if we don’t take the appropriate care when implementing them. Every choice to take the easiest way turns into lost users, decreased conversions, and increased bouncing rates. You can already lazy-load Disqus, Google Adsense, Google Maps, social media buttons, you can also develop and share custom techniques. Be responsible!


Lazy-Loading Disqus Comments originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/lazy-loading-disqus-comments/feed/ 13 248615