Native lazy-loading là gì, nó cải thiện tốc độ website như thế nào

Trình duyệt Chrome chính thức hỗ trợ lazy-loading cho ảnh và iframe ở cấp độ trình duyệt! Dưới đây là video demo tính năng này:

Bắt đầu từ Chrome 76, bạn có thể sử dụng thuộc tính loading để lazy load các tài nguyên mà không cần viết riêng mã lazy-loading hoặc sử dụng riêng thư viện JavaScript (chẳng hạn như lazysizes). Chúng ta sẽ đi vào chi tiết ngay sau đây.

P/S: native trong cụm từ “native lazy-loading” có thể được dịch sát nghĩa là “tải lười kiểu bản địa” hoặc “lazy-loading bản địa”, tuy nhiên tôi (người dịch) thấy giữ nguyên từ gốc tiếng Anh hay hơn, hoặc nếu có dịch sẽ dùng từ “lazy-loading cấp trình duyệt” cho dễ hiểu.

Tại sao lại dùng native lazy-loading?

Theo HTTPArchive, ảnh là thành phần được yêu cầu nhiều nhất trên hầu hết website và thường chiếm nhiều băng thông hơn bất kỳ tài nguyên nào. Ở phân vị 90(*), các website gửi 4,7 MB ảnh trên máy bàn và di động.

(*): phân vị thứ 90 (90th percentile), theo cách hiểu thông thường là những trang web có dung lượng ảnh lớn hơn 90% các website khác, nói cách khác top 10% các website có nhiều ảnh nhất chứa ít nhất 4,7 MB ảnh.

Nhúng iframe cũng sử dụng rất nhiều dữ liệu và có thể ảnh hưởng tiêu cực đến hiệu suất của trang (page performance). Nếu chúng ta chỉ tải các ảnh, iframe không quan trọng (non-critical), nằm trong màn hình thứ hai trở đi (below-the-fold) khi người dùng cần xem chúng thì sẽ giúp cải thiện thời gian tải trang (page load times), tối thiểu hóa băng thông (bandwidth), và giảm sử dụng bộ nhớ.

Hiện tại có hai cách để trì hoãn tải ảnh và iframe không thuộc màn hình đầu tiên (off-screen):

  • Sử dụng Intersection Observer API
  • Sử dụng scroll, resize, hoặc orientationchange even hanler (trình xử lý sự kiện)

Cả hai tùy chọn này có thể cho phép lập trình viên bao gồm hàm lazy-loading vào website, và nhiều lập trình viên xây dựng thư viện của bên thứ ba để đưa ra các abstraction thậm chí còn dễ sử dụng hơn. Nhưng với lazy-loading được hỗ trợ trực tiếp bởi trình duyệt, bạn không cần đến các thư viện bên ngoài (external library) nữa. Native lazy loading cũng đảm bảo việc trì hoãn tải ảnh và iframes vẫn hoạt động bình thường ngay cả khi JavaScript bị vô hiệu hóa trên trình duyệt của người dùng (còn gọi là máy khách/client).

P/S: Rất hiếm khi trình duyệt bị vô hiệu hóa JavaScript, vì các website hiện đại, nhiều chức năng thì JavaScript là một thành phần cốt lõi, nhưng nếu nó bị vô hiệu hóa, mà trang của bạn không triển khai dự phòng bằng thẻ <noscript> thì lazy load sẽ bị gặp lỗi không hiển thị ảnh. Bạn có thể thử vô hiệu hóa JS trên Chrome bằng cách vào copy đường dẫn sau vào thanh địa chỉ:

chrome://settings/content/javascript

Rồi nhấn button vô hiệu hóa, tiếp theo bạn thử vào trang này, sẽ thấy nhiều ảnh không được tải: https://code.speed.family/lazysizes-demo1.html

Thuộc tính loading

Ở thời điểm hiện tại, Chrome đã hỗ trợ tải ảnh ở các cấp độ ưu tiên khác nhau phụ thuộc vào vị trí của nó tương quan với viewport (khung nhìn) của thiết bị. Ảnh ở bên dưới viewport được tải với mức ưu tiên thấp hơn (lower priority), nhưng chúng sẽ vẫn được tìm nạp (fetched) để tải về nhanh nhất có thể (as soon as possible).

Trong Chrome 76, bạn có thể sử dụng thuộc tính loading để trì hoãn tải hoàn toàn các ảnh và iframes không thuộc màn hình đầu tiên (những khu vực mà phải cuộn chuột mới tiếp cận được), mã trông giống như thế này:

<img src="image.png" loading="lazy" alt="…" width="200" height="200">
<iframe src="https://example.com" loading="lazy"></iframe>

Dưới đây là các giá trị hỗ trợ cho thuộc tính loading:

  • auto (tự động): Hành vi lazy-loading mặc định của trình duyệt, nó có cùng kết quả như khi bạn không bao gồm thuộc tính này trong thẻ.
  • lazy (lười): Trì hoãn tải tài nguyên cho đến khi nó đạt đến khoảng cách định trước từ viewport (khoảng cách này cụ thể như thế nào bạn sẽ được biết cụ thể ở phần sau).
  • eager (hăng hái): Tải tài nguyên ngay lập tức, không quan trọng là nó nằm ở vị trí nào trên trang (nói cách khác, ảnh có thể nằm rất sâu bên dưới viewport nhưng nó sẽ được tải ngay như là nó nằm ở trên cùng vậy).

P/S: các từ mà người dịch thêm vào là tự động, lười, hăng hái để bạn dễ nhớ và dễ hiểu hơn thôi.

Tính năng này sẽ tiếp tục được cập nhật cho đến khi nó được phát hành trong phiên bản ổn định (Chrome 76 là phiên bản đầu tiên). Nhưng bạn có thể thử nó bằng cách bật flags dưới đây trong Chrome:

  • chrome://flags/#enable-lazy-image-loading
  • chrome://flags/#enable-lazy-frame-loading

Ngưỡng khoảng cách tải

Tất cả các ảnh và iframe nằm trong màn hình đầu tiên (above the fold) – tức là những thứ xuất hiện ngay trong khung nhìn trình duyệt mà không cần cuộn chuột – sẽ được tải bình thường. Nhưng các ảnh và iframes nằm xa bên dưới viewport của thiết bị chỉ được tìm nạp khi người dùng cuộn chuột gần đến chúng (when user scrolls near them).

Ngưỡng khoảng cách là không cố định (not fixed) và tùy thuộc vào một số yếu tố sau:

  • Kiểu tài nguyên được tìm nạp (nó là ảnh hay iframe)
  • Chế độ Lite có được bật trên Chrome cho Android hay không
  • Tác động của kiểu kết nối (nhanh, chậm, cáp quang, 3G, 4G, wifi, vân vân)

Bạn có thể tìm thấy các giá trị mặc định cho các kiểu kết nối khác nhau trên nguồn dữ liệu Chromium. Các con số này, và thậm chí là cách tiếp cận chỉ tìm nạp khi đạt đến khoảng cách nhất định từ viewport có thể thay đổi trong tương lai gần khi nhóm phát triển trình duyệt Chrome cải thiện heuristics (*) để xác định được khi nào nên bắt đầu tải.

(*): Kiểu quy tắc/thuật giải được phát triển dựa trên kinh nghiệm, thường là tối ưu trong phần lớn trường hợp nhưng không phải trong mọi trường hợp và có thể không phải là biện pháp tốt nhất có thể tồn tại. Ưu điểm của nó là cho chất lượng sản phẩm đủ tốt nhưng không mất nhiều thời gian phát triển.

Tôi (người dịch) sẽ cung cấp luôn thông tin về ngưỡng khoảng cách tải ở thời điểm hiện tại (cuối năm 2019):

 //
    // Lazy image loading distance-from-viewport thresholds for different effective connection types.
    //
    {
      name: "lazyImageLoadingDistanceThresholdPxUnknown",
      initial: 5000,
      type: "int",
    },
    {
      name: "lazyImageLoadingDistanceThresholdPxOffline",
      initial: 8000,
      type: "int",
    },
    {
      name: "lazyImageLoadingDistanceThresholdPxSlow2G",
      initial: 8000,
      type: "int",
    },
    {
      name: "lazyImageLoadingDistanceThresholdPx2G",
      initial: 6000,
      type: "int",
    },
    {
      name: "lazyImageLoadingDistanceThresholdPx3G",
      initial: 4000,
      type: "int",
    },
    {
      name: "lazyImageLoadingDistanceThresholdPx4G",
      initial: 3000,
      type: "int",
    },

Từ đoạn mã trên ta nhận thấy rằng trình duyệt Chrome sẽ:

  • Trên mạng mà nó không nhận biết được (Unknown) nó sẽ tải ảnh khi ảnh cách viewport 5000 px
  • Khi không có kết nối (Offline) nó sẽ tải khi cách viewport 8000 px
  • Trên mạng 2G chậm là 8000 px
  • Trên mạng 2G là 6000 px
  • Trên mạng 3G là 4000 px
  • Trên mạng 4G là 3000 px

Bạn có thể quan sát thấy quy luật là trên mạng càng nhanh, ngưỡng khoảng cách tải sẽ càng thấp.

Lưu ý: Trong Chrome 77, bạn có thể trải nghiệm các ngưỡng khác nhau bằng cách điều chỉnh mạng trong DevTools (công cụ cho nhà phát triển được tích hợp sẵn trong Chrome), bạn sẽ cần ghi đè ảnh hưởng của kiểu kết nối trong trình duyệt bằng cách sử dụng flag:

chrome://flags/#force-effective-connection-type

Tải hình ảnh

Để ngăn các nội dung xung quanh không reflowing(**) khi ảnh lazy-load tải, cần đảm bảo thêm thuộc tính heightwidth vào thành phần <img> hoặc chỉ định giá trị cụ thể, trực tiếp trong style nội tuyến (inline style):

<img src="..." loading="lazy" width="200" height="200">
<img src="..." loading="lazy" style="height:200px; width:200px;">

(**): reflowing là hiện tượng trình duyệt phải tính toán lại vị trí và bố cục của các thành phần trong tài liệu HTML, đây là hành vi sẽ khiến người dùng bị cản trở nên cần phải tránh. Bạn có thể tham khảo thêm thông tin về nó ở liên kết này.

Các ảnh sẽ vẫn được lazy-load nếu kích cỡ (dài, rộng) không được chỉ định, nhưng việc thông báo kích cỡ cho chúng sẽ giúp làm giảm khả năng trình duyệt bị reflow.

Hỗ trợ cho thuộc tính intrinsicsize vẫn đang được tiếp tục thực hiện, vì thế các ảnh sẽ vẫn được lazy-load chính xác nếu intrinsicsize được chỉ định kèm với một trong hai kích cỡ (width hoặc height).

<img src="…" alt="…" loading="lazy" intrinsicsize="250x200" width="450">
<!-- lazy-loaded -->

P/S: Bạn có thể xem ví dụ demo về cách thuộc tính loading làm việc với 100 bức ảnh sau (toàn mèo dễ thương).

Tải iframe

Thuộc tính loading ảnh hưởng đến iframe khác so với ảnh, phụ thuộc vào việc liệu iframe có ẩn hay không. (Các iframe ẩn thường được sử dụng cho mục đích phân tích hoặc giao tiếp). Chrome sử dụng các đặc điểm sau để xác định liệu một iframe có đang ẩn hay không:

  • Chiều rộng và chiều cao của iframe là 4 px hoặc nhỏ hơn.
  • Các thuộc tính display: none hoặc visibility: hidden đang được áp dụng.
  • Iframe đang được đặt ở vị trí ngoài màn hình đầu tiên sử dụng positioning X hoặc Y có giá trị âm.

Nếu một iframe thỏa mãn bất kỳ điều kiện nào kể trên, Chrome xem nó là ẩn và không lazy-load nó trong phần lớn trường hợp. Iframe không ẩn sẽ chỉ được tải khi nó nằm trong ngưỡng khoảng cách cho phép tải (đã nói ở trên). Placeholder hiển thị cho iframes lazy-load sẽ vẫn được tìm nạp.

Các câu hỏi thường gặp (FAQ)

Hiện có bất kỳ kế hoạch nào để mở rộng tính năng này không?

Có một số kế hoạch trong việc thay đổi hành vi lazy-loading mặc định của trình duyệt để tự động tải lười bất cứ ảnh hoặc iframe nào mà phù hợp để trì hoãn nếu chế độ Lite được bật trên Chrome cho Android.

Các ảnh nền (background) viết trong CSS có tận dụng được lợi thế của thuộc tính loading không?

Không, hiện tại nó chỉ được sử dụng với các thẻ <img> mà thôi. Nói cách khác các ảnh background vẫn tải bình thường.

Thuộc tính loading hoạt động như thế nào với các ảnh nằm trong viewport nhưng không hiện ra ngay (ví dụ như nó ẩn trong các slide, mà người dùng phải click tiếp mới thấy)?

Chỉ các ảnh từ màn hình thứ hai trở đi mới được tải lười (kết hợp với việc nó thỏa mãn tiêu chí về ngưỡng khoảng cách). Tất cả các ảnh nằm trong màn hình đầu tiên, bất kể là nó không quan sát thấy ngay lúc đầu sẽ được tải như bình thường.

Chuyện gì xảy ra nếu tôi sử dụng thư viện của bên thứ ba (third-party) hoặc script để lazy-load ảnh và iframe?

Thuộc tính loading không ảnh hưởng gì đến mã lazy-load bạn đang sử dụng trên các tài nguyên, nhưng có vài điểm quan trọng sau cần để ý:

  1. Nếu đoạn mã lazy-load của bạn cố gắng tải ảnh hoặc iframe sớm hơn so với Chrome tải chúng theo cách thông thường – điều đó có nghĩa là ngưỡng khoảng cách tải lớn hơn so với ngưỡng của Chrome – chúng sẽ vẫn được trì hoãn và tải dựa trên hành vi thông thường của trình duyệt (có nghĩa là trình duyệt sẽ ghi đè thiết lập của bạn).
  2. Nếu đoạn mã lazy-load của bạn sử dụng ngưỡng khoảng cách tải thấp hơn so với trình duyệt, thế thì tùy chỉnh của bạn sẽ được áp dụng (chứ không phải các ngưỡng mặc định của Chrome được áp dụng, nói cách khác tùy chỉnh của bạn sẽ ghi đè thiết lập của Chrome).

Một trong các lý do quan trọng để tiếp tục sử dụng thư viện của bên thứ ba song song với loading=”lazy” đó là để hỗ trợ (polyfill) cho các trình duyệt hiện chưa có thuộc tính này.

Các trình duyệt khác có hỗ trợ native lazy-loading hay không?

Thuộc tính loading có thể được coi là một cải tiến, cho phép các trình duyệt hỗ trợ nó có thể lazy-load ảnh và iframe. Những trình duyệt chưa hỗ trợ sẽ vẫn tải theo cách cũ như hiện nay. Về mặt hỗ trợ chéo trình duyệt, loading được hỗ trợ trên Chrome 76 và bất kỳ trình duyệt nào dựa trên Chromium 76 (ví dụ như Cốc Cốc). FireFox cũng đang mở thảo luận việc tích hợp thuộc tính này vào.

Một API tương tự đã được đề xuất và sử dụng trong IE và Edge nhưng nó tập trung vào việc hạ thấp mức độ ưu tiên tải xuống của các tài nguyên thay vì trì hoãn tải chúng hoàn toàn. Nó không còn ủng hộ resource hints (gợi ý tài nguyên) nữa.

Tôi phải làm như thế nào với các trình duyệt hiện chưa hỗ trợ native lazy-loading?

Bạn có thể sử dụng thư viện của bên thứ ba hoặc các đoạn mã hỗ trợ khác để tải lười các ảnh trên website. Thuộc tính loading có thể được detect (phát hiện) nếu tính năng này được hỗ trợ trong trình duyệt nhờ đoạn mã sau:

if ('loading' in HTMLImageElement.prototype) {
  // có hỗ trợ trên trình duyệt này
} else {
  // sử dụng thư viện hoặc mã dự phòng
}

Ví dụ, lazysize là thư viện lazy-loading JavaScript phổ biến. Bạn có thể sử dụng đoạn mã dùng ở trên để phát hiện thuộc tính loading có được hỗ trợ trên trình duyệt không, nếu nó không, hãy dự phòng bằng lazysize. Điều đó được thực hiện như sau:

  • Thay thế <img src> bằng <img data-src> để tránh trình duyệt (không hỗ trợ thuộc tính loading) tải ảnh ngay lập tức. Nếu thuộc tính loading được hỗ trợ, bạn chỉ việc chuyển lại data-src thành src.
  • Nếu loading không được hỗ trợ, tải mã dự phòng (lazysizes). Theo tài liệu của lazysizes, bạn sử dụng class (lớp – một khái niệm trong CSS) lazyload như cách để chỉ cho lazysizes biết ảnh nào cần lazy-load.
<!-- Let's load this in-viewport image normally -->
<img src="hero.jpg" alt="…">

<!-- Let's lazy-load the rest of these images -->
<img data-src="unicorn.jpg" alt="…" loading="lazy" class="lazyload">
<img data-src="cats.jpg" alt="…" loading="lazy" class="lazyload">
<img data-src="dogs.jpg" alt="…" loading="lazy" class="lazyload">

<script>
  if ('loading' in HTMLImageElement.prototype) {
    const images = document.querySelectorAll('img[loading="lazy"]');
    images.forEach(img => {
      img.src = img.dataset.src;
    });
  } else {
    // Dynamically import the LazySizes library
    const script = document.createElement('script');
    script.src =
      'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/4.1.8/lazysizes.min.js';
    document.body.appendChild(script);
  }
</script>

Dưới đây là trang demo cho kiểu tải này. Bạn hãy thử nó trên trình duyệt FireFox hoặc Safari để xem mã dự phòng hoạt động ra sao.

Lưu ý: Thư viện lazysizes cũng cung cấp plugin native loading được sử dụng để native lazy-loading khi có cơ hội nhưng vẫn có dự phỏng bằng chức năng tùy chỉnh của thư viện khi cần thiết.

Native lazy-loading ảnh hưởng như thế nào đến các quảng cáo trên trang web?

Tất cả các quảng cáo hiển thị cho người dùng dưới dạng ảnh hoặc iframe sẽ được lazy-load giống như bất kỳ ảnh hoặc iframe nào khác.

Ảnh sẽ được xử lý như thế nào khi trang web được in?

Mặc dù chức năng này không có trong Chrome 76, có một open issue để đảm bảo rằng tất cả ảnh và iframe được tải ngay lập tức nếu trang trong chế độ in ấn.

Kết luận

Triển khai lazy-loading ảnh và iframe dưới dạng native của trình duyệt có thể làm nhiệm vụ này trở nên dễ dàng hơn đáng kể cho mục tiêu cải thiện hiệu suất của trang web.

Nếu bạn để ý thấy bất kỳ hành vi bất thường nào với tính năng này trên Chrome hãy báo cho chúng tôi biết.

(Dịch từ bài viết: Native lazy-loading for the web, các tác giả: Houssein Djirdeh, Addy Osmani và Mathias Bynens, trang web: web[.]dev)

Thông tin bổ sung. Hiện tôi biết có 2 plugin cho WordPress hỗ trợ tính năng native lazy-loading là:

Mặc dù rất thích lazy loading, tôi có một bài viết mang hơi hướng chống lại nó: Tại sao lazy load ảnh không hấp dẫn như bạn nghĩ.

Leave a Comment