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 mang lại những lợi ích tích cực và đ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, có thể có những hậu quả không mong muốn. Do đó, bạn cần phải lưu ý những vấn đề sau đây.

Chú ý đến màn hình đầu tiên

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

Tính năng tải từng phần sẽ trì hoãn quá trình tải tài nguyên cho đến khi DOM có tính tương tác khi 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 thì không sao, nhưng các tài nguyên quan trọng trong màn hình đầu tiên phải được tải bằng phần tử <img> chuẩn để chúng hiển thị càng sớm càng tốt.

Đương nhiên, hiện nay, nếp gấp nằm không rõ ràng khi các trang web được xem trên quá nhiều màn hình ở các kích thước khác nhau. Những thành phần nằm trong màn hình đầu tiên trên máy tính xách tay có thể nằm dưới màn hình đầu tiên trên thiết bị di động. Không có lời khuyên nào đi đúng hướng để 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, bạn không nên quá nghiêm ngặt đối với đường gập như ngưỡng kích hoạt tính năng tải từng phần. Lý tưởng hơn cho mục đích của bạn là thiết lập 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 trước khi người dùng cuộn vào khung nhìn. Chẳng hạn như API Intersection Observer cho phép bạn chỉ định thuộc tính rootMargin trong đối tượng tuỳ chọn khi tạo thực thể IntersectionObserver mới. Cách này mang lại 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 trông giống với các giá trị bạn chỉ định cho thuộc tính CSS margin, thì đó là do! Trong trường hợp này, lề dưới cùng của phần tử được quan sát (khung nhìn của trình duyệt theo mặc định, nhưng có thể thay đổi lề này thành một phần tử cụ thể bằng cách sử dụng thuộc tính root) sẽ đượ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ợ Giao diện quan sát, hãy sử dụng mã xử lý sự kiện cuộn và điều chỉnh tuỳ chọn kiểm tra getBoundingClientRect để thêm một vùng đệm.

Thay đổi bố cục và phần giữ chỗ

Phương tiện tải từng phần có thể gây ra hiện tượng thay đổi bố cục nếu không sử dụng phần giữ chỗ. Những thay đổi này có thể làm người dùng mất phương hướng và kích hoạt các thao tác bố cục DOM tốn kém làm tiêu tốn tài nguyên hệ thống và góp phần gây ra tình trạng giật. Ở mức tối thiểu, hãy cân nhắc sử dụng 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 giúp 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 sẽ 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 thuộc tính widthheight trên cả 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 các hình ảnh lớn trong JavaScript và thả các hình ảnh đó 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 trong khi quá trình giải mã diễn ra. Việc giải mã không đồng bộ hình ảnh bằng phương thức decode trước khi chèn các hình ảnh đó vào DOM có thể giúp giảm tình trạng giật này, nhưng hãy lưu ý: Tính năng này chưa có sẵn ở mọi nơi và làm tăng thêm sự phức tạp cho logic tải từng phần. Nếu muốn dùng ứng dụng đó, bạn cần kiểm tra tính năng đó. Dưới đây là cách bạn có thể sử dụng Image.decode() với phương án 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 vào đường liên kết CodePen này để xem mã tương tự như ví dụ này trong thực tế. Nếu hầu hết hình ảnh của bạn khá nhỏ, thì điều này có thể không ảnh hưởng nhiều đến bạn, nhưng chắc chắn nó có thể giúp giảm hiện tượng giật khi tải từng phần các hình ảnh lớn và chèn chúng vào DOM.

Khi nội dung không tải

Đôi khi, tài nguyên nội dung đa phương tiện không tải được vì một lý do này hay lý do khác và dẫn đến lỗi. Khi nào điều này có thể xảy ra? Điều này còn phụ thuộc, nhưng sau đây là một tình huống giả định dà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 của bạn. Tại một thời điểm trong quá trình này, quá trình tái triển khai sẽ diễn ra. Trong quá trình triển khai này, tên của tài nguyên hình ảnh sẽ thay đổi do 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 sử dụng được và do đó sẽ không hoạt động.

Mặc dù vấn đề này tương đối hiếm khi xảy ra, nhưng bạn có thể cần có một kế hoạch dự phòng nếu tính năng tải từng phần không thành công. Đối với hình ảnh, 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 nên làm trong trường hợp xảy ra lỗi sẽ phụ thuộc vào đơn đăng ký của bạn. Ví dụ: bạn có thể thay thế vùng phần giữ chỗ hình ảnh bằng một nút cho phép người dùng thử 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 phần giữ chỗ hình ảnh.

Các trường hợp khác cũng có thể phát sinh. Dù làm gì, bạn cũng không nên ra tín hiệu cho người dùng khi xảy ra lỗi và có thể cho họ hành động nếu có sự cố xảy ra.

Tính sẵn có của JavaScript

Bạn không nên giả định 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 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ụ dự phòng đơn giản nhất có thể là sử dụng các phần tử <noscript> để phân phát hình ảnh nếu JavaScript bị tắt:

Tôi là một hình ảnh!

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

<html class="no-js">

Sau đó, đặt một dòng tập lệnh cùng dòng vào <head> trước khi yêu cầu bất kỳ biểu định kiểu nào thông qua thẻ <link> để 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;
}

Thao tác này không ngăn việc tải hình ảnh phần giữ chỗ, nhưng sẽ mang lại kết quả mong muốn hơn. Những người đã tắt JavaScript sẽ nhận được nhiều lợi ích hơn cả hình ảnh giữ chỗ. Công cụ này hiệu quả hơn phần giữ chỗ và không nhận được nội dung hình ảnh có ý nghĩa nào.