รูป responsive สำหรับมือใหม่: srcset sizes picture และ AVIF WebP ที่ใช้ได้จริง
รูปคือไบต์ที่หนักที่สุดของหน้าแรกส่วนใหญ่ ส่งรูปขนาดพอดีจอด้วย srcset และ sizes เลือก AVIF แล้ว WebP แล้ว JPEG ด้วย picture ใส่ width height และ lazy ให้ถูกที่
- ย่อรูปให้พอดีจอประหยัดได้มากกว่าเปลี่ยนฟอร์แมต ทำก่อนเสมอ
- ใช้ picture เรียง AVIF ก่อน แล้ว WebP แล้ว JPEG เบราว์เซอร์เลือกตัวแรกที่รู้จัก
- ใส่ width และ height ทุกรูป เบราว์เซอร์จองที่ไว้ หน้าไม่กระตุก
- loading lazy ใช้กับรูปใต้จอแรกเท่านั้น ห้ามใส่กับรูปใหญ่สุดด้านบน
หน้าแรกของเว็บส่วนใหญ่ ไบต์ก้อนใหญ่ที่สุดคือรูป ไม่ใช่ JavaScript ข่าวดีคือแก้ได้ด้วย HTML ล้วน ไม่ต้องลง library อะไร: ส่งรูปขนาดที่จอใช้จริง เลือกฟอร์แมตที่เล็กกว่า และบอกขนาดล่วงหน้าเพื่อไม่ให้หน้ากระตุก บทนี้พาทำทีละขั้น พร้อมตัวเลขว่าแต่ละขั้นประหยัดได้เท่าไร
ทดสอบบน Node.js 20.14, sharp 0.35.5 (สคริปต์แปลงรูป) ส่วนข้อมูลการรองรับของเบราว์เซอร์ตรวจกับ caniuse และ MDN ณ ตุลาคม 2026
รูปหนักแค่ไหน
Web Almanac 2024 ของ HTTP Archive วัดหน้าแรกของเว็บทั่วโลก ค่ากลางของรูปต่อหน้าคือ 900 KB บนมือถือ และ 1,054 KB บน desktop จากน้ำหนักหน้าทั้งหมด 2,311 KB และ 2,652 KB คือรูปกินราว 40% ของหน้า มากกว่า JavaScript (613 KB บน desktop) แต่หน้าข้างใน JavaScript จะหนักกว่า
รูปใหญ่ยังมักเป็นตัว LCP คือของชิ้นใหญ่สุดที่ผู้ใช้รอเห็น (ดูบทความ Core Web Vitals) ลดไบต์ของรูปจึงได้ทั้งเน็ตที่ประหยัดและหน้าที่ขึ้นเร็ว
srcset และ sizes: ส่งรูปขนาดที่จอใช้จริง
ปัญหาที่เจอบ่อยที่สุดไม่ใช่ฟอร์แมต แต่คือ ส่งรูปกว้าง 4000 px ไปจอกว้าง 390 px วิธีแก้คือทำรูปไว้หลายขนาด แล้วให้เบราว์เซอร์เลือก
<img
src="/img/hero-800.jpg"
srcset="/img/hero-480.jpg 480w,
/img/hero-800.jpg 800w,
/img/hero-1200.jpg 1200w,
/img/hero-1600.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
width="1600" height="900"
alt="หน้าจอ dashboard ตอนเช้า">
srcsetรายการไฟล์ ตัวเลขตามด้วยwคือ ความกว้างจริงของไฟล์ (pixel) ต้องตรงกับไฟล์sizesบอกว่ารูปจะ แสดงกว้างเท่าไร บนหน้า ตัวอย่างนี้คือ จอกว้างไม่เกิน 800 px รูปเต็มจอ (100vw) จอใหญ่กว่านั้นรูปกว้าง 800 px ถ้าไม่ใส่ sizes ค่าเริ่มต้นคือ100vw- เมื่อมี
wเบราว์เซอร์ไม่ใช้srcแต่ควรใส่ไว้เป็นค่าสำรอง
วิธีที่เบราว์เซอร์คิด: มือถือจอ 390 CSS px ความหนาแน่น 3 เท่า (DPR 3) รูปกว้าง 100vw = 390 CSS px × 3 = ต้องการ 1,170 pixel จึงมักเลือก 1200w ส่วน laptop ที่รูปกว้าง 800 px DPR 2 ต้องการ 1,600 จึงเลือก 1600w เบราว์เซอร์มีสิทธิ์เลือกตัวเล็กกว่านี้ได้ เช่น ตอนเน็ตช้า
picture: AVIF ก่อน WebP แล้ว JPEG
<picture> ให้เราเสนอหลายฟอร์แมต เบราว์เซอร์เช็ค <source> ตามลำดับ ตัวไหนมี type ที่ไม่รู้จักก็ข้าม ถ้าไม่มีตัวไหนใช้ได้เลยจะใช้ <img> ท้ายสุด จึงเรียงตัวที่เล็กที่สุดไว้บนสุด
<picture>
<source type="image/avif"
srcset="/img/hero-480.avif 480w, /img/hero-800.avif 800w,
/img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w"
sizes="(max-width: 800px) 100vw, 800px">
<source type="image/webp"
srcset="/img/hero-480.webp 480w, /img/hero-800.webp 800w,
/img/hero-1200.webp 1200w, /img/hero-1600.webp 1600w"
sizes="(max-width: 800px) 100vw, 800px">
<img src="/img/hero-800.jpg"
srcset="/img/hero-480.jpg 480w, /img/hero-800.jpg 800w,
/img/hero-1200.jpg 1200w, /img/hero-1600.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
width="1600" height="900"
alt="หน้าจอ dashboard ตอนเช้า"
fetchpriority="high">
</picture>
alt, width, height, loading, fetchpriority ใส่ที่ <img> เสมอ ไม่ใช่ที่ <picture> หรือ <source> (<picture> รับแค่ attribute ทั่วไป) CSS อย่าง object-fit ก็ใส่ที่ <img>
สร้างไฟล์หลายขนาดหลายฟอร์แมตด้วยสคริปต์เดียว
ใช้ sharp บน Node.js สคริปต์นี้รันจริงบน Node 20.14 กับ sharp 0.35.5
npm install sharp
// make-images.mjs รัน: node make-images.mjs
import sharp from 'sharp';
const src = 'hero.jpg';
const widths = [480, 800, 1200, 1600];
for (const w of widths) {
const img = sharp(src).resize({ width: w, withoutEnlargement: true });
await img.clone().avif({ quality: 50 }).toFile(`hero-${w}.avif`);
await img.clone().webp({ quality: 75 }).toFile(`hero-${w}.webp`);
await img.clone().jpeg({ quality: 80, mozjpeg: true }).toFile(`hero-${w}.jpg`);
}
ค่าเริ่มต้นของ sharp คือ AVIF quality 50, WebP 80, JPEG 80 ตัวเลข quality ของแต่ละฟอร์แมตเทียบกันตรง ๆ ไม่ได้ ให้เปิดดูด้วยตาแล้วปรับ withoutEnlargement กันไม่ให้ขยายรูปเล็กจนแตก ถ้าใช้ framework อย่าง Astro หรือ Next.js มีคอมโพเนนต์รูปที่ทำขั้นนี้ให้ตอน build อยู่แล้ว
ตัวอย่างคำนวณ: รูปจากกล้อง 3 MB เหลือเท่าไร
สมมติรูป hero ถ่ายจากมือถือ 4000×3000 px เป็น JPEG 3,000 KB อัปโหลดขึ้นเว็บตรง ๆ คนเปิดด้วยมือถือที่ต้องการกว้าง 1,200 px
ขั้น 1 ย่อขนาด จำนวน pixel ลดเหลือ (1200 × 900) ÷ (4000 × 3000) = 1,080,000 ÷ 12,000,000 = 0.09 ถ้าประมาณว่าไบต์ลดตามจำนวน pixel (ของจริงไม่ตรงเป๊ะ ใช้เพื่อดูขนาดคร่าว ๆ) เหลือ 3,000 × 0.09 ≈ 270 KB
ขั้น 2 เปลี่ยนฟอร์แมต ใช้ค่ากลางจากการทดสอบ 600 รูปของ ctrl.blog ที่คุณภาพเท่ากัน (DSSIM เท่ากัน)
| ฟอร์แมต | ลดจาก JPEG (ค่ากลาง) | ขนาดโดยประมาณ |
|---|---|---|
| JPEG 1200 px | - | 270 KB |
| WebP | 31.5% | 270 × 0.685 ≈ 185 KB |
| AVIF | 50.3% | 270 × 0.497 ≈ 134 KB |
จาก 3,000 KB เหลือราว 134 KB หายไป ~95% แต่ดูให้ดี ขั้นย่อขนาดอย่างเดียวตัดไป 2,730 KB ส่วนเปลี่ยนเป็น AVIF ตัดเพิ่มอีก ~136 KB เท่านั้น ลำดับงานจึงชัด: ย่อให้พอดีก่อน ฟอร์แมตคือของแถม
ช่วงที่คาดได้จริง:
- Google บอกว่า WebP แบบ lossy เล็กกว่า JPEG 25-34% ที่คุณภาพ SSIM เท่ากัน
- ctrl.blog: 85% ของรูปได้ WebP เล็กลงอย่างน้อย 20% และ AVIF อย่างน้อย 39.6% ไม่มีรูปไหนที่ AVIF ใหญ่กว่า JPEG แต่มี 2.7% ที่ WebP ใหญ่กว่า
- web.dev เขียนว่าเห็น AVIF ประหยัด มากกว่า 50% จาก JPEG แต่รูปตัวอย่างในบทความเดียวกันต่างกันแค่ราว 6% (18,769 กับ 20,036 ไบต์)
ตัวเลขประหยัดขึ้นกับรูปและ encoder เสมอ แปลงรูปของเว็บเราเองแล้วดูขนาดไฟล์จริง อย่าเชื่อตัวเลขกลางเป็นสัญญา
width height และ lazy: ให้หน้าไม่กระตุก
ใส่ width และ height ทุกรูป เป็นตัวเลขไม่มีหน่วย (ขนาดจริงของรูป) เบราว์เซอร์จะคำนวณสัดส่วนแล้วจองพื้นที่ไว้ก่อนรูปมา ข้อความข้างล่างไม่โดนดันลง CLS ไม่เพิ่ม ถ้า CSS กำหนดความกว้าง เช่น img { max-width: 100%; height: auto; } สัดส่วนจาก width/height ยังใช้ได้
loading="lazy" ให้เบราว์เซอร์รอจนรูปใกล้เข้าจอค่อยโหลด เหมาะกับรูปใต้จอแรก เช่น รูปในเนื้อบทความ แกลเลอรี่ แต่:
- ห้ามใส่กับรูป LCP (รูปใหญ่ด้านบน) เบราว์เซอร์ต้องรอ layout ก่อนถึงจะรู้ว่ารูปอยู่ในจอ รูปมาช้าลง ค่าเริ่มต้นคือ
eagerอยู่แล้ว ไม่ต้องใส่อะไร - รูป lazy ที่ไม่มี width/height มีขนาด 0 จนกว่าจะโหลด ยิ่งต้องใส่ขนาด
decoding="async" บอกว่าไม่ต้องรอถอดรหัสรูปก่อนวาดเฟรมถัดไป MDN บอกว่าผลแทบมองไม่เห็นกับรูป <img> ธรรมดาในหน้า จะช่วยมากกว่ากับรูปที่ใส่ด้วย JavaScript ไม่ต้องใส่ทุกรูปก็ได้
| attribute | รูปใหญ่ด้านบน (LCP) | รูปใต้จอแรก |
|---|---|---|
width / height | ใส่ | ใส่ |
loading | ไม่ใส่ (eager) | lazy |
fetchpriority | high | ไม่ใส่ |
srcset + sizes | ใส่ | ใส่ |
เบราว์เซอร์รองรับแค่ไหน ณ ตุลาคม 2026
| ฟีเจอร์ | Chrome | Edge | Firefox | Safari | ผู้ใช้ทั่วโลก (caniuse) |
|---|---|---|---|---|---|
| WebP | 32+ | 18+ | 65+ | 16.0+ (14-15.6 บางส่วน) | ~97% |
| AVIF | 85+ | 121+ | 93+ | 16.4+ (16.1-16.3 บางส่วน) | ~96% |
loading="lazy" | 77+ | 79+ | 121+ | 16.4+ | ~97% |
fetchpriority | 101+ | 101+ | 132+ | 17.2+ | - |
<picture> ทำให้ไม่ต้องกังวลเรื่องนี้ เบราว์เซอร์ไหนไม่รู้จัก AVIF ก็ไปเอา WebP หรือ JPEG เอง ส่วน attribute ที่เบราว์เซอร์ไม่รู้จักก็แค่ถูกข้าม รูปยังขึ้น
ข้อผิดพลาดที่เจอบ่อย
- ใส่
loading="lazy"ทุกรูปทั้งเว็บ รวมรูป hero ทำให้ LCP แย่ลง wใน srcset ไม่ตรงกับไฟล์จริง เช่น เขียน1200wแต่ไฟล์กว้าง 1000 เบราว์เซอร์คำนวณผิด- ลืม
sizesรูปในคอลัมน์ครึ่งจอโหลดไฟล์ขนาดเต็มจอ - ใส่ width/height ที่
<source>หรือ<picture>ต้องใส่ที่<img> - เรียง
<source>ผิด เอา WebP ไว้บน AVIF เบราว์เซอร์ที่รู้จักทั้งสองจะได้ WebP ตลอด - เซิร์ฟเวอร์ส่ง Content-Type ผิด เช็คด้วย
curl -sI https://example.com/img/hero-800.avifต้องเห็นcontent-type: image/avif - ไม่ได้ดูบนจอจริง ลองที่ความกว้าง 375 px (มีสคริปต์ให้ AI ตรวจแทน) แล้วเปิด DevTools แท็บ Network ดูว่าโหลดไฟล์ไหนจริง
ลำดับที่คุ้มที่สุด: ย่อให้พอดีจอ (srcset + sizes) → ใส่ width/height → เพิ่ม AVIF/WebP ใน picture → lazy เฉพาะรูปใต้จอแรก
แหล่งอ้างอิง
<img>: The Image Embed element — MDN<picture>: The Picture element — MDN- Page Weight, Web Almanac 2024 — HTTP Archive
- An image format for the Web (WebP) — Google for Developers
- Using AVIF to compress images on your site — web.dev
- Comparing AVIF vs WebP file sizes at the same DSSIM — ctrl.blog
- AVIF image format และ WebP image format — caniuse.com
- Output options (avif, webp, jpeg) — sharp
คำถามที่พบบ่อย
ปี 2026 ยังต้องมี JPEG สำรองไหม
ควรมี ณ ตุลาคม 2026 caniuse ให้ AVIF ราว 96% และ WebP ราว 97% ของผู้ใช้ทั่วโลก ที่เหลือคือเบราว์เซอร์เก่า เช่น Safari ก่อน 16.4 หรือ Opera Mini ใส่ JPEG ไว้ใน img ท้าย picture แทบไม่มีต้นทุน เพราะเบราว์เซอร์ที่รู้จัก AVIF จะไม่โหลด JPEG เลย
AVIF เล็กกว่า JPEG ครึ่งหนึ่งทุกรูปไหม
ไม่ทุกรูป การทดสอบ 600 รูปที่คุณภาพเท่ากันได้ค่ากลางที่เล็กลง 50.3% แต่บางรูปได้น้อยกว่านั้นมาก ตัวอย่างใน web.dev เองยังมีคู่ที่ต่างกันแค่ราว 6% ลองกับรูปของเว็บเราเองแล้ววัด
sizes ใส่ผิดจะเป็นอะไร
รูปยังขึ้น แต่เบราว์เซอร์เลือกไฟล์ผิดขนาด ถ้าไม่ใส่ sizes ค่าเริ่มต้นคือ 100vw รูปที่โชว์แค่ครึ่งจอจะโหลดไฟล์ใหญ่เกินสองเท่าของที่ต้องใช้
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ