jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Web Performance · Part 9 — Images & Media

Images are often the heaviest part of a page. Modern formats (AVIF/WebP), responsive images with srcset/sizes, lazy loading in the right places, decoding, and video/iframe optimization.

Ảnh thường chiếm phần lớn dung lượng tải của một trang. Tối ưu ảnh đúng cách vừa giảm mạnh byte tải, vừa cải thiện LCP (Phần 5) và CLS (Phần 7). Phần này gom các kỹ thuật ảnh/media từ cơ bản tới nâng cao.


1. Định dạng hiện đại: AVIF & WebP

Định dạng ảnh hưởng lớn tới dung lượng, nhưng không có codec thắng cho mọi asset. Hãy encode nhiều biến thể ở cùng chất lượng cảm nhận rồi so byte và thời gian decode:

Định dạngĐiểm mạnhLưu ýDùng cho
AVIFthường nén ảnh chụp rất tốt, có alpha/HDRencode/decode và kết quả tùy nội dungảnh chụp, hero sau khi đo
WebPnén lossy/lossless tốt, hỗ trợ rộngđôi khi lớn hơn AVIF, đôi khi lại phù hợp hơnthay JPEG/PNG đa năng
JPEG/PNGtương thích rộng, pipeline đơn giảnthường lớn hơn codec mới; PNG hợp lossless/alphafallback hoặc asset đặc thù
SVGvector co giãn, DOM/text có thể truy cậpkhông hợp ảnh chụp; SVG phức tạp vẫn có thể nặngicon, logo, hình học

Dùng <picture> để trình duyệt chọn <source> đầu tiên có media/type phù hợp; vì vậy đặt codec bạn muốn ưu tiên trước và giữ fallback an toàn:

<picture>
  <source srcset="/hero.avif" type="image/avif" />
  <source srcset="/hero.webp" type="image/webp" />
  <img src="/hero.jpg" alt="..." width="1200" height="600" /> <!-- fallback + preserve dimensions -->
</picture>

2. Responsive images: đúng kích thước cho đúng màn hình

Gửi ảnh 2000px cho điện thoại 400px là lãng phí băng thông. srcset + sizes cho trình duyệt chọn kích thước phù hợp với slot hiển thị và DPR (mật độ điểm ảnh):

<img
  src="/photo-800.jpg"
  srcset="/photo-400.jpg 400w, /photo-800.jpg 800w, /photo-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 800px"
  alt="..."
  width="800"
  height="600"
/>

Cách đọc: srcset liệt kê các bản kèm chiều rộng thật (400w…); sizes mô tả ảnh sẽ hiển thị rộng bao nhiêu ở mỗi breakpoint. Trình duyệt tự tính và tải bản nhỏ nhất đủ nét. Trong thực tế, điều này có thể tiết kiệm nhiều byte mà không giảm chất lượng cảm nhận.

Đừng tự sinh bản resize bằng tay — dùng pipeline ảnh (Astro <Image>, Next <Image>, hoặc dịch vụ ảnh/CDN) để tự tạo các kích thước + định dạng. Project này dùng <Image> của astro:assets để auto-optimize.


3. Lazy loading: hoãn ảnh dưới màn hình

Ảnh ngoài viewport ban đầu không cần tải ngay. loading="lazy" để trình duyệt chỉ tải khi sắp cuộn tới:

<!-- ✅ below-the-fold image: native lazy loading -->
<img src="/below.avif" loading="lazy" width="600" height="400" alt="..." />

Quy tắc vàng (nhắc lại từ Phần 5):

  • Ảnh trên màn hình đầu / ảnh LCP: KHÔNG lazy-load (mặc định eager), thêm fetchpriority="high".
  • Ảnh dưới màn hình: loading="lazy".

Lazy-load nhầm ảnh LCP là một trong những lỗi làm hỏng LCP phổ biến nhất.


4. decoding: hint về thời điểm trình bày ảnh

decoding không phải cam kết “decode ngoài main thread”. Nó là hint cho phép trình duyệt trình bày ảnh tách khỏi các cập nhật nội dung khác. auto là mặc định và thường là lựa chọn tốt; async có thể hữu ích cho ảnh lớn không critical để nội dung khác không phải chờ:

<img src="/photo.avif" decoding="async" loading="lazy" alt="..." width="800" height="600" />

Với ảnh LCP, đừng tự động ép sync hay async; giữ auto trước, đo LCP/trace rồi mới đổi. Với ảnh chèn động bằng JavaScript, await img.decode() hữu ích hơn để chỉ đưa ảnh vào DOM sau khi tải và decode xong.


5. Đặt chỗ tránh CLS (liên hệ Phần 7)

Luôn khai width/height (hoặc aspect-ratio) để ảnh chừa chỗ trước, tránh nhảy layout khi tải xong. Nếu <picture> dùng art direction với các crop khác tỉ lệ, khai width/height tương ứng trên từng <source>. Có thể thêm placeholder mờ (LQIP) hoặc màu trung bình để chỗ ảnh có nội dung trong lúc chờ, nhưng đừng nhúng base64 lớn làm phình HTML critical:

<!-- A cheap color placeholder while the real image loads. -->
<img
  src="/photo.avif"
  width="800"
  height="600"
  style="background: #1a1a1a;"
  alt="..."
/>

6. Video & iframe

Video và embed (YouTube, bản đồ) thường rất nặng:

  • Đặt kích thước/khung cho <video>/<iframe> (tránh CLS).
  • preload="none" hoặc metadata cho video không tự phát, để không tải cả file sớm.
  • Poster nhẹ thay vì tải video ngay: hiện ảnh poster, chỉ tải video khi người dùng bấm play.
  • Facade cho embed nặng: thay vì nhúng iframe YouTube ngay (kéo theo cả MB JS), hiện một ảnh thumbnail + nút play, chỉ tải iframe thật khi bấm (kỹ thuật “lite-youtube”).
  • loading="lazy" cho iframe dưới màn hình; không lazy embed nằm trong viewport ban đầu.
<video poster="/poster.avif" preload="none" controls width="800" height="450">
  <source src="/clip.mp4" type="video/mp4" />
</video>

7. Checklist ảnh & media

  1. Đã thử AVIF/WebP và chọn codec nhỏ nhất ở chất lượng cảm nhận đạt yêu cầu?
  2. srcset/sizes để gửi đúng kích thước theo màn hình?
  3. Ảnh LCP không lazy; đã thử và đo fetchpriority="high"?
  4. Ảnh dưới màn hình loading="lazy"?
  5. Mọi ảnh có width/height; art-directed <source> khác tỉ lệ cũng có kích thước?
  6. Video preload="none" + poster; embed nặng dùng facade?
  7. Dùng pipeline ảnh/CDN để tự sinh biến thể, không làm tay?

Tóm tắt

  • Thử AVIF/WebP/JPEG/PNG trên chính asset; dùng <picture> khi cần fallback/art direction, SVG cho vector.
  • srcset/sizes gửi đúng kích thước theo màn hình — tiết kiệm byte lớn.
  • Lazy-load ảnh dưới màn hình, nhưng không lazy ảnh LCP (thêm fetchpriority="high").
  • Luôn khai width/height để chống CLS; cân nhắc placeholder/LQIP cho cảm nhận.
  • Video/embed: đặt khung, preload="none", poster, và facade cho embed nặng.

Phần tiếp theo: Fonts — chiến lược nạp, FOIT/FOUT, font-display và subsetting.