บทความ › เว็บและแอป

รูป responsive สำหรับมือใหม่: srcset sizes picture และ AVIF WebP ที่ใช้ได้จริง

รูปคือไบต์ที่หนักที่สุดของหน้าแรกส่วนใหญ่ ส่งรูปขนาดพอดีจอด้วย srcset และ sizes เลือก AVIF แล้ว WebP แล้ว JPEG ด้วย picture ใส่ width height และ lazy ให้ถูกที่

เริ่มต้น อ่าน 11 นาที 22 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • ย่อรูปให้พอดีจอประหยัดได้มากกว่าเปลี่ยนฟอร์แมต ทำก่อนเสมอ
  • ใช้ 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
WebP31.5%270 × 0.685 ≈ 185 KB
AVIF50.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 เสมอ แปลงรูปของเว็บเราเองแล้วดูขนาดไฟล์จริง อย่าเชื่อตัวเลขกลางเป็นสัญญา

รูปจากกล้อง JPEG3,000 KBย่อเหลือ 1200px270 KBWebP~185 KBAVIF~134 KB
ย่อขนาดตัดไป 2,730 KB ส่วนเปลี่ยนฟอร์แมตตัดเพิ่มแค่ ~136 KB

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
fetchpriorityhighไม่ใส่
srcset + sizesใส่ใส่

เบราว์เซอร์รองรับแค่ไหน ณ ตุลาคม 2026

ฟีเจอร์ChromeEdgeFirefoxSafariผู้ใช้ทั่วโลก (caniuse)
WebP32+18+65+16.0+ (14-15.6 บางส่วน)~97%
AVIF85+121+93+16.4+ (16.1-16.3 บางส่วน)~96%
loading="lazy"77+79+121+16.4+~97%
fetchpriority101+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 เฉพาะรูปใต้จอแรก

แหล่งอ้างอิง

คำถามที่พบบ่อย

ปี 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 รูปที่โชว์แค่ครึ่งจอจะโหลดไฟล์ใหญ่เกินสองเท่าของที่ต้องใช้

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#เว็บเริ่มต้น

JPEG XL กลับมาใน Chrome แล้ว: ทำไมเคยถูกถอด และควรใช้ตอนนี้ไหม

Chrome ถอด JPEG XL ปี 2022 แล้วเอากลับมาใน Chrome 155 ด้วยตัวถอดรหัส jxl-rs ที่เขียนด้วย Rust ดูว่าเบราว์เซอร์ไหนรองรับ ใส่ใน picture ยังไง และเมื่อไรคุ้มกว่า AVIF

อ่าน 9 นาที
#เว็บกลาง

Core Web Vitals ปี 2026: LCP INP CLS วัดยังไง หาตัวการ และแก้ทีละตัว

สามตัวเลขที่ Google ใช้วัดว่าเว็บเร็วและนิ่งพอไหม เกณฑ์ 2.5 วินาที 200 ms และ 0.1 ที่ p75 ข้อมูลจริงต่างจากข้อมูลแล็บยังไง และวิธีแก้ที่ได้ผลของแต่ละตัว

อ่าน 13 นาที
#เว็บกลาง

ทางร้อนต้องไม่แตะฐานข้อมูล: cache สั้น จำคำตอบว่าไม่มี ปิดแล้วต้องปิดจริง

ระบบที่ถูกเรียกวินาทีละหลายร้อยครั้งบนเครื่อง 2GB อยู่รอดด้วย Redis ตั้ง TTL ยังไง จำคำตอบว่าไม่มีทำไม และทำไมสวิตช์ปิดต้องล้าง cache ทันที จากระบบที่ใช้จริง

อ่าน 7 นาที