การเพิ่มประสิทธิภาพรูปภาพด้วยคำสั่ง Angular Image Directive

Kara Erickson
Kara Erickson
Leena Sohoni
Leena Sohoni

ในเดือนพฤษภาคม 2022 ทีม Aurora และ Angular ได้ประกาศว่าพวกเขาจะทำงานร่วมกันในคำสั่งรูปภาพสำหรับ Angular คำสั่งนี้เพิ่งเปิดตัวให้นักพัฒนาซอฟต์แวร์ดูตัวอย่างเป็นส่วนหนึ่งของ Angular v14.2 โพสต์นี้อธิบายวิธีที่คำสั่งรูปภาพใหม่อย่าง NgOptimizedImage รองรับการเพิ่มประสิทธิภาพรูปภาพใน Angular

ข้อมูลเบื้องต้น

รูปภาพเป็นองค์ประกอบที่พบได้ทั่วไปและสำคัญอย่างยิ่งของประสบการณ์ของผู้ใช้เว็บ โดยหน้าเว็บ99.9% สร้างคำขอรูปภาพอย่างน้อย 1 รูป รูปภาพยังเป็นสิ่งที่มีส่วนสำคัญมากที่สุดต่อขนาดหน้าเว็บ โดยคิดเป็นค่ามัธยฐาน 982 กิโลไบต์ต่อหน้า

รูปภาพอาจส่งผลเสียต่อประสิทธิภาพของหน้าเว็บและเมตริก Core Web Vitals เนื่องจากมีจำนวนและขนาดที่เพิ่มขึ้น รูปภาพเป็นองค์ประกอบ Largest Contentful Paint (LCP) ของหน้าเดสก์ท็อป 79.4% ในปี 2021 ด้วยเหตุนี้ พวกเราจึงพยายามอย่างต่อเนื่องเพื่อค้นหารูปภาพที่เพิ่มประสิทธิภาพแล้ว

ทีม Aurora เชื่อว่าการใช้ประโยชน์จากความสามารถของเฟรมเวิร์กจะช่วยแก้ปัญหาที่นักพัฒนาแอปพบได้ทั่วไป การเริ่มต้นครั้งแรกในวงการการเพิ่มประสิทธิภาพรูปภาพคือ คอมโพเนนต์รูปภาพ Next.js พวกเขาถือว่าคอมโพเนนต์นี้เป็นพื้นที่ทดสอบว่าการเพิ่มประสบการณ์ของนักพัฒนาซอฟต์แวร์ (DX) ของการเพิ่มประสิทธิภาพรูปภาพจะทําให้แอปจำนวนมากขึ้นที่ใช้เฟรมเวิร์กมีประสิทธิภาพดีขึ้นหรือไม่

ผลลัพธ์ชุดแรกจากผู้ใช้ Next.js อย่าง Leboncoin นั้นน่าพอใจ Leboncoin พบว่า LCP ดีขึ้นอย่างมาก (จาก 2.4 วินาทีเป็น 1.7 วินาที) หลังจากเริ่มใช้ next/image การนำ next/image มาใช้ในชุมชนในภายหลังมีส่วนทำให้จำนวนต้นทาง Next.js ที่เป็นไปตามเกณฑ์ LCP เพิ่มขึ้น ในไม่ช้าก็เริ่มมีคำขอฟีเจอร์ที่คล้ายกันในเฟรมเวิร์กอื่นๆ ซึ่งหนึ่งในนั้นคือ Angular

ด้วยเหตุนี้ Aurora จึงปรึกษากับ Angular และ Nuxt เพื่อสร้างต้นแบบคอมโพเนนต์รูปภาพสำหรับเฟรมเวิร์กเหล่านี้ คอมโพเนนต์รูปภาพ Nuxt เปิดตัวไปเมื่อปีที่แล้ว ตอนนี้เราเปิดตัวคําสั่งรูปภาพ Angular (NgOptimizedImage) เพื่อนําค่าเริ่มต้นการเพิ่มประสิทธิภาพรูปภาพมายัง Angular แล้ว

โอกาส

Angular เป็นหนึ่งในเฟรมเวิร์ก JavaScript ชั้นนําที่นักพัฒนาซอฟต์แวร์ใช้ในปัจจุบัน แพ็กเกจนี้ใช้โดยต้นทางมากกว่า 50,000 รายการที่ HTTPArchive ทำการ Crawl บนอุปกรณ์เคลื่อนที่ และมีการดาวน์โหลดเกือบ 3 ล้านครั้งต่อสัปดาห์ใน NPM

LCP สําหรับเว็บไซต์ Angular ในช่วง 1 ปีที่ผ่านมา

เมื่อพิจารณาคะแนน Core Web Vitals พบว่าเปอร์เซ็นต์ของต้นทาง Angular ที่เป็นไปตามเกณฑ์ LCP "ดี" ยังต้องปรับปรุง เว็บไซต์ Angular เพียง 18.74% เท่านั้นที่มี LCP ดีในอุปกรณ์เคลื่อนที่ในเดือนมิถุนายน 2022 เนื่องจากรูปภาพเป็นองค์ประกอบ LCP ของหน้าเว็บมากกว่า 70% ในอุปกรณ์เคลื่อนที่และเดสก์ท็อป รูปภาพ LCP ที่ไม่ได้เพิ่มประสิทธิภาพจึงอาจเป็นสาเหตุหลักประการหนึ่งที่ทำให้ LCP ในเว็บไซต์ Angular ช้าลง

คำสั่งรูปภาพ Angular ได้รับการออกแบบมาเพื่อช่วยปรับปรุงตัวเลขเหล่านี้

MVP สําหรับคําสั่ง NgOptimizedImage

MVP ของคำสั่งรูปภาพ Angular สร้างขึ้นจากบทเรียนจากคอมโพเนนต์รูปภาพที่ Aurora สร้างขึ้นจนถึงปัจจุบัน พร้อมกับปรับการออกแบบให้เข้ากับประสบการณ์การแสดงผลฝั่งไคลเอ็นต์ของ Angular ปัญหาด้านการเพิ่มประสิทธิภาพรูปภาพมาตรฐานหลายอย่างได้รับการแก้ไขแล้วโดยการดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้

  • ระบุค่าเริ่มต้นที่รัดกุม
  • แสดงข้อผิดพลาดหรือคำเตือนเพื่อให้เป็นไปตามแนวทางปฏิบัติแนะนำ

ไฮไลต์ของการออกแบบมีดังนี้

  1. การโหลดแบบ Lazy Loading อัจฉริยะ

    รูปภาพที่ผู้ใช้มองไม่เห็นเมื่อหน้าเว็บโหลด (เช่น รูปภาพที่อยู่ด้านล่างของหน้าเว็บหรือรูปภาพภาพสไลด์ที่ซ่อนอยู่) ควรโหลดแบบเลื่อนดูเมื่อถึงเวลา การโหลดแบบเลื่อนเวลาช่วยให้เบราว์เซอร์มีทรัพยากรว่างสำหรับโหลดข้อความ สื่อ หรือสคริปต์ที่สำคัญอื่นๆ รูปภาพส่วนใหญ่ไม่สำคัญและควรใช้การโหลดแบบเลื่อนดูทีละหน้า แต่มีเพียงหน้าเว็บ 7.8% ที่ใช้การโหลดแบบเลื่อนดูทีละหน้าแบบเนทีฟในปี 2021

    คำสั่งรูปภาพ Angular จะโหลดแบบ Lazy Loading รูปภาพที่ไม่สําคัญโดยค่าเริ่มต้น และจะโหลดเฉพาะรูปภาพที่ทําเครื่องหมายเป็น priority โดยเฉพาะ วิธีนี้ช่วยให้มั่นใจได้ว่ารูปภาพส่วนใหญ่จะแสดงลักษณะการโหลดที่ดีที่สุด

  2. การจัดลําดับความสําคัญของรูปภาพสําคัญ

    การเพิ่มคำแนะนำเกี่ยวกับทรัพยากร (เช่น preload หรือ preconnect) เพื่อจัดลําดับความสําคัญในการโหลดรูปภาพที่สําคัญเป็นแนวทางปฏิบัติแนะนํา แต่แอปส่วนใหญ่ไม่ได้ใช้ ตามข้อมูลใน Web Almanac ปี 2021 มีเพียงหน้าเว็บบนอุปกรณ์เคลื่อนที่ 12.7% ที่ใช้คำแนะนำการจองล่วงหน้า และหน้าเว็บบนอุปกรณ์เคลื่อนที่เพียง 22.1% ที่ใช้คำแนะนำการโหลดล่วงหน้า

    คำสั่งรูปภาพจะทำงาน 2 ด้านเมื่อมีการทําเครื่องหมายรูปภาพว่าสำคัญ

    • ซึ่งจะตั้งค่า fetchpriority ของรูปภาพเป็น "high" เพื่อให้เบราว์เซอร์ทราบว่าควรดาวน์โหลดรูปภาพดังกล่าวโดยให้ความสําคัญสูง
    • ในโหมดการพัฒนา การตรวจสอบรันไทม์จะยืนยันว่ามีการใส่คำแนะนำแหล่งข้อมูล preconnect ไว้ซึ่งสอดคล้องกับต้นทางของรูปภาพ

    ในโหมดการพัฒนา คำสั่งดังกล่าวจะใช้ PerformanceObserver API เพื่อยืนยันว่ามีการทําเครื่องหมาย รูปภาพ LCP เป็น priority ตามที่คาดไว้ หากไม่ได้ทําเครื่องหมายเป็น priority ระบบจะแสดงข้อผิดพลาดเพื่อแจ้งให้นักพัฒนาแอปเพิ่มแอตทริบิวต์ priority ลงในรูปภาพ LCP

    ท้ายที่สุดแล้ว การทำงานอัตโนมัติและการปฏิบัติตามข้อกำหนดนี้ช่วยให้มั่นใจได้ว่ารูปภาพ LCP จะมีคำแนะนำ preconnect, ค่าแอตทริบิวต์ fetchpriority เป็น high และจะไม่แสดงแบบ Lazy Load

  3. การกำหนดค่าที่เพิ่มประสิทธิภาพสำหรับเครื่องมือรูปภาพยอดนิยม

    เราขอแนะนําให้แอปพลิเคชัน Angular ใช้ CDN รูปภาพ ซึ่งมักจะให้บริการเพิ่มประสิทธิภาพโดยค่าเริ่มต้น

    คำสั่งนี้สนับสนุนให้ใช้ CDN รูปภาพโดยมอบประสบการณ์การใช้งาน (DX) ที่น่าสนใจอย่างยิ่งสำหรับนักพัฒนาแอปในการกำหนดค่า CDN ในแอป โดยรองรับ API ของโปรแกรมโหลดที่ช่วยให้คุณกำหนดผู้ให้บริการ CDN และ URL พื้นฐานในการกำหนดค่าได้ เมื่อกําหนดค่าแล้ว คุณจะต้องกําหนดชื่อชิ้นงานในมาร์กอัปเท่านั้น ตัวอย่างเช่น

    // in module providers:
    provideImgixLoader('https://mysite.net/assets/')
    
    // in markup
    <img ngSrc="image.png" >
    <img ngSrc="image2.png" >
    

    ซึ่งเทียบเท่ากับการใส่แท็กรูปภาพต่อไปนี้และลดมาร์กอัปที่นักพัฒนาแอปต้องใส่สำหรับรูปภาพทุกรูป

    <img src="https://mysite.net/assets/image.png">
    <img src="https://mysite.net/assets/image2.png">
    

    คำสั่งรูปภาพมีโปรแกรมโหลดในตัวที่มีการกำหนดค่าที่เหมาะสมที่สุดสำหรับ CDN รูปภาพที่ได้รับความนิยมสูงสุด โปรแกรมโหลดเหล่านี้จะจัดรูปแบบ URL รูปภาพโดยอัตโนมัติเพื่อให้แน่ใจว่ามีการใช้รูปแบบรูปภาพและการตั้งค่าการบีบอัดที่แนะนำสำหรับ CDN แต่ละรายการ

  4. ข้อผิดพลาดและคําเตือนในตัว

    นอกจากการเพิ่มประสิทธิภาพในตัวข้างต้นแล้ว คำสั่งดังกล่าวยังมีการตรวจสอบในตัวเพื่อให้มั่นใจว่านักพัฒนาซอฟต์แวร์ได้ปฏิบัติตามแนวทางปฏิบัติแนะนำในมาร์กอัปรูปภาพ คำสั่งรูปภาพจะดำเนินการตรวจสอบต่อไปนี้

    1. รูปภาพที่ไม่มีขนาด: คำสั่งรูปภาพจะแสดงข้อผิดพลาดหากมาร์กอัปรูปภาพไม่ได้กำหนดความกว้างและความสูงอย่างชัดเจน รูปภาพที่ไม่มีการปรับขนาดอาจทำให้เกิดการเปลี่ยนแปลงเลย์เอาต์ ซึ่งส่งผลต่อเมตริกการเปลี่ยนแปลงเลย์เอาต์สะสม (CLS) ของหน้า แนวทางปฏิบัติแนะนำเพื่อป้องกันปัญหานี้คือรูปภาพควรระบุแอตทริบิวต์ width และ height

    2. สัดส่วนการแสดงผล: คำสั่งรูปภาพจะแสดงข้อผิดพลาดเพื่อให้นักพัฒนาซอฟต์แวร์ทราบหากสัดส่วนการแสดงผลของ width:height ที่กําหนดไว้ใน HTML ไม่ใกล้เคียงกับสัดส่วนการแสดงผลจริงของรูปภาพที่แสดงผล ซึ่งอาจทำให้รูปภาพบิดเบี้ยวบนหน้าจอ กรณีนี้อาจเกิดขึ้นได้หาก

      1. คุณกําหนดขนาด (ความกว้างหรือความสูง) ที่ไม่ถูกต้องโดยไม่ตั้งใจ หรือ
      2. หากคุณกำหนดขนาด 1 รายการเป็นเปอร์เซ็นต์ใน CSS แต่ไม่ได้กำหนดอีก 1 รายการ (เช่น width: 100% ต้องกำหนด height: auto เพื่อให้รูปภาพขยายทั้ง 2 ขนาด)
    3. รูปภาพขนาดใหญ่เกิน: หากรูปภาพไม่ได้กำหนด srcset และรูปภาพภายในมีขนาดใหญ่กว่ารูปภาพที่แสดงผลอย่างมาก คำสั่งจะแสดงคำเตือนที่แนะนำให้ใช้แอตทริบิวต์ srcset และ sizes

    4. ความหนาแน่นของรูปภาพ: คำสั่งจะแสดงข้อผิดพลาดหากคุณพยายามใส่รูปภาพใน srcset ที่มีความหนาแน่นของพิกเซลมากกว่า 3x โดยทั่วไปเราไม่แนะนําให้ใช้คําอธิบายที่สูงกว่า 2x เนื่องจากอาจส่งผลให้อุปกรณ์เคลื่อนที่ที่มีความละเอียดสูงต้องดาวน์โหลดรูปภาพขนาดใหญ่ นอกจากนี้ ดวงตาของมนุษย์ไม่สามารถแยกความแตกต่างได้มากนักเมื่อใช้การขยายมากกว่า 2 เท่า

ความท้าทาย

การปรับกลยุทธ์การเพิ่มประสิทธิภาพรูปภาพให้ทำงานภายในเฟรมเวิร์กฝั่งไคลเอ็นต์เป็นปัญหาหลักเมื่อออกแบบ NgOptimizedImage ประสบการณ์การแสดงผลเริ่มต้นใน Next.js คือการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) หรือการสร้างเว็บไซต์แบบคงที่ (SSG) ส่วนใน Angular คือการแสดงผลฝั่งไคลเอ็นต์ (CSR) แม้ว่า Angular จะรองรับไลบรารี SSR อย่าง angular/universal แต่แอป Angular ส่วนใหญ่ (~60%) ใช้ CSR

คำสั่งรูปภาพสร้างขึ้นทั้งหมดสําหรับ CSR เพื่อให้สอดคล้องกับกรณีการใช้งานทั่วไปในแอป Angular ข้อจำกัดนี้ทำให้เกิดข้อจำกัดเพิ่มเติม และทีมต้องคิดหาวิธีสร้างการเพิ่มประสิทธิภาพที่เฉพาะเจาะจงสำหรับแอป CSR

ปัญหาที่พบมีดังนี้

  1. คำแนะนำเกี่ยวกับแหล่งข้อมูลที่รองรับ

    การโหลดเนื้อหาสําคัญล่วงหน้าช่วยให้เบราว์เซอร์ค้นพบเนื้อหาเหล่านั้นได้เร็วขึ้น อย่างไรก็ตาม การใส่คำแนะนำแหล่งข้อมูลในแอป Angular มีความซับซ้อนเนื่องจากเหตุผลต่อไปนี้

    การเพิ่มด้วยตนเอง: นักพัฒนาแอปจะเพิ่มคำแนะนำทรัพยากร preload ด้วยตนเองได้ยาก Angular ใช้ไฟล์ index.html ที่แชร์ไฟล์เดียวสําหรับทั้งโปรเจ็กต์หรือสําหรับเส้นทางทั้งหมดในเว็บไซต์ ดังนั้น <head> ของเอกสารจึงเหมือนกันสำหรับทุกเส้นทาง (อย่างน้อยก็ในเวลาที่แสดง) การเพิ่มคำแนะนำ preload ลงใน <head> หมายความว่าระบบจะโหลดทรัพยากรไว้ล่วงหน้าสำหรับทุกเส้นทาง แม้ว่าจะไม่จำเป็นก็ตาม ดังนั้นจึงไม่แนะนำให้เพิ่มคำแนะนำ preload ด้วยตนเอง

    การเพิ่มอัตโนมัติระหว่างการแสดงผล: การใช้เฟรมเวิร์กเพื่อเพิ่มคำแนะนำการโหลดล่วงหน้าในส่วนหัวของเอกสารระหว่างการแสดงผลในแอป CSR ไม่ได้ช่วยอะไร เนื่องจากการแสดงผลเกิดขึ้นหลังจากที่ดาวน์โหลดและเรียกใช้ JavaScript แล้ว <head> จึงแสดงผลช้าเกินไปที่จะมีประโยชน์

    สำหรับคำสั่งเวอร์ชันแรก คำแนะนำ preconnect และ fetchpriority ร่วมกันจะจัดลําดับความสําคัญของรูปภาพแทน preload อย่างไรก็ตาม ตอนนี้ Aurora กำลังทำงานร่วมกับทีม Angular CLI เพื่อเปิดใช้การแทรกคำแนะนำทรัพยากรโดยอัตโนมัติเมื่อถึงเวลาสร้าง โปรดติดตามดู

  2. การเพิ่มประสิทธิภาพขนาดและรูปแบบรูปภาพบนเซิร์ฟเวอร์

    เนื่องจากโดยทั่วไปแอป Angular จะแสดงผลฝั่งไคลเอ็นต์ ระบบจึงไม่สามารถบีบอัดรูปภาพในระบบไฟล์ ณ เวลาที่มีคำขอและจะแสดงรูปภาพตามที่เป็นอยู่ ดังนั้น เราขอแนะนำให้ใช้ CDN รูปภาพเพื่อบีบอัดรูปภาพและแปลงเป็นรูปแบบสมัยใหม่ เช่น WebP หรือ AVIF ตามคำขอ

    แม้ว่าคำสั่งจะไม่บังคับให้ใช้ CDN รูปภาพ แต่เราขอแนะนำให้ใช้ CDN กับคำสั่งและโปรแกรมโหลดในตัวเพื่อให้ใช้ตัวเลือกการกำหนดค่าที่ถูกต้อง

ผลกระทบ

การสาธิตต่อไปนี้แสดงให้เห็นความแตกต่างที่คำสั่งรูปภาพ Angular สามารถสร้างต่อประสิทธิภาพของรูปภาพ โดยจะเปรียบเทียบเว็บไซต์ 2 เว็บไซต์ ดังนี้

เว็บไซต์ที่ 1: ใช้องค์ประกอบ <img> เดิมที่มีรูปภาพที่แสดงผ่าน CDN ของ Imgix (พร้อมตัวเลือกการกําหนดค่าเริ่มต้น)

เว็บไซต์ที่ 2: ใช้คําสั่งรูปภาพสําหรับรูปภาพทั้งหมด รวมถึงการเพิ่มประสิทธิภาพที่แนะนําโดยคําเตือนหรือข้อผิดพลาดที่ไดเรกทีฟแสดงโดยตรง

การเปรียบเทียบแถบสไลด์: เว็บไซต์ที่ 1 ที่มีแท็กรูปภาพเนทีฟเทียบกับเว็บไซต์ที่ 2 ที่มีคำสั่งรูปภาพ Angular

ทีมได้ทํางานร่วมกับพาร์ทเนอร์เพื่อตรวจสอบผลกระทบด้านประสิทธิภาพของคําสั่งรูปภาพในแอปพลิเคชัน Angular ขององค์กรจริง

โดยหนึ่งในพาร์ทเนอร์เหล่านี้คือ Land's End เว็บไซต์ดังกล่าวถือเป็นกรณีทดสอบที่ดีสำหรับผลลัพธ์ที่แอปพลิเคชันจริงอาจเห็น

การทดสอบในแล็บของ Lighthouse ดำเนินการในสภาพแวดล้อม QA ก่อนและหลังการใช้คำสั่งรูปภาพ LCP มัธยฐานในเดสก์ท็อปลดลงจาก 12.0 วินาทีเป็น 3.0 วินาที ซึ่งถือเป็นการปรับปรุง LCP ขึ้น 75% ในอุปกรณ์เคลื่อนที่ ค่ามัธยฐานของ LCP ลดลงจาก 20.2 วินาทีเป็น 12.0 วินาที (ปรับปรุงขึ้น 40.6%)

แผนกลยุทธ์ในอนาคต

นี่เป็นเพียงการเปิดตัวครั้งแรกของการออกแบบสำหรับคำสั่งรูปภาพ Angular เรามีแผนที่จะเพิ่มฟีเจอร์อื่นๆ อีกมากมายในเวอร์ชันในอนาคต ซึ่งรวมถึง

  • การรองรับรูปภาพที่ปรับเปลี่ยนตามพื้นที่โฆษณาได้ดียิ่งขึ้น

    ปัจจุบัน NgOptimizedImage รองรับการใช้ srcset แต่ต้องระบุแอตทริบิวต์ srcset และ sizes ด้วยตนเองสำหรับรูปภาพแต่ละรูป ในอนาคต คำสั่งนี้จะสร้างแอตทริบิวต์ srcset และ sizes โดยอัตโนมัติ

  • การแทรกคำแนะนำแหล่งข้อมูลโดยอัตโนมัติ

    คุณอาจผสานรวมกับ Angular CLI เพื่อสร้างแท็กการจองล่วงหน้าและโหลดล่วงหน้าสําหรับรูปภาพ LCP ที่สําคัญได้

  • การรองรับ Angular SSR

    เวอร์ชัน MVP ได้รับการออกแบบโดยคำนึงถึงข้อจำกัดของ CSR ของ Angular แต่คุณก็ควรสำรวจโซลูชันการเพิ่มประสิทธิภาพรูปภาพสำหรับ Angular SSR (angular/universal) ด้วย

  • การปรับปรุงประสบการณ์การใช้งานสำหรับนักพัฒนาแอป

    NgOptimizedImage กำหนดให้ต้องระบุแอตทริบิวต์ width และ height สำหรับรูปภาพแต่ละรูป อย่างไรก็ตาม การระบุข้อมูลเหล่านี้สำหรับรูปภาพแต่ละรูปอาจทำให้นักพัฒนาแอปบางรายรู้สึกเหนื่อยหน่าย เราอาจปรับปรุงประสบการณ์การใช้งานของนักพัฒนาแอปในส่วนนี้ในรุ่นถัดไป ดังนี้

    1. รองรับโหมดเพิ่มเติม (คล้ายกับตัวเลือกเลย์เอาต์รูปภาพ "fill" ใน Next.js) ที่ไม่จําเป็นต้องกําหนดความกว้าง/ความสูงอย่างชัดเจน
    2. การใช้การผสานรวม CLI เพื่อตั้งค่าความกว้างและความสูงของรูปภาพในเครื่องโดยอัตโนมัติโดยพิจารณาจากขนาดจริงของรูปภาพ

บทสรุป

คำสั่งรูปภาพ Angular จะพร้อมให้นักพัฒนาซอฟต์แวร์ใช้งานเป็นระยะๆ โดยเริ่มจากเวอร์ชันตัวอย่างสำหรับนักพัฒนาซอฟต์แวร์ใน v14.2.0 ลองใช้ NgOptimizedImage แล้วแสดงความคิดเห็น

ขอขอบคุณ Katie Hempenius และ Alex Castle เป็นอย่างยิ่งสำหรับการมีส่วนร่วม