Các phương pháp hay nhất cho tính năng tải từng phần

Mặc dù hình ảnh và video tải từng phần có các lợi ích tích cực và có thể đo lường được về hiệu suất, nhưng bạn không nên xem nhẹ việc này. Nếu bạn làm sai thì có thể những hậu quả không mong muốn. Do đó, bạn cần phải lưu ý những mối lo ngại sau.

Chú ý đến nếp gấp

Bạn có thể muốn tải từng phần từng tài nguyên phương tiện trên trang bằng JavaScript, nhưng bạn cần phải chống lại sức hấp dẫn này. Mọi thứ nằm trong màn hình đầu tiên đều không được tải từng phần. Những tài nguyên đó phải được coi là tài sản quan trọng và do đó phải được tải như bình thường.

Tính năng tải từng phần trì hoãn quá trình tải tài nguyên cho đến khi DOM tương tác khi các tập lệnh đã tải xong và bắt đầu thực thi. Đối với hình ảnh dưới màn hình đầu tiên, điều này là bình thường, nhưng bạn nên tải các tài nguyên quan trọng trong màn hình đầu tiên bằng phần tử <img> tiêu chuẩn để chúng hiển thị sớm nhất có thể.

Đương nhiên, hiện nay đường ranh giới phần hiển thị không rõ ràng đến vậy khi người dùng xem trang web trên rất nhiều màn hình với kích thước khác nhau. Thành phần nằm trong màn hình đầu tiên trên máy tính xách tay cũng có thể nằm bên dưới trên thiết bị di động. Không có lời khuyên cụ thể nào về cách giải quyết vấn đề này một cách tối ưu trong mọi tình huống. Bạn sẽ cần tiến hành kiểm kê các tài sản quan trọng trên trang và tải những hình ảnh đó theo cách thông thường.

Ngoài ra, có thể bạn không nên quá nghiêm ngặt về đường ranh giới phần hiển thị đến mức ngưỡng kích hoạt tính năng tải từng phần. Tốt hơn là bạn nên thiết lập một vùng đệm một khoảng cách dưới màn hình đầu tiên để hình ảnh bắt đầu tải tốt trước khi người dùng cuộn vào khung nhìn. Ví dụ: API Intersection Observer cho phép bạn chỉ định một thuộc tính rootMargin trong đối tượng tuỳ chọn khi bạn tạo một thực thể IntersectionObserver mới. Điều này cung cấp một cách hiệu quả cho các phần tử một vùng đệm, kích hoạt hành vi tải từng phần trước khi phần tử nằm trong khung nhìn:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

Nếu giá trị của rootMargin có vẻ giống với các giá trị mà bạn chỉ định cho thuộc tính margin của CSS, thì đó là do giá trị đó! Trong trường hợp này, lề dưới của phần tử được quan sát (theo mặc định là khung nhìn của trình duyệt, nhưng bạn có thể thay đổi thành một phần tử cụ thể bằng thuộc tính root) được mở rộng thêm 256 pixel. Điều đó có nghĩa là hàm callback sẽ thực thi khi một phần tử hình ảnh nằm trong phạm vi 256 pixel của khung nhìn và hình ảnh sẽ bắt đầu tải trước khi người dùng thực sự nhìn thấy.

Để đạt được hiệu quả tương tự trong các trình duyệt không hỗ trợ Intersection Observe, hãy dùng mã xử lý sự kiện cuộn và điều chỉnh bước kiểm tra getBoundingClientRect để bổ sung một vùng đệm.

Dịch chuyển bố cục và phần giữ chỗ

Nội dung nghe nhìn tải từng phần có thể gây ra hiện tượng dịch chuyển trong bố cục nếu không sử dụng phần giữ chỗ. Những thay đổi này có thể gây mất phương hướng cho người dùng và kích hoạt các thao tác bố cục DOM tốn kém tiêu tốn tài nguyên hệ thống và góp phần gây ra hiện tượng giật. Ở mức tối thiểu, hãy cân nhắc sử dụng một phần giữ chỗ màu đồng nhất có cùng kích thước với hình ảnh mục tiêu, hoặc các kỹ thuật như LQIP hoặc SQIP để gợi ý nội dung của mục nội dung đa phương tiện trước khi tải.

Đối với thẻ <img>, ban đầu, src phải trỏ đến một phần giữ chỗ cho đến khi thuộc tính đó được cập nhật bằng URL hình ảnh cuối cùng. Sử dụng thuộc tính poster trong phần tử <video> để trỏ đến hình ảnh giữ chỗ. Ngoài ra, hãy sử dụng các thuộc tính widthheight trên cả hai thẻ <img><video>. Điều này đảm bảo rằng việc chuyển đổi từ phần giữ chỗ sang hình ảnh cuối cùng sẽ không làm thay đổi kích thước kết xuất của phần tử khi nội dung nghe nhìn tải.

Độ trễ khi giải mã hình ảnh

Việc tải hình ảnh lớn trong JavaScript và thả chúng vào DOM có thể liên kết luồng chính, khiến giao diện người dùng không phản hồi trong một khoảng thời gian ngắn khi quá trình giải mã diễn ra. Giải mã không đồng bộ hình ảnh bằng phương thức decode trước khi chèn hình ảnh vào DOM có thể làm giảm hiện tượng giật này, nhưng hãy lưu ý: Tính năng này hiện chưa có ở mọi nơi và khiến logic tải từng phần thêm phức tạp. Nếu muốn sử dụng mã này, bạn cần phải kiểm tra. Dưới đây cho thấy cách bạn có thể sử dụng Image.decode() với tính năng dự phòng:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

Hãy truy cập đường liên kết đến CodePen này để xem mã tương tự như ví dụ thực tế này. Nếu hầu hết hình ảnh đều khá nhỏ, thì điều này có thể không giúp ích nhiều cho bạn, nhưng chắc chắn có thể giúp giảm hiện tượng giật khi tải từng phần hình ảnh lớn và chèn chúng vào DOM.

Khi nội dung không tải

Đôi khi, các tài nguyên nội dung nghe nhìn không tải được vì lý do này hay lý do khác và sẽ xảy ra lỗi. Khi nào việc này có thể xảy ra? Điều này còn phụ thuộc, nhưng sau đây là một trường hợp giả định cho bạn: Bạn có chính sách lưu HTML vào bộ nhớ đệm trong một khoảng thời gian ngắn (ví dụ: 5 phút) và người dùng truy cập trang web hoặc người dùng đã để một thẻ cũ mở trong một thời gian dài (ví dụ: vài giờ) và quay lại để đọc nội dung. Tại một thời điểm nào đó trong quá trình này, sẽ xảy ra việc triển khai lại. Trong quá trình triển khai này, tên của tài nguyên hình ảnh sẽ thay đổi do việc tạo phiên bản dựa trên hàm băm hoặc sẽ bị xoá hoàn toàn. Vào thời điểm người dùng tải từng phần hình ảnh, tài nguyên sẽ không có sẵn nên sẽ không hoạt động.

Mặc dù đây là trường hợp tương đối hiếm, nhưng bạn nên lập kế hoạch sao lưu nếu không tải được từng phần. Đối với hình ảnh, một giải pháp như vậy có thể có dạng như sau:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

Những việc bạn quyết định làm trong trường hợp xảy ra lỗi phụ thuộc vào ứng dụng của bạn. Ví dụ: bạn có thể thay thế khu vực giữ chỗ hình ảnh bằng một nút cho phép người dùng cố gắng tải lại hình ảnh, hoặc đơn giản là hiển thị thông báo lỗi trong vùng giữ chỗ hình ảnh.

Cũng có thể phát sinh các tình huống khác. Dù bạn làm gì, bạn cũng nên báo hiệu cho người dùng khi xảy ra lỗi và có thể đưa ra biện pháp xử lý nếu có vấn đề xảy ra.

Tính khả dụng của JavaScript

Bạn không nên cho rằng JavaScript luôn có sẵn. Nếu bạn định tải từng phần hình ảnh, hãy cân nhắc việc cung cấp mã đánh dấu <noscript> để hiển thị hình ảnh trong trường hợp không có JavaScript. Ví dụ đơn giản nhất có thể về tính năng dự phòng bao gồm việc sử dụng các phần tử <noscript> để phân phát hình ảnh nếu JavaScript tắt:

Tôi là hình ảnh!

Nếu JavaScript bị tắt, người dùng sẽ thấy cả hình ảnh phần giữ chỗ và hình ảnh có trong các phần tử <noscript>. Để khắc phục vấn đề này, hãy đặt một lớp no-js trên thẻ <html> như sau:

<html class="no-js">

Sau đó, hãy đặt một dòng tập lệnh cùng dòng trong <head> trước khi bất kỳ biểu định kiểu nào được yêu cầu thông qua thẻ <link>. Thẻ này sẽ xoá lớp no-js khỏi phần tử <html> nếu JavaScript đang bật:

<script>document.documentElement.classList.remove("no-js");</script>

Cuối cùng, hãy sử dụng một số CSS để ẩn các phần tử có lớp lazy khi không có JavaScript:

.no-js .lazy {
  display: none;
}

Điều này không ngăn tải hình ảnh giữ chỗ, nhưng kết quả sẽ tốt hơn. Những người đã tắt JavaScript nhận được nhiều lợi ích hơn hình ảnh giữ chỗ, hiệu quả hơn so với phần giữ chỗ và hoàn toàn không có nội dung hình ảnh có ý nghĩa.