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 dưới dạng 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ủa cả hai loại hình ảnh.

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 phần tử <img>. Với hình ảnh cùng dòng, chúng ta có 3 lựa chọn để tải từng phần. Bạn có thể sử dụng kết hợp các hình ảnh 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ả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ư các phần tử <iframe>. Giá trị của lazy sẽ yêu cầu trình duyệt tải hình ảnh ngay lập tức nếu hình ảnh đó đang nằm trong khung nhìn và tìm nạp các hình ảnh khác khi người dùng cuộn gần hình ảnh.

Xem trường loading của 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ả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 lưu những hình ảnh mà có thể họ sẽ không bao giờ cuộn đến. 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ợ tính năng tải từng phần sẽ mang lại lợi ích cho bạ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 dành cho web.

Sử dụng Trình quan sát giao điểm

Để chèn từng phần của các phần tử <img>, chúng tôi sử dụng JavaScript để kiểm tra xem các phần tử đó 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 sẵn URL đến nội dung hình ảnh mong muốn.

Nếu đã viết mã tải từng phần trước đây, có thể bạn đã 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 lại hiệu suất cao và hiệu quả hơn để kiểm tra chế độ hiển thị của phần tử thông qua Intersection Observer API.

Trình quan sát giao điểm dễ sử dụng và đọc hơn so với việc dựa vào nhiều trình xử lý sự kiện, vì bạn chỉ cần đăng ký một 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ử nhàm chán. Bạn chỉ cần quyết định việc cần làm khi một phần tử hiển thị. 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 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 phần 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à những thuộc tính phần giữ chỗ chứa URL cho hình ảnh mà bạn sẽ tải khi phần tử nằm trong khung nhìn.

Giờ hãy xem cách sử dụng Intersection Observer trong JavaScript để tải từng phần hình ảnh bằng cách sử dụ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 sẽ truy vấn DOM cho tất cả các phần tử <img> có lớp lazy. Nếu có Intersection Observer, hãy tạo một đối tượng tiếp nhận dữ liệu mới để chạy lệnh gọi lại khi các phần tử img.lazy nhập vào khung nhìn.

Giao lộ Observer có trong tất cả trình duyệt hiện đại. Do đó, việc sử dụng nó dưới dạng polyfill cho loading="lazy" sẽ đảm bảo rằng tính năng tải từng phần hoạt động cho hầu hết khách truy cập.

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 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 để 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ị nào, hành vi tải hình ảnh trong CSS được thực hiện với nhiều thông tin suy đoán hơn. Khi tạo tài liệu và mô hình đối tượng CSScây kết xuất, trình duyệt sẽ kiểm tra cách áp dụng CSS cho 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 quy tắc CSS 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 liệu đang được tạo, thì trình duyệt sẽ không yêu cầu tài liệu đó.

Hành vi suy đoán này có thể dùng để 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 giúp hình ảnh được tải xuống tại thời điểm cần thiết thay vì khi tải lúc đầ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ề 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 phần tử này 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ừ đây, hãy sử dụng JavaScript để kiểm tra xem phần tử có nằm trong khung nhìn hay không (với Intersection Observer!) và 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 đối với thời gian hiển thị nội dung lớn nhất (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 quá trình tải hình ảnh đến thời điểm thực sự cần thiết. Việc này có thể giúp cải thiện thời gian khởi động và giảm xử lý trên luồng chính bằng cách 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 Thời gian hiển thị nội dung lớn nhất (LCP) của trang web nếu bạn quá hào hứng với kỹ thuật này. Một điều bạn cần tránh là tải từng phần hình ảnh trong khung nhìn trong quá trình khởi động.

Khi sử dụng trình tải từng phần dựa trên JavaScript, bạn nê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 sử 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à quá trình tải những hình ảnh này sẽ bị trì hoãn do trình quét tải trước trình quét không thể tìm thấy những hình ảnh đó trong quá trình khởi động.

Ngay cả khi 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 cũng có thể kích hoạt ngược. Khi loading="lazy" được áp dụng cho một hình ảnh trong khung nhìn, hình ảnh đó sẽ bị trễ cho đến khi trình duyệt biết chắc chắn rằng nó đang nằm trong khung nhìn. Điều này có thể ảnh hưởng đến LCP của một trang.

Không bao giờ tải từng phần những hình ảnh hiển thị trong khung nhìn trong quá trình khởi động. Đó là một dạng sẽ ảnh hưởng tiêu cực đến LCP của trang web, từ đó ảnh hưởng tiêu cực đế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 rơi vào một tình huống không thể làm được – chẳng hạn như một nhóm người dùng đáng kể vẫn phụ thuộc vào các trình duyệt cũ – bạn có thể sử dụng các thư viện sau đây để 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, tải từng phần hình ảnh và iframe. Mẫu mà mã này sử dụng khá giống với các mã ví dụ ở đây, vì mã tự động liên kết với một lớp lazyload trên các phần tử <img> và 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, trong đó nội dung của các thuộc tính đó được hoán đổi thành src và/hoặc srcset tương ứng. Giao diện này sử dụng Giao diện quan sát (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 từng phần video. Tìm hiểu thêm về cách sử dụng kích thước từng phần.
  • vanilla-lazyload là một lựa chọn gọn nhẹ để tải từng phần hình ảnh, hình nền, video, iframe và tập lệnh. Lớp 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 đó, API này có hiệu suất cao, nhưng sẽ cần phải được điền vào ô trước khi 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 react-lazyload. Mặc dù không sử dụng Intersection Observer, nhưng cung cấp một phương thức tải từng phần hình ảnh quen thuộc cho những người đã quen với việc phát triển ứng dụng bằng React.