Tự động lựa chọn tài nguyên thông qua gợi ý của ứng dụng

Ilya Grigorik

Việc xây dựng ứng dụng cho web mang đến cho bạn phạm vi tiếp cận chưa từng có. Chỉ cần một cú nhấp chuột là bạn có thể sử dụng ứng dụng web của mình trên hầu hết mọi thiết bị được kết nối – điện thoại thông minh, máy tính bảng, máy tính xách tay, máy tính để bàn, TV và nhiều thiết bị khác bất kể thương hiệu hoặc nền tảng. Để mang lại trải nghiệm tốt nhất, bạn đã xây dựng một trang web thích ứng phù hợp với bản trình bày và chức năng cho từng kiểu dáng. Giờ đây, bạn đang chạy danh sách kiểm tra hiệu suất để đảm bảo rằng ứng dụng tải nhanh nhất có thể: bạn đã tối ưu hoá đường dẫn hiển thị quan trọng, bạn đã nén và lưu vào bộ nhớ đệm các tài nguyên văn bản, và giờ bạn đang xem tài nguyên hình ảnh, tài nguyên này thường được chuyển các byte. Vấn đề là việc tối ưu hoá hình ảnh rất khó:

  • Xác định định dạng thích hợp (vectơ so với đường quét)
  • Xác định định dạng mã hoá tối ưu (jpeg, webp, v.v.)
  • Xác định chế độ nén phù hợp (có tổn hao và không tổn hao)
  • Xác định siêu dữ liệu cần giữ lại hay xoá
  • Tạo nhiều biến thể của mỗi biến thể cho từng màn hình + độ phân giải DPR
  • ...
  • Tính đến loại mạng, tốc độ và các lựa chọn ưu tiên của người dùng

Mỗi lần, đây đều là các vấn đề đều hiểu rõ. Nói chung, họ tạo ra một không gian tối ưu hoá rộng lớn mà chúng tôi (các nhà phát triển) thường bỏ qua. Con người làm một công việc kém, đó là khám phá cùng một không gian tìm kiếm lặp đi lặp lại, đặc biệt là khi có nhiều bước. Mặt khác, máy tính vượt trội ở những loại công việc này.

Câu trả lời cho một chiến lược tối ưu hoá hiệu quả và bền vững cho hình ảnh và các tài nguyên khác có thuộc tính tương tự thật đơn giản: đó là tự động hoá. Nếu bạn đang điều chỉnh tài nguyên của mình, thì bạn sẽ làm sai: bạn sẽ quên, bạn sẽ trở nên lười biếng, hoặc người khác sẽ sửa những lỗi này cho bạn – đảm bảo như vậy.

Câu chuyện về nhà phát triển chú trọng đến hiệu suất

Việc tìm kiếm thông qua không gian tối ưu hoá hình ảnh có hai giai đoạn riêng biệt: thời gian xây dựng và thời gian chạy.

  • Một số phương thức tối ưu hoá là nội tại đối với chính tài nguyên – ví dụ: chọn định dạng và loại mã hoá phù hợp, điều chỉnh chế độ cài đặt nén cho từng bộ mã hoá, xoá siêu dữ liệu không cần thiết, v.v. Bạn có thể thực hiện các bước này tại "thời gian xây dựng".
  • Các hoạt động tối ưu hoá khác được xác định theo loại và thuộc tính của ứng dụng yêu cầu và phải được thực hiện trong "thời gian chạy": chọn tài nguyên thích hợp cho DPR và chiều rộng hiển thị dự định của ứng dụng, có tính đến tốc độ mạng, lựa chọn ưu tiên của người dùng và ứng dụng, v.v.

Công cụ tạo bản dựng này đã tồn tại nhưng có thể được cải thiện. Ví dụ: có rất nhiều khoản tiết kiệm bằng cách điều chỉnh linh động chế độ cài đặt "chất lượng" cho mỗi hình ảnh và mỗi định dạng hình ảnh, nhưng tôi chưa thấy có người nào thực sự sử dụng chế độ này ngoài nghiên cứu. Đây là lĩnh vực đầy đủ cho sự đổi mới, nhưng trong mục đích của bài đăng này, tôi sẽ giữ nguyên nội dung đó. Hãy tập trung vào phần thời gian chạy của câu chuyện.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Mục đích của ứng dụng rất đơn giản: tìm nạp và hiển thị hình ảnh ở 50% khung nhìn của người dùng. Đây là nơi hầu hết mọi nhà thiết kế đều rửa tay và đầu óc khi đến quầy rượu. Trong khi đó, nhà phát triển tập trung vào hiệu suất trong nhóm phải mất nhiều thời gian:

  1. Để có khả năng nén tốt nhất, cô muốn sử dụng định dạng hình ảnh tối ưu cho từng ứng dụng: WebP cho Chrome, JPEG XR cho Edge và JPEG cho các định dạng còn lại.
  2. Để có được chất lượng hình ảnh tốt nhất, cô cần tạo nhiều biến thể của mỗi hình ảnh ở các độ phân giải khác nhau: 1x, 1,5x, 2x, 2,5x, 3x và thậm chí có thể thêm một vài biến thể ở giữa.
  3. Để tránh phân phối các pixel không cần thiết, cô cần hiểu "50% khung nhìn của người dùng thực sự có ý nghĩa gì" — có rất nhiều chiều rộng khung nhìn khác nhau trên thị trường!
  4. Lý tưởng nhất là cô ấy cũng muốn mang đến trải nghiệm bền bỉ, trong đó người dùng trên các mạng chậm hơn sẽ tự động tìm nạp độ phân giải thấp hơn. Suy cho cùng thì cũng đã đến lúc bán kính rồi.
  5. Ứng dụng này cũng hiển thị một số chế độ kiểm soát của người dùng ảnh hưởng đến việc tài nguyên hình ảnh nào cần được tìm nạp, vì vậy, bạn cũng cần cân nhắc những yếu tố đó.

Sau đó, nhà thiết kế nhận ra rằng cô ấy cần hiển thị một hình ảnh khác ở chiều rộng 100% nếu kích thước khung nhìn nhỏ để tối ưu hoá mức độ dễ đọc. Điều này có nghĩa là giờ đây, chúng ta phải lặp lại quy trình tương tự cho một nội dung nữa, sau đó thực hiện điều kiện tìm nạp theo kích thước khung nhìn. Tôi đã đề cập rằng vấn đề này là khó chưa? Chà, hãy bắt đầu thôi. Phần tử picture sẽ giúp chúng ta hiểu rõ hơn:

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

Chúng tôi đã xử lý hướng nghệ thuật, lựa chọn định dạng và cung cấp 6 biến thể của từng hình ảnh để tính đến sự thay đổi về DPR và chiều rộng khung nhìn trên thiết bị của khách hàng. Thật ấn tượng!

Rất tiếc, phần tử picture không cho phép chúng ta xác định bất kỳ quy tắc nào về cách hoạt động của phần tử này dựa trên loại kết nối hoặc tốc độ của ứng dụng. Tuy nhiên, trong một số trường hợp, thuật toán xử lý của API này cho phép tác nhân người dùng điều chỉnh tài nguyên mà nó tìm nạp (xem bước 5). Chúng ta chỉ phải hy vọng rằng tác nhân người dùng đã đủ thông minh. (Lưu ý: hiện không có cách triển khai nào như vậy). Tương tự, không có hook nào trong phần tử picture để cho phép logic dành riêng cho ứng dụng có tính đến lựa chọn ưu tiên của người dùng hoặc ứng dụng. Để có hai bit cuối cùng này, chúng ta phải di chuyển tất cả logic trên vào JavaScript, nhưng sẽ mất các tính năng tối ưu hoá tải trước trình quét do picture cung cấp. Rất tiếc!

Bỏ qua những hạn chế đó, bộ công cụ này hoạt động hiệu quả. Ít nhất là đối với nội dung cụ thể này. Thách thức thực tế và lâu dài ở đây là chúng ta không thể kỳ vọng rằng nhà thiết kế hoặc nhà phát triển có thể tự tạo mã như thế này cho từng thành phần. Đây là một câu đố trí tuệ thú vị ở lần thử đầu tiên, nhưng sẽ mất đi sức hấp dẫn ngay sau đó. Chúng ta cần hệ thống tự động hoá. Có thể công cụ của IDE hoặc công cụ biến đổi nội dung khác có thể giúp chúng ta tiết kiệm thời gian và tự động tạo các mã nguyên mẫu ở trên.

Tự động hoá việc lựa chọn tài nguyên thông qua gợi ý ứng dụng

Hãy hít một hơi thật sâu, tạm dừng việc không tin vào vấn đề nào nữa và giờ hãy xem xét ví dụ sau:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

Dù bạn có tin hay không, ví dụ trên đã đủ để cung cấp tất cả tính năng giống như mã đánh dấu hình ảnh dài hơn nhiều ở trên. Ngoài ra, như chúng ta thấy, nó cho phép nhà phát triển toàn quyền kiểm soát cách thức, nội dung và thời điểm tìm nạp tài nguyên hình ảnh. "Ma thuật" nằm ở dòng đầu tiên bật tính năng báo cáo gợi ý ứng dụng và yêu cầu trình duyệt quảng cáo tỷ lệ pixel của thiết bị (DPR), chiều rộng khung nhìn bố cục (Viewport-Width) và chiều rộng hiển thị dự định (Width) của tài nguyên đến máy chủ.

Khi bật gợi ý ứng dụng, mã đánh dấu phía máy khách tạo ra chỉ giữ lại các yêu cầu về bản trình bày. Nhà thiết kế không phải lo lắng về loại hình ảnh, độ phân giải ứng dụng khách, điểm ngắt tối ưu để giảm số byte được phân phối hay các tiêu chí lựa chọn tài nguyên khác. Hãy đối mặt với thực tế là họ chưa bao giờ làm vậy và lẽ ra thì không cần phải làm vậy. Tốt hơn là nhà phát triển cũng không cần phải viết lại và mở rộng mã đánh dấu ở trên vì việc lựa chọn tài nguyên thực tế là do ứng dụng và máy chủ thương lượng.

Chrome 46 cung cấp dịch vụ hỗ trợ gốc cho các gợi ý DPR, WidthViewport-Width. Theo mặc định, các gợi ý sẽ bị tắt và <meta http-equiv="Accept-CH" content="..."> ở trên đóng vai trò là tín hiệu chọn sử dụng để yêu cầu Chrome nối các tiêu đề đã chỉ định vào các yêu cầu gửi đi. Sau khi thiết lập xong, hãy kiểm tra tiêu đề của yêu cầu và phản hồi để lấy yêu cầu hình ảnh mẫu:

Sơ đồ thương lượng gợi ý ứng dụng

Chrome quảng cáo khả năng hỗ trợ định dạng WebP thông qua tiêu đề Accept request; tương tự, trình duyệt Edge mới cũng quảng cáo việc hỗ trợ định dạng JPEG XR thông qua tiêu đề Accept (Chấp nhận).

Ba tiêu đề yêu cầu tiếp theo là tiêu đề gợi ý ứng dụng quảng cáo tỷ lệ pixel của thiết bị của thiết bị khách (3x), chiều rộng khung nhìn bố cục (460px) và chiều rộng hiển thị dự định của tài nguyên (230px). Hành động này cung cấp tất cả thông tin cần thiết cho máy chủ để chọn biến thể hình ảnh tối ưu dựa trên bộ chính sách riêng: khả năng sử dụng tài nguyên được tạo trước, chi phí mã hoá lại hoặc đổi kích thước tài nguyên, mức độ phổ biến của tài nguyên, tải máy chủ hiện tại, v.v. Trong trường hợp cụ thể này, máy chủ sử dụng gợi ý DPRWidth và trả về tài nguyên WebP, như được biểu thị trong các tiêu đề Content-Type, Content-DPRVary.

Không có điều gì kỳ diệu ở đây cả. Chúng tôi đã di chuyển lựa chọn tài nguyên từ mã đánh dấu HTML và chuyển sang quá trình thương lượng yêu cầu-phản hồi giữa ứng dụng và máy chủ. Do đó, HTML chỉ quan tâm đến các yêu cầu về trình bày và là thứ chúng ta có thể tin tưởng để mọi nhà thiết kế và nhà phát triển viết, trong khi việc tìm kiếm qua không gian tối ưu hoá hình ảnh được trì hoãn đến máy tính và giờ đây dễ dàng tự động hoá trên quy mô lớn. Bạn còn nhớ nhà phát triển quan tâm đến hiệu suất của chúng ta không? Công việc bây giờ của cô là viết một dịch vụ hình ảnh có thể tận dụng các gợi ý được cung cấp và trả về phản hồi phù hợp: cô có thể sử dụng bất kỳ ngôn ngữ hoặc máy chủ nào mình thích, hoặc để một dịch vụ của bên thứ ba hoặc một CDN thay mặt cô thực hiện việc này.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Ngoài ra, bạn còn nhớ người này ở trên không? Với gợi ý của khách hàng, thẻ hình ảnh đơn giản hiện sẽ nhận biết được chiều rộng, khung nhìn và DPR mà không cần đánh dấu bổ sung. Nếu cần thêm hướng nghệ thuật, bạn có thể sử dụng thẻ picture như chúng tôi minh họa ở trên. Nếu không, tất cả các thẻ hình ảnh hiện có của bạn sẽ thông minh hơn rất nhiều. Gợi ý về ứng dụng giúp cải thiện các phần tử imgpicture hiện có.

Kiểm soát việc lựa chọn tài nguyên bằng trình chạy dịch vụ

Thực tế, ServiceWorker là một proxy phía máy khách đang chạy trong trình duyệt của bạn. API này chặn mọi yêu cầu gửi đi và cho phép bạn kiểm tra, ghi lại, lưu vào bộ nhớ đệm và thậm chí là tổng hợp các phản hồi. Hình ảnh không có gì khác biệt. Khi bật gợi ý ứng dụng, ServiceWorker đang hoạt động có thể xác định các yêu cầu hình ảnh, kiểm tra gợi ý ứng dụng được cung cấp và xác định logic xử lý riêng.

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
Dịch vụ ServiceWorker được gợi ý từ ứng dụng.

ServiceWorker cung cấp cho bạn toàn quyền kiểm soát phía máy khách đối với việc lựa chọn tài nguyên. Điều này rất quan trọng. Hãy chú trọng vào điều đó, bởi vì khả năng là gần như vô hạn:

  • Bạn có thể ghi lại các giá trị tiêu đề gợi ý ứng dụng do tác nhân người dùng đặt.
  • Bạn có thể nối thêm các giá trị tiêu đề gợi ý ứng dụng mới vào yêu cầu.
  • Bạn có thể ghi lại URL và trỏ yêu cầu hình ảnh tới một máy chủ thay thế (ví dụ: CDN).
    • Thậm chí, bạn có thể di chuyển các giá trị gợi ý từ tiêu đề và vào chính URL nếu việc đó giúp triển khai trong cơ sở hạ tầng của bạn dễ dàng hơn.
  • Bạn có thể lưu các phản hồi vào bộ nhớ đệm và xác định logic riêng để phân phát tài nguyên.
  • Bạn có thể điều chỉnh câu trả lời của mình dựa trên khả năng kết nối của người dùng.
  • Bạn có thể tính đến các chế độ ghi đè lựa chọn ưu tiên của người dùng và ứng dụng.
  • Bạn có thể ... làm bất cứ điều gì trái tim bạn mong muốn, thực sự.

Phần tử picture cung cấp chức năng kiểm soát hướng mỹ thuật cần thiết trong mã đánh dấu HTML. Gợi ý ứng dụng cung cấp chú thích về các yêu cầu hình ảnh thu được, cho phép tự động lựa chọn tài nguyên. ServiceWorker cung cấp khả năng quản lý yêu cầu và phản hồi trên ứng dụng. Đây là môi trường web có thể mở rộng trong thực tế.

Câu hỏi thường gặp về gợi ý của ứng dụng

  1. Gợi ý ứng dụng có sẵn ở đâu? Giao hàng bằng Chrome 46. Đang được xem xét trong FirefoxEdge.

  2. Tại sao người dùng chọn sử dụng tính năng gợi ý từ ứng dụng? Chúng ta muốn giảm thiểu mức hao tổn cho các trang web không sử dụng gợi ý ứng dụng. Để bật tính năng gợi ý cho ứng dụng, trang web nên cung cấp tiêu đề Accept-CH hoặc lệnh <meta http-equiv> tương đương trong mã đánh dấu trang. Khi sử dụng một trong hai gợi ý đó, tác nhân người dùng sẽ thêm gợi ý thích hợp vào mọi yêu cầu về tài nguyên phụ. Trong tương lai, chúng tôi có thể cung cấp thêm một cơ chế để duy trì lựa chọn ưu tiên này cho một nguồn gốc cụ thể, nhờ đó, chúng tôi sẽ cho phép phân phối các gợi ý tương tự trong các yêu cầu điều hướng.

  3. Tại sao cần có gợi ý của ứng dụng khi có ServiceWorker? ServiceWorker không có quyền truy cập vào thông tin về bố cục, tài nguyên và chiều rộng khung nhìn. Ít nhất thì bạn không cần phải thực hiện các quy trình khứ hồi tốn kém và gây trì hoãn đáng kể yêu cầu hình ảnh, ví dụ như khi trình phân tích cú pháp tải trước yêu cầu hình ảnh. Gợi ý ứng dụng tích hợp với trình duyệt để cung cấp dữ liệu này như một phần của yêu cầu.

  4. Có phải ứng dụng chỉ gợi ý cho tài nguyên hình ảnh không? Trường hợp sử dụng chính đằng sau các gợi ý DPR, Viewport-Width và Width là bật tính năng lựa chọn tài nguyên cho các thành phần hình ảnh. Tuy nhiên, các gợi ý như nhau được phân phối cho mọi tài nguyên phụ, bất kể thuộc loại nào. Ví dụ: yêu cầu CSS và JavaScript cũng nhận được cùng thông tin và có thể dùng để tối ưu hoá các tài nguyên đó.

  5. Tại sao một số yêu cầu hình ảnh không báo cáo Chiều rộng? Trình duyệt có thể không biết chiều rộng hiển thị dự định vì trang web phụ thuộc vào kích thước nội tại của hình ảnh. Do đó, gợi ý về Chiều rộng bị bỏ qua đối với các yêu cầu như vậy và đối với các yêu cầu không có "chiều rộng hiển thị", ví dụ: tài nguyên JavaScript. Để nhận được gợi ý về Chiều rộng, hãy nhớ chỉ định giá trị kích thước trên hình ảnh của bạn.

  6. Vậy còn <insert my favorite hint> thì sao? ServiceWorker cho phép các nhà phát triển chặn và sửa đổi (ví dụ: thêm tiêu đề mới) tất cả các yêu cầu gửi đi. Ví dụ: bạn có thể dễ dàng thêm thông tin dựa trên NetInfo để cho biết loại kết nối hiện tại – xem phần "Báo cáo chức năng với ServiceWorker". Các gợi ý "gốc" được chuyển trong Chrome (DPR, Width, Resource-Width) được triển khai trong trình duyệt vì việc triển khai dựa trên SW thuần tuý sẽ làm chậm tất cả các yêu cầu hình ảnh.

  7. Tôi có thể tìm hiểu thêm, xem bản minh hoạ khác và có thể làm gì? Hãy xem tài liệu giải thích và vui lòng mở một vấn đề trên GitHub nếu bạn có ý kiến phản hồi hoặc câu hỏi khác.