เมื่อเดือนพฤษภาคม 2022 ทีม Aurora และ Angular ได้ประกาศว่าจะทำงานร่วมกันตามคำสั่งเกี่ยวกับรูปภาพสำหรับ Angular เราเพิ่งเปิดตัวคำสั่งสำหรับตัวอย่างสำหรับนักพัฒนาซอฟต์แวร์ซึ่งเป็นส่วนหนึ่งของ Angular v14.2 โพสต์นี้พูดถึงวิธีที่คำสั่งรูปภาพใหม่ ซึ่งก็คือ NgOptimizedImage
จะรองรับการเพิ่มประสิทธิภาพรูปภาพใน Angular
ที่มา
รูปภาพเป็นองค์ประกอบสำคัญที่พบได้ทั่วไปของผู้ใช้เว็บ เนื่องจาก 99.9% ของหน้าเว็บสร้างคำขอรูปภาพอย่างน้อย 1 รูป นอกจากนี้รูปภาพยังเป็นปัจจัยที่สำคัญที่สุดต่อน้ำหนักหน้าเว็บ โดยมีค่ามัธยฐานอยู่ที่ 982 กิโลไบต์ต่อหน้า
เนื่องจากจำนวนและขนาดที่เพิ่มขึ้น รูปภาพจึงอาจขัดขวางประสิทธิภาพของหน้าเว็บและส่งผลต่อเมตริก Core Web Vitals สําหรับหน้าเว็บในเดสก์ท็อป 79.4% รูปภาพเป็นองค์ประกอบ Largest Contentful Paint (LCP) ในปี 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
Opportunity
Angular เป็นหนึ่งในเฟรมเวิร์ก JavaScript ชั้นนำที่นักพัฒนาซอฟต์แวร์ใช้กันในปัจจุบัน มีต้นทางมากกว่า 5 หมื่นครั้งที่รวบรวมข้อมูลโดย HTTPArchive บนอุปกรณ์เคลื่อนที่และมีการดาวน์โหลดเกือบ 3 ล้านครั้งต่อสัปดาห์บน NPM
![LCP สำหรับเว็บไซต์ Angular ในช่วง 1 ปีที่ผ่านมา](https://developer.chrome.com/static/blog/angular-image-directive/image/lcp-angular-websites-th-313c9b6e8afd2.png?authuser=5&hl=th)
เมื่อดูคะแนน Core Web Vitals แล้ว เปอร์เซ็นต์ของต้นทางของ Angular ที่ตรงตามเกณฑ์ LCP ที่ "ดี" ยังคงต้องปรับปรุง เว็บไซต์ Angular เพียง 18.74% ที่มี LCP ที่ดีในอุปกรณ์เคลื่อนที่ในเดือนมิถุนายน 2022 เนื่องจากรูปภาพเป็นองค์ประกอบ LCP ของหน้าเว็บมากกว่า 70% บนอุปกรณ์เคลื่อนที่และเดสก์ท็อป รูปภาพ LCP ที่ไม่ได้เพิ่มประสิทธิภาพจึงอาจเป็นหนึ่งในสาเหตุหลักที่ทำให้ LCP มีคุณภาพต่ำในเว็บไซต์ Angular
คำสั่งรูปภาพ Angular ได้รับการออกแบบมาเพื่อช่วยปรับปรุงตัวเลขเหล่านี้
MVP สำหรับคำสั่ง NgEnhancedImage
MVP ของ Angular image Directive สร้างขึ้นจากบทเรียนจากองค์ประกอบรูปภาพที่ Aurora สร้างขึ้นในปัจจุบัน ขณะเดียวกันก็ปรับการออกแบบให้เข้ากับประสบการณ์การแสดงผลฝั่งไคลเอ็นต์ของ Angular ปัญหาการเพิ่มประสิทธิภาพรูปภาพมาตรฐานส่วนใหญ่ได้รับการแก้ไขแล้วใน
- การกำหนดค่าเริ่มต้นที่รัดกุม
- การแสดงข้อผิดพลาดหรือคำเตือนเพื่อให้เป็นไปตามแนวทางปฏิบัติแนะนำ
การออกแบบนี้มีจุดเด่นดังต่อไปนี้
การโหลดแบบ Lazy Loading อัจฉริยะ
รูปภาพที่ผู้ใช้มองไม่เห็นเมื่อโหลดหน้าเว็บ (เช่น รูปภาพครึ่งหน้าล่างหรือรูปภาพภาพสไลด์ที่ซ่อนอยู่) ควรโหลดแบบ Lazy Loading การโหลดแบบ Lazy Loading จะเพิ่มทรัพยากรของเบราว์เซอร์เพื่อโหลดข้อความ สื่อ หรือสคริปต์ที่สำคัญอื่นๆ รูปภาพส่วนใหญ่ไม่สำคัญและควรเป็นการโหลดแบบ Lazy Loading แต่มีหน้าเว็บเพียง 7.8% ที่ใช้การโหลดแบบ Lazy Loading เริ่มต้นในปี 2021
คำสั่งรูปภาพ Angular จะโหลดรูปภาพที่ไม่สำคัญโดยค่าเริ่มต้นและโหลดเฉพาะรูปภาพที่ทำเครื่องหมายพิเศษว่า
priority
เท่านั้น วิธีนี้ช่วยให้รูปภาพส่วนใหญ่แสดงลักษณะการโหลดที่เหมาะสมที่สุดการจัดลำดับความสำคัญของรูปภาพที่สำคัญ
การเพิ่มคําแนะนําด้านทรัพยากร (เช่น
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 Loading- โดยจะตั้งค่า fetchPriority ของรูปภาพเป็น
การกำหนดค่าที่เพิ่มประสิทธิภาพสำหรับเครื่องมือรูปภาพยอดนิยม
เราขอแนะนำให้แอปพลิเคชัน Angular ใช้ CDN รูปภาพ ซึ่งมักให้บริการเพิ่มประสิทธิภาพโดยค่าเริ่มต้น
คำสั่งนี้ส่งเสริมการใช้ CDN รูปภาพโดยการให้ประสบการณ์สำหรับนักพัฒนาซอฟต์แวร์ (DX) ที่น่าสนใจเป็นพิเศษในการกำหนดค่าในแอป รองรับ 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 แต่ละรายการ
ข้อผิดพลาดและคำเตือนในตัว
นอกเหนือจากการเพิ่มประสิทธิภาพในตัวข้างต้นแล้ว คำสั่งนี้ยังมีการตรวจสอบในตัวเพื่อให้แน่ใจว่านักพัฒนาแอปได้ทำตามแนวทางปฏิบัติแนะนำในมาร์กอัปรูปภาพแล้ว คำสั่งรูปภาพจะดำเนินการตรวจสอบดังต่อไปนี้
รูปภาพที่ไม่มีขนาด: คำสั่งรูปภาพจะแสดงข้อผิดพลาดหากมาร์กอัปรูปภาพไม่ได้กำหนดความกว้างและความสูงที่ชัดเจน รูปภาพที่ไม่ได้ปรับขนาดอาจทำให้เกิดการเปลี่ยนเลย์เอาต์ ซึ่งส่งผลต่อเมตริก Cumulative Layout Shift (CLS) ของหน้าเว็บ แนวทางปฏิบัติแนะนำเพื่อป้องกันปัญหานี้คือ รูปภาพควรมีการระบุแอตทริบิวต์
width
และheight
สัดส่วนภาพ: คำสั่งรูปภาพแสดงข้อผิดพลาดเพื่อแจ้งให้นักพัฒนาแอปทราบว่าสัดส่วนภาพ
width
:height
ที่กำหนดไว้ใน HTML ไม่ใกล้เคียงกับสัดส่วนภาพจริงของรูปภาพที่แสดงผล ซึ่งอาจทำให้รูปภาพดูบิดเบี้ยวบนหน้าจอได้ เหตุการณ์นี้อาจเกิดขึ้นได้หาก- คุณกำหนดขนาดที่ไม่ถูกต้อง (ความกว้างหรือความสูง) โดยไม่ได้ตั้งใจ หรือ
- หากคุณกำหนดมิติข้อมูลหนึ่งตามเปอร์เซ็นต์ใน CSS แต่ไม่ได้กำหนดอีกมิติข้อมูล (เช่น
width: 100%
ต้องใช้height: auto
เพื่อให้รูปภาพมีขนาดใหญ่ขึ้นในทั้ง 2 มิติข้อมูล)
รูปภาพที่ใหญ่เกินขนาด: หากรูปภาพไม่ได้กำหนด
srcset
และรูปภาพภายในมีขนาดใหญ่กว่ารูปภาพที่แสดงผลอย่างมาก คำสั่งจะแสดงคำเตือนที่แนะนำการใช้แอตทริบิวต์srcset
และsizes
ความหนาแน่นของรูปภาพ: คำสั่งนี้จะแสดงข้อผิดพลาดหากคุณพยายามรวมรูปภาพใน
srcset
ที่มีความหนาแน่นของพิกเซลมากกว่า3x
โดยทั่วไปเราไม่แนะนำให้ใช้คำอธิบายที่สูงกว่า2x
เนื่องจากผลที่ตามมาจากการบังคับให้อุปกรณ์เคลื่อนที่ความละเอียดสูงดาวน์โหลดรูปภาพขนาดใหญ่โดยไม่ตั้งใจ ยิ่งไปกว่านั้น ดวงตาของมนุษย์ไม่สามารถบอกความแตกต่างได้มากกว่า 2 เท่า
การท้า
การปรับกลยุทธ์การเพิ่มประสิทธิภาพให้รูปภาพเพื่อให้ทํางานภายในเฟรมเวิร์กฝั่งไคลเอ็นต์ถือเป็นความท้าทายหลักในการออกแบบ NgOptimizedImage
ประสบการณ์การแสดงผลเริ่มต้นใน Next.js คือการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) หรือการสร้างเว็บไซต์แบบคงที่ (SSG) ขณะที่ใน Angular คือการแสดงผลฝั่งไคลเอ็นต์ (CSR) แม้ว่า Angular จะรองรับไลบรารี SSR ซึ่งก็คือ Angular/Universal - แอป Angular ส่วนใหญ่ (ประมาณ 60%) จะใช้ CSR
คำสั่งเกี่ยวกับรูปภาพสร้างขึ้นเพื่อให้ CSR สอดคล้องกับกรณีการใช้งานทั่วไปในแอป Angular ซึ่งทำให้เกิดข้อจำกัดเพิ่มเติม และทีมก็ต้องทบทวนวิธีสร้างการเพิ่มประสิทธิภาพที่เฉพาะเจาะจงสำหรับแอป CSR ใหม่
ปัญหาบางส่วนที่พบมีดังนี้
คำแนะนำเกี่ยวกับแหล่งข้อมูลสนับสนุน
การโหลดเนื้อหาที่สำคัญล่วงหน้าช่วยให้เบราว์เซอร์ค้นพบเนื้อหาดังกล่าวได้เร็วขึ้น อย่างไรก็ตาม การรวมคำแนะนำทรัพยากรในแอป Angular จะซับซ้อนเนื่องจากสาเหตุต่อไปนี้
การเพิ่มด้วยตนเอง: นักพัฒนาซอฟต์แวร์จะเพิ่มคำแนะนำแหล่งข้อมูล
preload
ด้วยตนเองได้ยาก Angular ใช้ไฟล์ index.html ที่ใช้ร่วมกัน 1 ไฟล์สำหรับทั้งโครงการหรือทุกเส้นทางในเว็บไซต์ ดังนั้น<head>
ของเอกสารจะเหมือนกันในทุกเส้นทาง (อย่างน้อยตามเวลาที่ให้บริการ) การเพิ่มคำแนะนำpreload
ลงใน<head>
หมายความว่าระบบจะโหลดทรัพยากรไว้ล่วงหน้าสำหรับทุกเส้นทางแม้จะไม่จำเป็นก็ตาม ดังนั้น ไม่แนะนําให้เพิ่มคําแนะนําpreload
ด้วยตนเองการเพิ่มอัตโนมัติระหว่างการแสดงภาพ: การใช้เฟรมเวิร์กเพื่อเพิ่มคำแนะนำการโหลดล่วงหน้าไว้ที่ส่วนหัวของเอกสารระหว่างการแสดงภาพในแอป CSR ไม่ช่วยอะไร เนื่องจากการแสดงผลเกิดขึ้นหลังจากดาวน์โหลดและเรียกใช้ JavaScript แล้ว
<head>
จึงจะแสดงผลช้าเกินไปที่จะไม่มีค่าใดๆสำหรับคำสั่งเวอร์ชันแรก ระบบจะใช้คำแนะนำ
preconnect
รายการร่วมกับคำแนะนำfetchpriority
รายการเพื่อจัดลำดับความสำคัญรูปภาพแทนpreload
อย่างไรก็ตาม ขณะนี้ Aurora กำลังทำงานร่วมกับทีม Angular CLI เพื่อเปิดใช้การแทรกคำแนะนำเกี่ยวกับทรัพยากรโดยอัตโนมัติในเวลาบิลด์ โปรดอดใจรอการเพิ่มประสิทธิภาพขนาดและรูปแบบรูปภาพในเซิร์ฟเวอร์
เนื่องจากแอป Angular มักแสดงผลฝั่งไคลเอ็นต์ รูปภาพในระบบไฟล์จะบีบอัดไม่ได้เมื่อขอและแสดงผลตามนั้น ด้วยเหตุนี้ เราจึงแนะนำให้ใช้ CDN รูปภาพเพื่อบีบอัดรูปภาพและแปลงเป็นรูปแบบที่ทันสมัย เช่น WebP หรือ AVIF ตามคำขอ
แม้ว่าคำสั่งจะไม่ได้บังคับให้ใช้อิมเมจ CDN ขอแนะนำเป็นอย่างยิ่งให้ใช้คำสั่งเหล่านั้นกับคำสั่งและตัวโหลดในตัวเพื่อให้แน่ใจว่าใช้ตัวเลือกการกำหนดค่าที่ถูกต้อง
มีอิทธิพล
การสาธิตต่อไปนี้แสดงให้เห็นถึงความแตกต่างที่คำสั่งของ Angular Image สามารถส่งผลต่อประสิทธิภาพของรูปภาพได้ โดยจะเปรียบเทียบ 2 เว็บไซต์ต่อไปนี้
Website 1: ใช้องค์ประกอบ <img>
แบบเนทีฟที่มีรูปภาพที่แสดงผ่าน Imgix CDN (ที่มีตัวเลือกการกำหนดค่าเริ่มต้น)
เว็บไซต์ 2: ใช้คำสั่งรูปภาพกับรูปภาพทั้งหมด นอกจากนี้ยังรวมถึงการเพิ่มประสิทธิภาพที่คำเตือนหรือข้อผิดพลาดที่คำสั่งแนะนำโดยตรง
![การเปรียบเทียบแถบฟิล์ม: เว็บไซต์ 1 ที่มีแท็กรูปภาพดั้งเดิมเทียบกับเว็บไซต์ 2 ที่มีคำสั่งรูปภาพ Angular](https://developer.chrome.com/static/blog/angular-image-directive/image/filmstrip-comparison-web-c03e5b7f51387.png?authuser=5&hl=th)
ทีมทำงานร่วมกับพาร์ทเนอร์เพื่อตรวจสอบผลกระทบด้านประสิทธิภาพของคำสั่งเกี่ยวกับรูปภาพที่มีต่อแอปพลิเคชัน 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 (เชิงมุม/สากล) ด้วย
การปรับปรุงประสบการณ์ของนักพัฒนาแอป
NgOptimizedImage
ต้องมีการระบุแอตทริบิวต์width
และheight
สำหรับแต่ละรูปภาพ อย่างไรก็ตาม การระบุชนิดเหล่านี้สำหรับแต่ละรูปภาพอาจเป็นเรื่องน่าเบื่อหน่ายสำหรับนักพัฒนาซอฟต์แวร์บางราย คุณมีโอกาสที่จะปรับปรุงประสบการณ์การใช้งานสำหรับนักพัฒนาซอฟต์แวร์ดังต่อไปนี้ในการปรับปรุงประสิทธิภาพครั้งถัดไป ดังนี้- รองรับโหมดเพิ่มเติม (คล้ายกับตัวเลือกเลย์เอาต์รูปภาพใน Next.js "
fill
") ที่ไม่ต้องกำหนดความกว้าง/ความสูงที่ชัดเจน - การใช้การผสานรวม CLI เพื่อตั้งค่าความกว้างและความสูงโดยอัตโนมัติสำหรับรูปภาพในเครื่องด้วยการกำหนดขนาดจริงของรูปภาพ
- รองรับโหมดเพิ่มเติม (คล้ายกับตัวเลือกเลย์เอาต์รูปภาพใน Next.js "
บทสรุป
คำสั่งรูปภาพ Angular จะพร้อมใช้งานสำหรับนักพัฒนาแอปเป็นขั้นตอน โดยเริ่มจากเวอร์ชันตัวอย่างของนักพัฒนาแอปใน v14.2.0 ลองใช้ NgOptimizedImage
และแสดงความคิดเห็นดูสิ
ขอขอบคุณ Katie Hempenius และ Alex Castle สำหรับการมีส่วนร่วม