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ả 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 ta có ba tuỳ chọn để tải từng phần, có thể được sử dụng kết hợp để 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ị lazy yêu cầu trình duyệt tải hình ảnh ngay lập tức nếu hình ảnh ở 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 chúng.

Xem trường loading của MDN khả năng tương thích với trình duyệt bảng để biết chi tiết về 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 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à lưu người dùng đang tải hình ảnh mà họ có thể không bao giờ cuộn vào. Nếu bạn có một số lượng lớn hình ảnh và muốn đảm bảo rằng những người dùng trình duyệt không hỗ trợ tính năng tải từng phần sẽ được hưởng lợi 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 đoạn mã polyfill, chúng ta sử dụng JavaScript để kiểm tra xem các phần tử này có nằm trong tệp khung nhìn. Nếu có, các thuộc tính src (và đôi khi là srcset) sẽ là điền sẵn URL vào nội dung hình ảnh mong muốn.

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

Intersection Observer dễ 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 thay vì viết mã phát hiện chế độ hiển thị phần tử tẻ nhạt. Tất cả việc còn lại chỉ là 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ẽ dùng để 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 được 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ỗ chứa URL của hình ảnh mà bạn sẽ tải khi phần tử này 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 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
  }
});

Trong sự kiện DOMContentLoaded của tài liệu, tập lệnh này truy vấn DOM về tất cả Các phần tử <img> có lớp lazy. Nếu có thể sử dụng Trình quan sát giao lộ, 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 nhập 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 CSS background-image thuộc tính (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 pháp khác nếu muốn tải từng phần hình nền.

Không giống như các phần tử <img> tải bất kể về khả năng hiển thị, hành vi tải hình ảnh trong CSS được thực hiện với nhiều hơn suy đoán. Khi tài liệu và đối tượng CSS mô hìnhkết xuất hình ảnh cái cây được tạo, 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 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 này hiện đang tạo, trình duyệt sẽ không yêu cầu thông tin đó.

Hành vi suy đoán này có thể được 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 lệnh gọi hình nền. Điều này có thể khiến hình ảnh được tải xuống vào thời điểm cần thiết thay vì tải lúc 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 nền chính do một số CSS gọi ra. 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 visible thêm vào phần tử khi phần tử đó 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 (bằng Intersection Observer!), rồi thêm lớp visible vào Tại thời điểm đó, phần tử div.lazy-background sẽ 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 dạng thức 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 ứng dụng tải từng phần đầy đủ tính năng đang tải thư viện tải từng phần hình ảnh và iframe. Mẫu sử dụng khá tương tự như các mã ví dụ hiển thị ở đây, trong đó mã tự động liên kết với một lazyload trên các phần tử <img> và yêu cầu bạn chỉ định URL hình ảnh trong Thuộc tính data-src và/hoặc data-srcset, nội dung của các thuộc tính này được hoán đổi vào thuộc tính src và/hoặc srcset. Đoạn đường này sử dụng Giao lộ Observer (bạn có thể polyfill) và có thể được mở rộng bằng một số trình bổ trợ sang 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 lazysize.
  • vanilla-lazyload là nhẹ để tải từng phần hình ảnh, hình nền, video, iframe, và tập lệnh. Công cụ này tận dụng Intersection Observer, hỗ trợ hình ảnh thích ứng, và bật tính năng tải từng phần ở cấp trình duyệt.
  • lozad.js là một ứng dụng web đơn giản khác chỉ sử dụng Trình quan sát giao lộ. Như vậy, quảng cáo này đạt hiệu suất cao nhưng sẽ cần phải được chèn polyfill trước khi bạn có thể sử dụng trên các trình duyệt cũ hơn.
  • 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 react-lazyload. Trong khi không dùng Intersection Observer mà cung cấp một phương thức quen thuộc để chạy từng phần tải hình ảnh cho những người đã quen với việc phát triển ứng dụng bằng React.