Ảnh chiếm phần lớn trọng lượng trang web — HTTP Archive 2025 ghi nhận trung bình ~900 KB ảnh trên trang desktop top site. Format chọn sai → LCP rơi, user rời sớm, SEO mất điểm. Bài này giúp bạn chọn format đúng (PNG, JPEG, WebP, AVIF, SVG) và triển khai responsive gọn trong HTML/CSS.
1. Bốn loại nội dung ảnh — chọn format khác nhau
Trước khi bàn “PNG hay AVIF”, xếp ảnh vào 4 nhóm:
- Ảnh chụp / photo / illustration liên tục tông màu → JPEG / WebP lossy / AVIF.
- Ảnh đồ hoạ / UI / screenshot / logo có viền sắc → PNG / WebP lossless / AVIF (với mode lossless nếu cần).
- Vector (logo, icon) → SVG (độ phân giải độc lập, file nhỏ).
- Trong suốt (transparency) → PNG / WebP / AVIF (JPEG không có alpha).
Dưới đây đi sâu từng format.
2. Profile từng format
2.1. PNG (lossless)
- Không mất chất lượng; có alpha (trong suốt).
- To, đặc biệt với ảnh photo.
- Hợp: screenshot, UI, logo đơn giản cần pixel chính xác.
Tối ưu: pngquant, oxipng, zopflipng — giảm 30–70% file mà không thay đổi pixel (hoặc có tuỳ pngquant).
2.2. JPEG (lossy)
- Tiêu chuẩn hơn 30 năm; support mọi nơi.
- Không có alpha.
- Progressive JPEG cho phép hiển thị ảnh mờ trước khi load xong → UX tốt hơn baseline.
- Chất lượng
quality=75–82thường là sweet spot; sub-sampling 4:2:0 cho ảnh photo; 4:4:4 cho ảnh có chi tiết màu sắc quan trọng (text đỏ trên xanh).
Tool: mozjpeg (libjpeg fork) — encode chặt hơn ~10–15% so với libjpeg-turbo ở cùng chất lượng nhìn.
2.3. WebP
- Google 2010; hỗ trợ lossy và lossless, alpha, animation.
- Thường nhỏ hơn JPEG ~25–35% ở cùng chất lượng cảm quan.
- Hỗ trợ trình duyệt: ~97% (tất cả trình duyệt hiện đại, Safari từ 2020).
- Fallback JPEG/PNG cho IE11 — nếu bạn còn phải hỗ trợ.
Tool: cwebp, sharp, ImageMagick.
2.4. AVIF (AV1 Image File Format)
- Kế thừa codec AV1 (video); ra mắt ~2019.
- Nén tốt hơn WebP 20–50%, hỗ trợ HDR, 10/12-bit, alpha.
- Support: Chrome, Edge, Firefox, Safari 16+ (~90% globally, 2025).
- Encode chậm hơn nhiều so với WebP/JPEG — cần cân nhắc build pipeline.
- Đôi khi artifact ở ảnh có chi tiết nhỏ (text) — test bằng mắt thật.
Tool: avifenc (libavif), sharp (Node), ImageMagick 7.
2.5. SVG
- Vector, XML-based. Scale vô hạn.
- Phù hợp: logo, icon, illustration phẳng.
- Nhớ: minify (SVGO) và inline cho các icon nhỏ (giảm request).
- Cẩn trọng XSS nếu user upload SVG — strip
<script>, external refs.
3. Quyết định nhanh
| Trường hợp | Chọn |
|---|---|
| Ảnh banner/hero, photo | AVIF + WebP + JPEG fallback |
| Thumbnail photo | WebP + JPEG fallback |
| Screenshot UI, diagram | PNG (qua oxipng) hoặc WebP lossless |
| Logo / icon | SVG (inline nếu nhỏ) |
| Animated | WebP animation hoặc AVIF (video H.264/AV1 cho GIF dài) |
Không dùng GIF cho animation lớn — file nặng, chất lượng tệ. Thay bằng video <video> (MP4/WebM) hoặc WebP animation.
4. Responsive images với <picture> và srcset
4.1. <picture> — fallback theo format
Trình duyệt duyệt từ trên xuống, dùng <source> đầu tiên hỗ trợ:
<picture>
<source type="image/avif" srcset="/img/hero.avif" />
<source type="image/webp" srcset="/img/hero.webp" />
<img
src="/img/hero.jpg"
alt="Hero"
width="1600"
height="900"
loading="lazy"
/>
</picture>
Tối thiểu luôn có <img> fallback. Đặt width/height thực tế để tránh CLS (Cumulative Layout Shift).
4.2. srcset + sizes — chọn resolution theo viewport
<img
src="/img/hero-800.jpg"
srcset="
/img/hero-400.jpg 400w,
/img/hero-800.jpg 800w,
/img/hero-1600.jpg 1600w
"
sizes="(min-width: 1024px) 50vw, 100vw"
alt="Hero"
width="1600"
height="900"
loading="lazy"
/>
srcsetliệt kê các phiên bản theo width.sizesnói cho trình duyệt biết kích thước hiển thị thực tế ở mỗi breakpoint.- Trình duyệt chọn ảnh vừa đủ phân giải — không tải ảnh 1600w xuống điện thoại 400w.
Kết hợp cả hai:
<picture>
<source
type="image/avif"
srcset="
/img/hero-400.avif 400w,
/img/hero-800.avif 800w,
/img/hero-1600.avif 1600w
"
sizes="(min-width: 1024px) 50vw, 100vw"
/>
<source
type="image/webp"
srcset="
/img/hero-400.webp 400w,
/img/hero-800.webp 800w,
/img/hero-1600.webp 1600w
"
sizes="(min-width: 1024px) 50vw, 100vw"
/>
<img
src="/img/hero-800.jpg"
srcset="
/img/hero-400.jpg 400w,
/img/hero-800.jpg 800w,
/img/hero-1600.jpg 1600w
"
sizes="(min-width: 1024px) 50vw, 100vw"
alt="Hero"
width="1600"
height="900"
loading="lazy"
/>
</picture>
5. Lazy loading và fetch priority
5.1. loading="lazy"
Trì hoãn tải ảnh ngoài viewport — tiết kiệm băng thông, tăng tốc render. Không đặt lazy cho ảnh above-the-fold (hero, LCP) — sẽ trì hoãn LCP.
5.2. fetchpriority
<img src="/img/hero.jpg" fetchpriority="high" ... />
Nói với trình duyệt ưu tiên tải ảnh này. Thường dùng cho ảnh LCP.
5.3. decoding
<img decoding="async" ... />
Báo trình duyệt được decode off-main-thread; giảm long task. Thường async là an toàn.
5.4. Preload cho LCP
<link
rel="preload"
as="image"
imagesrcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w"
imagesizes="(min-width: 1024px) 50vw, 100vw"
type="image/avif"
/>
Preload chỉ cho ảnh chắc chắn sẽ dùng (LCP). Preload bừa → tốn băng thông.
6. Pipeline build
6.1. Dùng CDN ảnh
Cloudflare Images, imgix, Cloudinary, Akamai Image Manager… nhận URL, trả về ảnh tự động chọn format theo Accept header.
/cdn/hero.jpg?w=800&q=75
Không phải dự án nào cũng cần, nhưng cho site có nhiều ảnh user upload thì đỡ vô vàn việc.
6.2. Self-host với sharp (Node)
import sharp from "sharp";
async function build(input, outDir) {
const sizes = [400, 800, 1600];
for (const w of sizes) {
await sharp(input)
.resize({ width: w })
.jpeg({ quality: 78, mozjpeg: true })
.toFile(`${outDir}/hero-${w}.jpg`);
await sharp(input)
.resize({ width: w })
.webp({ quality: 78 })
.toFile(`${outDir}/hero-${w}.webp`);
await sharp(input)
.resize({ width: w })
.avif({ quality: 50 })
.toFile(`${outDir}/hero-${w}.avif`);
}
}
Chạy trong build-time (Next, Nuxt, Astro, Hugo pipes) → caching hiệu quả, không encode khi request.
6.3. Chi phí CPU encode
AVIF encode có thể chậm 10–50 lần JPEG. Nếu bạn có nhiều ảnh user upload real-time, encode AVIF async (push job queue, serve JPEG/WebP trước, thay ảnh khi AVIF xong) hoặc dùng CDN.
7. Đo ảnh hưởng đến CWV
- LCP: thường là ảnh hero → tối ưu format + preload +
fetchpriority=high→ giảm đáng kể. - CLS: luôn có
width/height(hoặcaspect-ratioCSS) trên ảnh để browser reserve slot. - INP: decode ảnh lớn trên main thread có thể gây long task → dùng
decoding=async.
Kiểm tra với Lighthouse, PageSpeed Insights (trường data lẫn lab), WebPageTest filmstrip xem chuỗi load.
8. Cạm bẫy thường gặp
8.1. Serve ảnh gốc 4000×3000 cho thumbnail 200×200
Browser resize xuống, nhưng bạn đã trả vài MB thay vì vài KB. Pipeline phải sinh nhiều kích thước.
8.2. Đặt loading=lazy cho ảnh LCP
Gây regression LCP 500–1500ms trên mạng chậm. Luôn đánh dấu LCP bằng eager (mặc định).
8.3. Không tính alpha khi chuyển JPEG → WebP/AVIF lossy
Một số tool convert sẽ fill nền đen/trắng. Kiểm tra output — đặc biệt nếu source có transparency.
8.4. Dùng AVIF cho text-heavy screenshot
Ở quality thấp, AVIF nhoè text tệ hơn WebP lossless hay PNG. Test bằng mắt trước khi đổi toàn bộ.
8.5. Quên cache-control dài hạn
Ảnh đã versioned trong URL (hero-abc123.jpg) → Cache-Control: public, max-age=31536000, immutable. Nếu dùng URL chung, set hợp lý để CDN/browser tận dụng.
9. Tối ưu ảnh cho social share
Open Graph, Twitter Card: thường JPEG 1200×630, không nên AVIF — nhiều social crawler chưa hỗ trợ. Serve JPEG/PNG qua URL riêng cho og:image.
10. Tóm tắt
- Phân loại ảnh trước, chọn format sau. SVG cho vector, PNG cho UI/screenshot, WebP/AVIF cho photo, JPEG làm fallback.
- Triển khai
<picture>+srcset/sizesthay vì chỉ một<img src>. - Width/height hoặc
aspect-ratiolà bắt buộc để tránh CLS. - Lazy-load ảnh ngoài viewport; preload +
fetchpriority=highcho LCP. - Build-time encode với
sharp, hoặc dùng CDN ảnh. - Đo CWV trước và sau để xác nhận thay đổi có lợi.
Ảnh tốt không chỉ là “chọn AVIF”. Quan trọng là pipeline tự động sinh đúng size + format, HTML khai báo đầy đủ, và ưu tiên phù hợp giữa ảnh LCP và ảnh phụ. Nhỡ có một thứ ở trên sai, toàn bộ công sức tối ưu cũng rơi đáng kể.