Hình ảnh tải từng phần

Hình ảnh có thể xuất hiện trên một trang web do nằm cùng dòng trong HTML dưới dạng phần tử <img> hoặc hình nền CSS. Trong bài đăng này, bạn sẽ tìm hiểu cách tải từng phần cả hai loại hình ảnh này.

Hình ảnh cùng dòng

Các đề xuất tải từng phần phổ biến nhất là hình ảnh dùng trong các phần tử <img>. Với hình ảnh cùng dòng, chúng tôi có 3 tuỳ chọn để tải từng phần. Bạn có thể sử dụng kết hợp các tuỳ chọn này để có khả năng tương thích tốt nhất trên các trình duyệt:

Sử dụng tính năng tải từng phần ở cấp trình duyệt

Cả Chrome và Firefox đều hỗ trợ tính năng tải từng phần bằng thuộc tính loading. Bạn có thể thêm thuộc tính này vào các phần tử <img> cũng như vào các phần tử <iframe>. Giá trị của lazy yêu cầu trình duyệt tải hình ảnh ngay lập tức nếu hình ảnh nằm trong khung nhìn, đồng thời yêu cầu trình duyệt tìm nạp các hình ảnh khác khi người dùng cuộn đến gần hình ảnh.

Hãy xem trường loading trong bảng khả năng tương thích với trình duyệt của MDN để biết thông tin chi tiết về tính năng hỗ trợ trình duyệt. Nếu trình duyệt không hỗ trợ tính năng tải từng phần, thì thuộc tính này sẽ bị bỏ qua và hình ảnh sẽ tải ngay lập tức, như bình thường.

Đối với hầu hết các trang web, việc thêm thuộc tính này vào hình ảnh cùng dòng sẽ giúp tăng hiệu suất và giúp người dùng tải hình ảnh mà họ có thể không bao giờ cuộn vào. Nếu có số lượng lớn hình ảnh và muốn đảm bảo rằng người dùng trình duyệt không hỗ trợ lợi ích tải từng phần, bạn sẽ cần kết hợp phương thức này với một trong các phương thức được giải thích tiếp theo.

Để tìm hiểu thêm, hãy xem bài viết Tải từng phần ở cấp trình duyệt cho web.

Sử dụng Trình quan sát giao lộ

Để tải từng phần của các phần tử <img> bằng tính năng polyfill, chúng tôi sử dụng JavaScript để kiểm tra xem chúng có nằm trong khung nhìn hay không. Nếu có, các thuộc tính src (và đôi khi là srcset) sẽ được điền URL đến nội dung hình ảnh mong muốn.

Nếu trước đây đã viết mã tải từng phần, thì bạn có thể đã hoàn thành nhiệm vụ của mình bằng cách sử dụng các trình xử lý sự kiện như scroll hoặc resize. Mặc dù phương pháp này tương thích nhất trên các trình duyệt, nhưng các trình duyệt hiện đại lại mang đến một phương pháp hiệu quả và hiệu quả hơn để kiểm tra chế độ hiển thị của phần tử thông qua Intersection Observer API.

Intersection Observer dễ sử dụng và đọc hơn so với mã dựa vào nhiều trình xử lý sự kiện, vì bạn chỉ cần đăng ký trình quan sát để theo dõi các phần tử thay vì viết mã phát hiện chế độ hiển thị phần tử tẻ nhạt. Bạn chỉ cần quyết định việc cần làm khi một phần tử xuất hiện. Giả sử mẫu đánh dấu cơ bản này cho các phần tử <img> được tải từng phần:

<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load-1x.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" alt="I'm an image!">

Có 3 yếu tố liên quan trong mã đánh dấu này mà bạn nên tập trung:

  1. Thuộc tính class là thuộc tính mà bạn sẽ chọn phần tử trong JavaScript.
  2. Thuộc tính src tham chiếu đến một hình ảnh giữ chỗ sẽ xuất hiện khi trang tải lần đầu tiên.
  3. Các thuộc tính data-srcdata-srcset là các thuộc tính phần giữ chỗ có chứa URL của hình ảnh bạn sẽ tải khi phần tử nằm trong khung nhìn.

Bây giờ, hãy xem cách sử dụng Intersection Observer trong JavaScript để tải từng phần hình ảnh bằng mẫu đánh dấu sau:

document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
});

Trên sự kiện DOMContentLoaded của tài liệu, tập lệnh này truy vấn DOM cho tất cả các phần tử <img> có lớp lazy. Nếu có thể sử dụng Intersection Observer, hãy tạo một trình quan sát mới để chạy lệnh gọi lại khi các phần tử img.lazy đi vào khung nhìn.

Intersection Observer hoạt động trên mọi trình duyệt hiện đại. Do đó, việc sử dụng thuộc tính này làm polyfill cho loading="lazy" sẽ đảm bảo hầu hết khách truy cập đều có thể sử dụng tính năng tải từng phần.

Hình ảnh trong CSS

Mặc dù thẻ <img> là cách phổ biến nhất để sử dụng hình ảnh trên trang web, nhưng hình ảnh cũng có thể được gọi thông qua thuộc tính CSS background-image (và các thuộc tính khác). Tính năng tải từng phần ở cấp trình duyệt không áp dụng cho hình nền CSS. Vì vậy, bạn cần cân nhắc các phương thức khác nếu có hình nền cần tải từng phần.

Không giống như các phần tử <img> tải bất kể chế độ hiển thị, hành vi tải hình ảnh trong CSS được thực hiện với nhiều suy đoán hơn. Khi tạo tài liệu và mô hình đối tượng CSS cũng như cây kết xuất, trình duyệt sẽ kiểm tra cách CSS được áp dụng cho một tài liệu trước khi yêu cầu tài nguyên bên ngoài. Nếu trình duyệt đã xác định một quy tắc CSS có liên quan đến tài nguyên bên ngoài không áp dụng cho tài liệu vì tài nguyên đó đang được tạo, thì trình duyệt sẽ không yêu cầu tài liệu đó.

Bạn có thể dùng hành vi suy đoán này để trì hoãn việc tải hình ảnh trong CSS bằng cách sử dụng JavaScript để xác định thời điểm một phần tử nằm trong khung nhìn và sau đó áp dụng một lớp cho phần tử đó để áp dụng kiểu gọi hình nền. Điều này khiến hình ảnh được tải xuống tại thời điểm cần thiết thay vì tại thời điểm tải ban đầu. Ví dụ: hãy lấy một phần tử chứa hình nền chính lớn:

<div class="lazy-background">
  <h1>Here's a hero heading to get your attention!</h1>
  <p>Here's hero copy to convince you to buy a thing!</p>
  <a href="/buy-a-thing">Buy a thing!</a>
</div>

Phần tử div.lazy-background thường chứa hình nền chính do một số CSS gọi. Tuy nhiên, trong ví dụ về cách tải từng phần này, bạn có thể tách riêng thuộc tính background-image của phần tử div.lazy-background thông qua một lớp visible được thêm vào phần tử khi nó nằm trong khung nhìn:

.lazy-background {
  background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}

.lazy-background.visible {
  background-image: url("hero.jpg"); /* The final image */
}

Tại đây, hãy sử dụng JavaScript để kiểm tra xem phần tử này có nằm trong khung nhìn không (bằng Intersection Observer!) rồi thêm lớp visible vào phần tử div.lazy-background tại thời điểm đó để tải hình ảnh:

document.addEventListener("DOMContentLoaded", function() {
  var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));

  if ("IntersectionObserver" in window) {
    let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          entry.target.classList.add("visible");
          lazyBackgroundObserver.unobserve(entry.target);
        }
      });
    });

    lazyBackgrounds.forEach(function(lazyBackground) {
      lazyBackgroundObserver.observe(lazyBackground);
    });
  }
});

Ảnh hưởng đến nội dung lớn nhất hiển thị (LCP)

Tải từng phần là một tính năng tối ưu hoá tuyệt vời, giúp giảm cả mức sử dụng dữ liệu tổng thể và tranh chấp mạng trong quá trình khởi động bằng cách trì hoãn việc tải hình ảnh đến thời điểm thực sự cần thiết. Cách này có thể cải thiện thời gian khởi động và giảm quá trình xử lý trên luồng chính nhờ giảm thời gian cần thiết cho việc giải mã hình ảnh.

Tuy nhiên, tải từng phần là một kỹ thuật có thể ảnh hưởng tiêu cực đến LCP nội dung lớn nhất hiển thị của trang web nếu bạn quá háo hức với kỹ thuật này. Một điều bạn nên tránh là tải từng phần hình ảnh trong khung nhìn khi khởi động.

Khi sử dụng trình tải từng phần dựa trên JavaScript, bạn sẽ cần tránh tải từng phần hình ảnh trong khung nhìn vì các giải pháp này thường dùng thuộc tính data-src hoặc data-srcset làm phần giữ chỗ cho các thuộc tính srcsrcset. Vấn đề ở đây là việc tải các hình ảnh này sẽ bị chậm trễ do trình quét tải trước trình duyệt không tìm thấy chúng trong khi khởi động.

Ngay cả việc sử dụng tính năng tải từng phần ở cấp trình duyệt để tải từng phần một hình ảnh trong khung nhìn vẫn có thể phản tác dụng. Khi loading="lazy" được áp dụng cho hình ảnh trong khung nhìn, hình ảnh đó sẽ bị trì hoãn cho đến khi trình duyệt biết chắc chắn nó nằm trong khung nhìn. Điều này có thể ảnh hưởng đến LCP của trang.

Không bao giờ hiển thị từng phần hình ảnh hiển thị trong khung nhìn khi khởi động. Đó là một mẫu sẽ ảnh hưởng tiêu cực đến LCP của trang web và từ đó ảnh hưởng đến trải nghiệm người dùng. Nếu bạn cần hình ảnh khi khởi động, hãy tải hình ảnh khi khởi động nhanh nhất có thể bằng cách không tải từng phần!

Thư viện tải từng phần

Bạn nên sử dụng tính năng tải từng phần ở cấp trình duyệt bất cứ khi nào có thể, nhưng nếu bạn thấy mình không thể giải quyết được vấn đề này, chẳng hạn như một nhóm người dùng đáng kể vẫn đang phụ thuộc vào các trình duyệt cũ hơn, thì bạn có thể dùng các thư viện sau để tải từng phần hình ảnh:

  • lazysizes là một thư viện tải từng phần đầy đủ tính năng giúp tải từng phần hình ảnh và iframe. Mẫu mà lớp này sử dụng khá giống với mã ví dụ minh hoạ ở đây, trong đó mẫu này tự động liên kết với một lớp lazyload trên các phần tử <img>, đồng thời yêu cầu bạn chỉ định URL hình ảnh trong các thuộc tính data-src và/hoặc data-srcset, nội dung trong đó sẽ được hoán đổi thành thuộc tính src và/hoặc srcset. Công cụ này sử dụng Intersection Observer (bạn có thể chèn polyfill) và có thể mở rộng bằng một số trình bổ trợ để thực hiện những việc như tải video từng phần. Tìm hiểu thêm về cách sử dụng lazysize.
  • vanilla-lazyload là một lựa chọn gọn nhẹ khi tải từng phần hình ảnh, hình nền, video, iframe và tập lệnh. Thư viện này tận dụng Intersection Observer, hỗ trợ hình ảnh thích ứng và cho phép tải từng phần ở cấp trình duyệt.
  • lozad.js là một tuỳ chọn gọn nhẹ khác chỉ sử dụng Intersection Observer. Do đó, lớp này sẽ mang lại hiệu suất cao, nhưng sẽ cần được chèn polyfill trước khi bạn có thể sử dụng trên các trình duyệt cũ.
  • Nếu bạn cần một thư viện tải từng phần dành riêng cho React, hãy cân nhắc sử dụng reac-lazyload. Mặc dù không sử dụng Intersection Observer, nhưng API này cung cấp một phương thức quen thuộc để tải từng phần hình ảnh cho những người đã quen phát triển ứng dụng bằng React.