Boosting Web Performance: Mastering Lazy Loading Techniques for Optimal User Experience

Draft Disclaimer: Please note that this article is currently in draft form and may undergo revisions before final publication. The content, including information, opinions, and recommendations, is subject to change and may not represent the final version. We appreciate your understanding and patience as we work to refine and improve the quality of this article. Your feedback is valuable in shaping the final release.

Lazy load them all

description: Lazy load them all: only load what you need by the time you need it tags: beginners, webdev, tutorial, productivity, seo, javascript

Lazy loading in general

  • Definition on the series intro
  • Helper to lazy load anything
import hash from 'object-hash'
/**
 * Idempotent lazy load script
 * @param {object} options
 * @param {string} [options.type]
 * @param {string} [options.id]
 * @returns {Promise}
 */
export function lazyLoad (options) {
  return new Promise((resolve, reject) => {
    if (!options.type) {
      return reject(new Error('LazyLoad is expecting { type: string } as argument'))
    }
    if (!options.id) {
      options.id = `${options.type}${hash(options)}`
    }
    let element = document.querySelector(`#${options.id}`)
    if (element) {
      console.warn(`Element ${options.type} you are trying to lazy load already exists with id = ${options.id}`)
      return resolve(element)
    }
    element = document.createElement(options.type)
    Object.keys(options).forEach(key => {
      if (key !== 'type') {
        element.setAttribute(key, options[key])
      }
    })
    element.addEventListener('load', () => {
      resolve(element)
    })
    element.addEventListener('error', (e) => {
      reject(e)
    })
    document.body.append(element)
  })
}

Scripts & Styles

Scripts


/**
 * @param options
 * @returns {Promise}
 */
export function lazyLoadScript (options) {
  return lazyLoad({
    type: 'script',
    ...options
  })
}

Styles

/**
 * @param options
 * @returns {Promise}
 */
export function lazyLoadCss (options) {
  return lazyLoad({
    rel: 'stylesheet',
    type: 'link',
    ...options
  })
}

Perspectives

  • Todo:
    • Use an element on the page to know when to load the
  • Alternatives
    • prefetch <link rel="prefetch" href="<your-resource>"/>

Note that the lazyLoad function we defined above can be used to lazy load anything you want ie video, audio and so on. Page loads matter, as well as other metrics such as:

  • first contentfull paint Those metrics would make or break your user experience. Improving them could set you apart from your competition.

Images

Image lazy loading and their implications your SEO score

If you are always trying to reduce load time 10ms to 100ms, if optimizing your algorithms is important to you then this is for you because today I will be sharing with you how you can enhance your UI/UX by lazy loading your image

This can be part of

  • Be at the top of your SEO game
  • Your SEO rank performance +10
  • Image lazy loading and their implication

Summary

  • how image are fetched ?

how images are fetched ?

  • get a page [insert:visual] [insert:visual] as graph
  • browser gets sub resources (css, js, img) for you [insert:visual] as graph [insert:visual]

[insert:page: ten sections page]

Sum up: You are asking for one page but you're doing (10 + 1) network requests

First improvement attempt

  • defer, async

Defer or loading the image content of our site can improve our first render

  • improve load time
    • bots care about content and how fast it is delivered
    • first paint, first meaningful paint
    • important for SEO

How to ?

Load the image only when needed, only when image come into view. No need to load something we may not need.

Listener to window scroll

  • not viable solution. Too much computing
// get the elements once page loads
let $elements = document.querySelectorAll('.element')

window.addEventListener('scroll', () => {
  $elements.forEach($element => {
    if ($element.getBoundingClientRect().top < window.innerHeight) {
      // element is visible
      $element.setAttribute('src', $element.getAttribute('data-src'))
      $element.addEventListener('load', () => {
        // element is now available
        // add class for nice css transition
      })
      $element.addEventListener('error', () => {
        // may be interesting to catch this error when network image is not available or failed to load for any server issue
      })
      $element.removeAttribute('data-src')
    }

  })
})

Listener to window scroll - improvement

Debounce listener to scroll.

Use Web api new InteractionObserver

Realize that even when all image are loaded, we are still listening to the window scroll. This is when interaction observer come in handy with interaction observer

  const defaultOptions = {
    attribute: 'data-src',
    onVisibleClass: 'visible',
    removeOnVisible: true,
  }
  options = Object.assign({}, defaultOptions, options)

  const io = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // element is visible
        const element = entry.target
        const src = element.getAttribute(options.attribute)
        element.setAttribute('src', src)
        element.classList.add(options.onVisibleClass)
        if (options.removeOnVisible) {
          observer.disconnect()
        }
      }
    })
  })

  const $element = options.selector instanceof HTMLElement ? options.selector : document.querySelector(options.selector)
  io.observe($element)

Wrap up

  • pros
    • improve first render time
  • cons
    • UI wise problem: flickering
      • reflow coming from image render. Image is taking space it did not have in first place since it had no width nor height
    • solution needs javascript
      • no javascript then you're content won't show

Comments

This also applies to reviews or any non critical information on page load.

If you have a blog with comments at the bottom, or any other dynamic contents that are not meaningful to your content, then you should consider lazy loading it. That of course apply to a comment section of a blog article. Besides, you don't necessarily want your comments showing up on search engine like Google as coming from you

Insert Amazing blog post content with comment that is hateful

This is also applicable to your reviews section if you are an ecommerce website

General advices

  • bundle your resources (css, js, img, fonts) in one file - If you're using font-awesome or glyphicons you can use sprite to group all your images
  • otherwise lazy load them - only load them when need

Wrap up

  • pros
    • improve first render time
  • cons
    • UI wise problem: flickering
      • reflow coming from image render. Image is taking space it did not have in first place since it had no width nor height
    • solution needs javascript
      • no javascript then you're content won't show