4 cách triển khai Lazy Loading Images

Kỹ thuật lazy loading được áp dụng rất nhiều trong thực tế, nó giúp website tải nhanh hơn, giảm băng thông cho server, từ đó tăng trải nghiệm người dùng. Bài viết này, mọi người hãy cùng mình tìm hiểu về kỹ thuật này và cách triển khai nó nhé.

Let’s get started !

Lazy loading images

Kỹ thuật Lazy Loading là gì ?

Lazy loading (Tải chậm, tải lười biếng) là một kỹ thuật được dùng để ngăn trình duyệt tải tất cả các tài nguyên của website cùng một lúc. Chỉ khi người dùng thật sự cần đến thì mới tải.

Kỹ thuật này được áp dụng cho tất cả các loại tài nguyên từ Javascript, css, nội dung, đến hình ảnh. Bài viết này mình sẽ tập trung về lazy loading images, các tài nguyên kia cũng tương tự cách làm như thế.

Ưu điểm:

  • Tốc độ tải trang nhanh hơn đáng kể.
  • Cải thiện trải nghiệm người dùng lên.
  • Tiết kiệm được rất nhiều băng thông cho hosting.

Cách 1: Bắt sự kiện scroll

Trình duyệt sẽ load bức ảnh nếu tìm thấy thuộc tính src trong thẻ img (trừ trường hợp ở cách 3) hoặc thuộc tính background-image trong css.

Vì vậy, nguyên lý cách này rất đơn giản:

  • Không dùng thuộc tính src, thay vào đó dùng một thuộc tính data-src (hoặc data-gì-đó) để chứa link của image.
  • Nếu không dùng thẻ img mà dùng background-image css, thì ban đầu đặt background-image: none
  • Bắt sự kiện scroll của document.
  • Khi bức ảnh tiến vào khung nhìn của màn hình thì ta chép thuộc tính data-src qua src (Hoặc đổi background-image: none thành url(‘src’).
  • Xoá sự kiện scroll khi đã tải tất cả ảnh.
<style>
  .grid {
    max-width: 1140px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
  }

  img {
    width: 100%;
    height: 450px;
  }
</style>

<div class="grid">
  <img class="lazy-load" data-src="http://lorempixel.com/450/450" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/460" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/470" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/480" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/490" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/420" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/40" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/450/440" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/420/450" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/451/450" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/452/450" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/454/451" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/454/452" alt="Photos" />
  <img class="lazy-load" data-src="http://lorempixel.com/454/453" alt="Photos" />
</div>
document.addEventListener("DOMContentLoaded", function () {
    let timeout = null;
    let lazyImgList = document.querySelectorAll('img.lazy-load');
    const windowHeight = window.innerHeight; // độ cao màn hình

    const lazyLoadImage = () => {
      // áp dụng kỹ thuật debounce để giảm số lần thực thi hàm khi scroll
      if (timeout) {
        clearTimeout(timeout);
      }

      timeout = setTimeout(() => {
        const windowYOffset = window.pageYOffset; // vị trí scroll hiện tại

        lazyImgList.forEach((img, index) => {
          // Load ảnh khi ảnh bước vào khung nhìn màn hình
          // Bạn cũng có thể load nó trước khi vào view-port để tăng UX
          if (img.offsetTop <= windowYOffset + windowHeight) {
            img.src = img.dataset?.src; // Chép link ảnh qua thuộc tính src
            img.classList.remove('lazy-load');
          }
        });
        
        // Cập nhật lại list
        lazyImgList = document.querySelectorAll('img.lazy-load');

        // Xoá event khi đã load tất cả các ảnh
        if (lazyImgList.length === 0) {
          document.removeEventListener('scroll', lazyLoadImage);
          return;
        }
      }, 30);
    }

    // load lần đầu nếu ảnh ở trên cùng trước khi người dùng scroll
    lazyLoadImage();
    lazyImgList.length && document.addEventListener('scroll', lazyLoadImage);
})

Tuy nhiên, chúng ta không nên dùng cách này, bạn chỉ nên dùng nó khi thật sự cần code cho các trình duyệt cũ, nếu không thì nên tham khảo các cách bên dưới nhé. Vì cách này có rất nhiều nhược điểm:

  • Phụ thuộc nhiều vào JavaScript
  • Xử lý scroll không tốt sẽ ảnh hưởng nhiều đến hiệu năng.
  • Trang có thể bị giật lag khi tải ảnh.

Cách 2: Dùng Intersection Observer API

Intersection Observer API cung cấp các API giúp chúng ta tạo ra những giao điểm trên màn hình. Nó sẽ quan sát trình duyệt một cách bất đồng bộ, khi người dùng scroll đến giao điểm đấy thì nó sẽ chạy chức năng mà chúng ta đã định nghĩa.

Cách này cũng sẽ tối ưu hơn nhiều so với cách 1, vì chúng ta không cần phải viết, tính toán cho sự kiện scroll. Mà trình duyệt sẽ tự quan sát và thực thi.

Ứng dụng của Intersection Observer API rất nhiều, điển hình là Infinite scroll, Lazy loading.

// HTML, CSS giống ở cách 1
document.addEventListener("DOMContentLoaded", function () {
    const imgLazyObserver = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // Chép link ảnh qua src
          entry.target.src = entry.target.dataset?.src;

          // bỏ theo dõi bức ảnh này
          observer.unobserve(entry.target);
        }
      });
    }, {
      // Chạy callback ở trên khi ảnh vừa vào view-port
      threshold: 0
    });

    document.querySelectorAll('img.lazy-load').forEach(img => { imgLazyObserver.observe(img) });
})

Cách 3: Dùng thuộc tính loading của thẻ img

Với cách này, bạn chỉ cần thêm thuộc tính loading="lazy" vào thẻ img là được.

Nhìn có vẻ đơn giản, tuy nhiên cách này rất hiệu quả, nhanh và gọn nhẹ. Bạn không cần phải thêm bất kỳ một dòng code javascript nào nữa.

Nếu loading="eager" thì trình duyệt sẽ tải bức ảnh đó ngay lặp tức nhé (mặc định). Tham khảo W3School.

<style>
  .grid {
    max-width: 1140px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
  }

  img {
    width: 100%;
    height: 450px;
  }
</style>
<div class="grid">
  <img src="http://lorempixel.com/350/350" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/360" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/370" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/380" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/390" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/320" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/330" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/350/340" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/320/350" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/351/350" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/352/350" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/353/350" loading="lazy" alt="Photos" />
  <img src="http://lorempixel.com/354/350" loading="lazy" alt="Photos" />
</div>

Tuy nhiên, một điểm yếu của cách này là nhiều trình duyệt cho hỗ trợ (Safari chỉ mới thử nghiệm). Vì thế, nếu dùng thì bạn nên thêm polyfill cho các trình duyệt chưa support nhé. Và cách này cũng không hỗ trợ khi dùng background-image css.

Lazy loading image attribute
Theo: Caniuse

Cách 4: Dùng thư viện có sẵn

Những cách trên để cho chúng ta hiểu được nguyên tắc, cách thức hoạt động của Lazy loading. Tuy nhiên, bạn có thể hoặc nên sử dụng các thư viện có sẵn để tiết kiệm thời gian và chính xác hơn nếu như logic code của bạn không quá chuyên biệt và phức tạp.

Một vài thư viện hỗ trợ việc này như:

Tạm kết

Lazy loading là một kỹ thuật không thể thiếu trong các website hiện nay. Tuy nhiên, bạn cũng nên cân nhắc nếu trang web của bạn không quá nhiều ảnh thì cũng không nên dùng, và những bức ảnh đầu tiên của trang cũng không nên lazy load nó nhé. Mong rằng bài viết này giúp mọi người hiểu hơn về kỹ thuật lazy loading này.

Cảm ơn mọi người đã đọc bài viết ❤

Bạn có thể tham khảo thêm:

1 Comment

  1. Trần Đăng Khoa 19/09/2021

Để lại một bình luận nhé