Some ways with image loading

Table of Contents

Loading with html <picture>

This is the most basic way to load image with html, it's also the most flexible way to load image with different size for different screen size,

<picture>
  <source srcset="https://picsum.photos/200/300" media="(max-width: 600px)" />
  <source srcset="https://picsum.photos/400/600" media="(max-width: 900px)" />
  <img src="https://picsum.photos/600/900" alt="Flowers" />
</picture>

Advantages

  • Easy to use
  • Flexible
  • Can load different image for different screen size

Disadvantages

  • Can not load different image for different screen size with old browsers
  • Not easy to maintain
  • Need more images stored in server

Loading with javascript

Use Javascript to check the screen size, and load the appropriate image

// Define the different image sources for each screen size
const smallScreenImg = '1.jpg' // 1MB
const mediumScreenImg = '2.jpg' // 2MB
const largeScreenImg = '3.jpg' // 10MB

// Get the current screen width
const screenWidth = window.innerWidth

// Choose the appropriate image source based on the screen width
let imgSource
if (screenWidth < 600) {
  imgSource = smallScreenImg
} else if (screenWidth < 900) {
  imgSource = mediumScreenImg
} else {
  imgSource = largeScreenImg
}

// Get the image element
const imgElement = document.querySelector('#myImage')

// Set the src attribute of the image element to the appropriate image source
imgElement.setAttribute('src', imgSource)

Advantages

  • Can load different image for different screen size
  • Easy to maintain

Disadvantages

  • Need more images stored in server
  • Need to write more code
  • Need to load the image after the page is loaded

Loading with intersection observer

Use intersection observer to check if the image is in the viewport.

Suppose that we have 100 images in the page, so with normal way, the browser will load all 100 images at the same time, but with intersection observer, the browser will only load the image when it's in the viewport, so it will save a lot of bandwidth

Take a look at the following example

<style>
  body {
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  img {
    width: 200px;
    height: 200px;
  }
</style>
<body>
  <img data-src="https://picsum.photos/200/301" alt="" />
  <img data-src="https://picsum.photos/200/302" alt="" />
  <img data-src="https://picsum.photos/200/303" alt="" />
  <img data-src="https://picsum.photos/200/304" alt="" />
  <img data-src="https://picsum.photos/200/305" alt="" />
  <img data-src="https://picsum.photos/200/306" alt="" />
  <img data-src="https://picsum.photos/200/307" alt="" />
  <img data-src="https://picsum.photos/200/308" alt="" />
  <img data-src="https://picsum.photos/200/309" alt="" />
  <img data-src="https://picsum.photos/200/310" alt="" />
  <img data-src="https://picsum.photos/200/311" alt="" />
  <img data-src="https://picsum.photos/200/312" alt="" />
  <img data-src="https://picsum.photos/200/313" alt="" />
</body>

remember that we set the data-src attribute instead of src attribute, because we don't want the browser to load the image when the page is loaded

and here is the javascript code

const images = document.querySelectorAll('img') // các DOM cần quan sát, ở đây ta lấy tất cả hình ảnh

const options = {
  root: null, // browser window
  rootMargin: '0px', // root margin top, left, right, bottom = 0
  threshold: 0.5, // kéo xuống >50% thì hình bắt đầu load
}

// định nghĩa observer
const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry, idx) => {
    // xác định xem target nằm trong hay ngoài viewport,
    if (entry.isIntersecting) {
      const image = entry.target

      // khúc này là lấy cái giá trị trong data-src và set vào src nè
      image.src = image.dataset.src

      // hình nào load rồi thì mình k theo dõi nữa
      observer.unobserve(image)
    }
  })
}, options)

// quan sát tất cả các <img> DOM
images.forEach((image) => {
  observer.observe(image)
})

Explanation:

  • root: the element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.
  • rootMargin: margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. Defaults to all zeros.
  • threshold: Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible.
  • isIntersecting: true if the target element intersects with the intersection observer's root element or root viewport at any of its thresholds, and false otherwise.

Note, this technique not only applies to images, but can be applied to many other DOMs, depending on your choice to suit your intended use.