Published on

Lazyload kiểu hiện đại trong năm 2021

Authors

Lazyload là một tính năng không thể thiếu để tăng tốc độ website và (có thể) giúp bạn đạt điểm cao trong Google PageSpeed. Thật vậy, lazyload quan trọng trong tối ưu website đến nỗi các trình duyệt (dẫn đầu là Google Chrome) đã thảo luận và thống nhất một thuộc tính HTML mới giúp hỗ trợ Lazyload mà không cần bất kì một thư viện Javascript bên thứ ba nào. Và đó là lúc thuộc tính loading được ra mắt và giúp chúng ta dễ dàng hơn trong việc lazyload hình ảnh.

Nhưng trước tiên...


Tại sao phải tải chậm hình ảnh?

Trong thế giới web, hình ảnh luôn chiếm một tỉ lệ lớn về dung lượng. Theo thống kê của trang HTTPArchive chỉ riêng đối với nền tảng WordPress 🔗, trung bình trên một trang của một website, tổng dung lượng hình ảnh (~1268 KB) chiếm hơn phân nửa tổng dung lượng của một website trên di động (~2381 KB).

true

Nguồn: HTTPArchive. Dịch: datuan.dev.

Chính vì vậy, việc tối ưu hình ảnh luôn là ưu tiên hàng đầu với những người lập trình website và cũng luôn được khuyến khích bởi Google PageSpeed. Chúng ta đã nghĩ ra rất nhiều cách tối ưu như:

  • Sử dụng đúng kích thước hình, tránh sử dụng hình fullsize
  • Giảm dung lượng hình ảnh JPG, PNG mà vẫn giữ được chất lượng (lossless)
  • Sử dụng định dạng ảnh WebP để giảm thêm dung lượng
  • Lazyload những hình ảnh không cần thiết

Và theo mình, cách tối ưu tốt nhất và hiệu quả nhất chính là Lazyload.

Bởi vì kết nối trên thiết bị di động là rất nhạy cảm. Việc có quá nhiều request đến server và dung lượng của các resource (hình ảnh, CSS, JS) trên website của bạn quá nặng sẽ là vấn đề cực kì lớn. Hãy tưởng tượng một đường truyền 4G ổn định (10Mbps) sẽ tốn đến 2 giây chỉ để tải xong tất cả resource website của bạn, chưa tính đến việc xử lý và dựng trang (parse & rendering). Thậm chí với bài kiểm tra di động Google PageSpeed, đường truyền sẽ bị giới hạn ở 1.6Mbps với latency là 150ms 🔗, đây chính là real-world pain mà bạn phải chấp nhận và đối mặt.

Để chứng minh sử dụng Lazyload sẽ hiệu quả hơn so với tối ưu hình ảnh, mời bạn xem một báo cáo khác khác cũng của HTTPArchive là báo cáo tình hình về hình ảnh trên các website WordPress.

Khi thực hiện lazyload hình ảnh (tối ưu hình ảnh offscreen) không nằm trong màn hình đầu tiên, chúng ta có thể tiết kiệm được 800 KB (trung vị, p50) hay 3947 KB (p90). Có nghĩa là có 90% (90th percentile) hình ảnh có dung lượng được tiết kiệm đến 3947 KB nếu thực hiện Lazyload (tính theo tháng Hai, 2019).

true

Trong khi đó đối với tối ưu hình ảnh thông thường, con số này lần lượt là 34 KB (p50) hay 1065 KB (p90). Có nghĩa là có 90% (90th percentile) hình ảnh có dung lượng được tiết kiệm đến 1065 KB thực hiện tối ưu hình ảnh (lossless) (tính theo tháng Hai, 2019).

true


Lazyload hình ảnh bằng cách nào?

Trước kia để tải chậm các hình ảnh trên website, đa số chúng ta sẽ sử dụng các thư viện Javascript của bên thứ ba để thực hiện tính năng này. Một số thư viện nổi tiếng có thể kể đến như vanilla-lazyload 🔗, lazyload của tuupola 🔗 hay lazysizes 🔗 có chất lượng rất tốt mà đặc điểm chung là được viết trên Javascript thuần và không phụ thuộc vào jQuery (sẽ giải thích tại sao ở đoạn sau). Đối với WordPress thì các thư viện này được đóng gói thành một plugin hoàn chỉnh để bạn sử dụng hoặc được các theme trả phí tính hợp sẵn luôn.

Cách sử dụng các thư viện lazyload khá tương đồng nhau và đơn giản để sử dụng. Thông thường để hiển thị một tấm hình chúng ta chỉ cần viết một đoạn HTML đơn giản như:

<img src="/đường/dẫn/đến/hình-ảnh.jpg">

Trình duyệt khi duyệt qua HTML được trả về từ server, khi đọc đến thẻ <img> này sẽ ngay lập tức tải ngay tập tin hình-ảnh.jpg mà không hề quan tâm tấm hình này có được sử dụng hay không hoặc được sử dụng ở đâu (display none hoặc chỉ hiển thị khi hover,...). Nhờ các thư viện lazyload, chúng ta có thể quy định việc hình ảnh được tải khi nào ví dụ: ngay khi các thành phần khác quan trọng hơn tải xong hoặc khi người dùng cuộn chuột gần đến đoạn cần hiển thị hình ảnh. Việc mọi người cần làm là thay đổi một chút trong thẻ <img> thành:

<img class="lazyload" data-src="/đường/dẫn/đến/hình-ảnh.jpg"
src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=">

Các bạn có thể thấy thuộc tính src đã được thay thế thành data-src với đường dẫn thật của hình ảnh cần lazyload. Thuộc tính src được thay thế thành base64 của một pixel trắng giúp trình duyệt vẫn xem đây là một thẻ <img> đúng chuẩn. Thực sự các trình duyệt hiện đại đã "thông minh" hơn nên thuộc tính src "giả" này có thể bỏ đi mà không gặp vấn đề gì.

Tuy nhiên, vì cách mà trình duyệt xử lý các thẻ <img> và các thẻ liên quan, việc chuyển thuộc tính từ src thành data-src là tối quan trọng và phải được thực hiện từ phía backend (PHP, .NET,...). Bởi vì trình duyệt luôn xử lý HTML trước tiên, nếu trình duyệt phát hiện thuộc tính src trong thẻ <img> thì sẽ tải ngay hình ảnh mà không đợi chờ gì và lazyload sẽ mất tác dụng. Điều này sẽ tạo nên một vấn đề cũng rất quan trọng mà mình sẽ nói ở phần tiếp theo.

Một số plugin như WP Fastest Cache 🔗 sử dụng một tập tin blank.gif thay cho đoạn base64 bên trên. Theo mình đây thật sự không phải là một cách hay khi trình duyệt thực sự tốn một khoảng thời gian để tải tập tin blank.gif này mặc dù không thực sự cần thiết, làm mất đi tính chất của việc lazyload.

Bước còn lại là thêm một class có tên lazyload vào thuộc tính class để các thư viện Javascript có thể "theo dõi" các thẻ này, giúp các thư viện chạy (fire) và tải hình ảnh lúc cần thiết. Đoạn video dưới đây cho bạn thấy cách hoạt động của các thư viện lazyload bằng Javascript hiện tại.


Lazyload native từ trình duyệt

Hiểu được những khó khăn này, các bên phát triển trình duyệt mà dẫn đầu là Google Chrome đã giới thiệu một thuộc tính loading mới cho các thẻ <img>, <video> hay <iframe>,... để giúp chúng ta không cần phụ thuộc vào các trình duyệt của bên thứ ba. Tính năng này hoàn toàn hữu dụng và giúp bên lập trình viên nhẹ đầu hơn trong việc lựa chọn giữa các thư viện, đồng thời người dùng cũng không phải tốn thêm thời gian để tải các thư viện bên thứ ba đó.

Vấn đề ở đây là thuộc tính loading này chưa được hỗ trợ 100% trên các trình duyệt phổ biến mà các bạn có thể xem tại Can I Use 🔗. Từ đó sinh ra một cách để hỗ trợ lazyload trên cả hai loại trình duyệt, một là có hỗ trợ loading, hai là không hỗ trợ loading. Điều này lại sinh ra một vấn đề khác (ôi thôi khổ quá đi mà), là native lazyload từ trình duyệt chỉ hiểu thuộc tính src, trong khi các thư viện lazyload phải sử dụng data-src như đã nói ở trên.

Sau đoạn này là một nội dung rất dài và nặng về kĩ thuật. Mình đã cố gắng tóm tắt nhưng nội dung truyền tải phải đầy đủ và chính xác. Ngoài ra đây là cách "hiện đại" và chưa được hỗ trợ rộng rãi. Vì vậy nếu bạn không tự tin về khả năng xử lý lỗi của website, vui lòng không thực hiện theo bài viết này.


Cách Lazyload hình ảnh sau năm 2020

Thật vậy, trong năm 2020 và có thể sang cả 2021, chúng ta sẽ phải chấp nhận chuyện có hai loại trình duyệt: loại có hỗ trợ loading và số còn lại. Vấn đề này tương tự khoảng 1 hay 2 năm trước khi số lượng trình duyệt hỗ trợ WebP là thiểu số.

Theo mình đây là vấn đề kĩ thuật khá hay và câu hỏi quan trọng mà mình muốn tìm cách giải quyết là làm sao có thể hỗ trợ cả 2 loại này mà không ảnh hưởng đến cả người dùng và mình, những người lập trình website, thay vì đặt câu hỏi bạn sẽ hỗ trợ số đông bằng công nghệ cũ hay chỉ hỗ trợ thiểu số và bỏ đi lượng người dùng kia.

Okay bắt đầu nha. Theo mình để xử lý được vấn đề này, chúng ta phải trả về một đoạn HTML có cấu trúc gần tương tự như sau:

true

Đoạn mã HTML trên có tác dụng không cho trình duyệt tải tấm ảnh này. Thuộc tính loading sẽ không được thực thi vì không tồn tại thuộc tính src. Ngoài ra các thư viện lazyload cũng thường kiểm tra thẻ <img> có chứa class lazyload hay không, nếu không sẽ không xử lý gì.

Từ đó giúp chúng ta chia ra được 2 hướng xử lý cho trình duyệt hiện đại và loại cũ hơn:

true

  1. Hướng thứ nhất: thêm thuộc tính src từ thuộc tính data-src có sẵn. Khi đã thay xong, trình duyệt sẽ tự động tải hình ảnh nếu cần thiết.
  2. Hướng còn lại: thêm class lazyload vào thẻ ảnh, giữ nguyên thuộc tính data-src và tải thư viện lazyload nếu cần thiết. Nếu thích chúng ta có thể bỏ luôn thuộc tính loading vì không dùng tới nữa.

Vậy thì cái khung đơn giản đoạn code của chúng ta sẽ là như thế này.

true

Đoạn code rất dễ hiểu, đầu tiên, chúng ta sẽ phải kiểm tra xem trình duyệt có thể hỗ trợ thuộc tính loading không, nếu có sẽ tiến hành xử lý theo hướng hiện đại. Nếu không thì tải một thư viện lazyload khác, ví dụ là lozad để hỗ trợ cho những trình duyệt cũ.

Tiếp tục, chúng ta sẽ phải lấy ra danh sách tất cả các hình ảnh có thuộc tính loading này để tiến hành xử lý. Đơn giản thôi, hãy dùng hàm document.querySelectorAll mặc định của trình duyệt bạn nhé. Sau đó tiếp tục sử dụng vòng lặp for each để thay đổi theo Hướng thứ nhất. Chỉ đơn giản như vậy thôi.

true

Trong trường hợp còn lại, để xử lý theo Hướng thứ hai, trước tiên chúng ta cần thêm class cần thiết vào các hình ảnh có thuộc tính loading mà trình duyệt không thể thực hiện được. Sau đó tiến hành tải một thư viện Lazyload về là được:

true

Okay vậy toàn bộ đoạn code mà bạn có thể copy được nằm tại Github của mình, bạn có thể xem qua hoặc tải về tại đây:

if ('loading' in document.createElement('img')) {
    document.querySelectorAll('[loading="lazy"]').forEach(elem => {
        if (elem.dataset.src) {
            elem.src = elem.dataset.src;
        }
        if (elem.dataset.srcset) {
            elem.srcset = elem.dataset.srcset;
        }
    })
} else {
    document.querySelectorAll('[loading=lazy]').forEach(elem => {
        elem.classList.add("lazyload");
    })

    var script = document.createElement('script');
    script.onload = function () {
        const el = document.querySelectorAll('img[data-src]');
        const observer = lozad(el);
        observer.observe();
    };
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lozad.js/1.16.0/lozad.min.js';
    document.head.appendChild(script);
}

Bạn có thể chèn vào thẻ script ở footer, càng gần </body> càng tốt là được. Ngoài ra, những theme phức tạp sử dụng thư viện lazyload riêng cũng khoan hãy áp dụng tính năng này.


Kết luận

Vậy đâu là ưu điểm của cách này?

  • Tiết kiệm: chúng ta không cần phải tải toàn bộ thư viện lazyload và tốn thời gian để xử lý chúng khi trình duyệt đã hỗ trợ sẵn tính năng này.
  • Hiệu năng: như mình đã nói, việc được tích hợp thẳng vào trình duyệt giúp tăng khả năng xử lý của trình duyệt hơn.
  • Đỡ phụ thuộc: chúng ta không còn phụ thuộc vào các thư viện lazyload của theme hay plugin nữa. Một plugin bình thường cũng không cần "đoán" theme đang dùng thư viện nào hay phải tải một thư viện mới, tạo gánh nặng về Javascript.

Mặt trái của vấn đề này chính là các trình duyệt hỗ trợ chưa thực sự phổ biến, dẫn tới các nhà phát triển theme cũng không quá mặn mà để tích hợp tính năng này vào.

Mình mong trong tương lai, những bên phát triển theme sẽ đem tính năng này lên theme của họ và tăng mức độ sử dụng lên trong tương lai.