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ạnh | Lưu ý | Dùng cho |
|---|---|---|---|
| AVIF | thường nén ảnh chụp rất tốt, có alpha/HDR | encode/decode và kết quả tùy nội dung | ảnh chụp, hero sau khi đo |
| WebP | nén lossy/lossless tốt, hỗ trợ rộng | đôi khi lớn hơn AVIF, đôi khi lại phù hợp hơn | thay JPEG/PNG đa năng |
| JPEG/PNG | tương thích rộng, pipeline đơn giản | thường lớn hơn codec mới; PNG hợp lossless/alpha | fallback hoặc asset đặc thù |
| SVG | vector co giãn, DOM/text có thể truy cập | không hợp ảnh chụp; SVG phức tạp vẫn có thể nặng | icon, 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ủaastro: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êmfetchpriority="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ặcmetadatacho 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
- Đã thử AVIF/WebP và chọn codec nhỏ nhất ở chất lượng cảm nhận đạt yêu cầu?
srcset/sizesđể gửi đúng kích thước theo màn hình?- Ảnh LCP không lazy; đã thử và đo
fetchpriority="high"? - Ảnh dưới màn hình
loading="lazy"? - Mọi ảnh có width/height; art-directed
<source>khác tỉ lệ cũng có kích thước? - Video
preload="none"+ poster; embed nặng dùng facade? - 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/sizesgử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.