Medium đã tối ưu tốc độ load hình ảnh như thế nào

Một website tốt đòi hỏi tốc độ load trang phải nhanh, đó là điều tất yếu. Đối với những website có lượng hình ảnh lớn, điều này đã trở thành 1 trong những quan tâm hàng đầu của các nhà phát triển. Trong bài viết này chúng ta cùng đi vào tìm hiểu kỹ thuật Progressive Image Loading mà Medium đã áp dụng, implement xem nó thế nào và bạn có áp dụng được vào các dự án của mình ko. Phần lớn nội dung bài viết này mình dịch lại từ post này của tác giả José M. Pérez. Nào chúng ta cùng tìm hiểu.

Việc đầu tiên cần làm là dùng các công cụ test có sẵn đễ xem tốc độ load của web bạn nhanh hay chậm. Các bạn có thể kiểm tra ở Google Page Speed, GTmetrix, WebPageTest hay những trang tương tự. Bạn sẽ thấy thủ phạm do đâu mà ra. Đa phần là do những hình ảnh chất lượng cao chưa được nén (chiếm cỡ 85% tốc độ load). Bạn biết vấn đề ở đâu rồi chứ? Và Medium đã giải quyết nó như thế nào?

Kỹ thuật của Medium

Để xem hình ảnh trên Medium được load thế nào, hãy cùng xem qua demo này:

Nếu bạn muốn tự test, hãy mở Medium'post này trong browser, disable cache rồi xem hiệu ứng. Đây là những gì đã xảy ra:

1. Render div chứa ảnh sẽ hiển thị.

Medium sử dụng 1 thẻ

với padding-bottom set theo tỷ lệ %, tương ứng với tỉ lệ ratio của ảnh. Điều này cũng được đề cập đến trong intrinsic placeholders..

2. Load 1 phiên bản thu nhỏ của ảnh.

Lúc này, nó request 1 ảnh JPEG thu nhỏ với chất lượng rất thấp( cỡ 20%). Markup cho ảnh thu nhỏ này được trả về trong initial HTML với 1 thẻ <img/>, nên trình duyệt bắt đầu fetch chúng vào.

3. Một khi ảnh đã được load, nó sẽ đẩy vào <canvas/>.

Sau đó, dữ liệu sẽ được lấy và truyền vào function blur(). Bạn có thể thấy, trong main-base.bundle JS file. Đồng thời, ảnh chính đã được requested.

4. Một khi ảnh chính đã được load, nó hiển thị ra và canvas được ẩn đi.

Toàn bộ quá trình này được diễn ra một cách trơn tru nhờ vào CSS animation.

Markup

Đây là cái nhìn toàn cảnh về markup cho image:

<figure>
  <div>
    <div/> <!-- this div keeps the aspect ratio so the placeholder doesn't collapse -->
    <img/> <!-- this is a tiny image with a resolution of e.g. ~27x17 and low quality -->
    <canvas/> <!-- takes the above image and applies a blur filter -->
    <img/> <!-- the large image to be displayed -->
    <noscript/> <!-- fallback for no JS -->
  </div>
</figure>

Một ví dụ cụ thể:

<figure name="7012" id="7012" class="graf--figure graf--layoutFillWidth graf-after--h4">
  <div class="aspectRatioPlaceholder is-locked">
    <div class="aspect-ratio-fill" style="padding-bottom: 66.7%;"></div>
    <div class="progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded" data-image-id="1*sg-uLNm73whmdOgKlrQdZA.jpeg" data-width="2000" data-height="1333" data-scroll="native">
      <img src="https://cdn-images-1.medium.com/freeze/max/27/1*sg-uLNm73whmdOgKlrQdZA.jpeg?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail">
        <canvas class="progressiveMedia-canvas js-progressiveMedia-canvas" width="75" height="47"></canvas>
        <img class="progressiveMedia-image js-progressiveMedia-image __web-inspector-hide-shortcut__" data-src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg">
        <noscript class="js-progressiveMedia-inner">&lt;img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg"&gt;</noscript>
    </div>
  </div>
</figure>

Chú ý là chất lượng ảnh request còn phụ thuộc vào thiết bị.

Tái hiện lại hiệu ứng

Trong CodePen bên dưới, tác giả đã implemented 1 hiệu ứng tương tự, tuy nhiên thay thế canvas bằng cách sử dụng CSS filters để làm mờ (xem bên dưới để biết thêm chi tiết). Đây là demo (click Run Pen để chạy):

Bạn nên mở full màn hình để xem, và nên disable cache trình duyệt để xem nó load thế nào. Các ảnh dưới thể hiện cách load ảnh khi disable cache:

Vậy có đáng để thực hiện việc này ko?

Rõ ràng có khá nhiều việc phải làm để render ảnh theo cách này. Và tất nhiên nó có thể gây nản chí, nhưng hiệu quả nó mang lại thì quá tuyệt. Việc hiểu và nắm rõ được cách load image có khá nhiều lợi thế:

  • Lazy loading: Sử dụng JS cho việc tạo request cho phép kiểm soát được image nào đã requested. Khi tất cả ảnh thu nhỏ đã được request, ảnh lớn chỉ được request khi chúng được nằm trong viewport.
  • Placeholder tốt hơn: Ảnh thumbnails rất nhỏ, cỡ 2kB, kết hợp với hiệu ứng làm mờ sẽ tốt hơn 1 màu solid, mà ko ảnh hưởng gì đến payload.
  • Kích thước hình ảnh phù hợp: Medium chuẩn bị rất nhiều ảnh có size khác nhau phụ thuộc vào từng device request lên, nên tải trọng sẽ được tối ưu hơn.

Các biến thể

Inlining image data

Thay vì tạo request cho ảnh thumbnails, ta có thể inline chúng bằng cách sử dụng data URIs. Việc này làm tăng kích thước HTML, nhưng giúp đẩy nhanh quá trình render placeholder, ngay khi markup được tải xuống.

Hiệu ứng làm mờ

Mặc định thì khi trình duyệt render 1 ảnh nhỏ theo kiểu scaled up, nó sẽ tạo ra 1 hiệu ứng mờ để làm mịn dần các góc cạnh của ảnh đó. Hiệu ứng này có thể tắt/mở được. Cả Chrome, Safari, Firefox đều có hiệu ứng này, tuy nhiên Chrome có hiệu ứng làm mịn tốt hơn cả. Đây là demo, bạn nên mở to màn hình lên xem cho rõ:

Cái ảnh này nó chỉ có 27px và chất lượng rất thấp, nhưng được phóng to lên khá mịn

Hiệu ứng này cũng có thể đạt được khi sử dụng CSS Filter Effects (có support IE). Lợi thế của kỹ thuật này là bạn có thể dể dàng tinh chỉnh độ mờ của ảnh bao nhiêu tùy thích, và mọi thứ đã có CSS lo. 1 option khác là sử dụng SVG filter, được đề cập đến ở đây.

Một cách khác để cải thiện placeholder: Google Images Search

Một kỹ thuật đơn giản được dùng bởi Google Search khi search ảnh trên điện thoại: Google Image Search hiển thị 1 background solid làm placeholder (ảnh bên trái là khi đang load, ảnh bên phải là khi đã load xong).

Họ lấy 1 mã màu và sử dụng solid colour background. Điều này làm cho user cảm thấy ảnh được load nhanh hơn :man_detective:

Thêm 1 kỹ thuật nâng cao nữa: Facebook's 200 bytes

Đầu năm nay Facebook đã post bài: "The technology behind preview photos", một chủ đề rất thú vị về việc sử dụng 1 ảnh 42x42 px làm ảnh previews với JPEG headers. Bạn có thể tham khảo thêm nếu muốn.

LQIP: Low Quality Image Placeholders

Thay vì chờ ảnh cuối cùng đc load, ta có thể dùng ảnh nén trước, xong rồi switch qua ảnh to kia sau. Đây có 1 bài viết về chủ đề này: Low Quality Image Placeholders. Ý tưởng này cũng giống như của Medium, nhưng dùng ảnh nén kích thước cao hơn.

Kết luận

Nếu web của bạn load càng nhiều ảnh, thì bạn nên nghĩ đến cách tối ưu image loading đi là vừa, điều này sẽ làm cho trải nghiệm người dùng tốt hơn. Chúc các bạn thực hiện cách trên thành công. Hẹn gặp lại trong các bài viết tới.