ResizeObserver
แจ้งให้คุณทราบเมื่อขนาดขององค์ประกอบมีการเปลี่ยนแปลง
ก่อน ResizeObserver
คุณต้องแนบ Listener กับresize
เหตุการณ์ของเอกสารเพื่อรับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงขนาดของวิวพอร์ต ในตัวแฮนเดิลเหตุการณ์ คุณจะต้องหาว่าองค์ประกอบใดได้รับผลกระทบจากการเปลี่ยนแปลงนั้น แล้วเรียกใช้รูทีนเฉพาะเพื่อตอบสนองอย่างเหมาะสม หากต้องการทราบขนาดใหม่ขององค์ประกอบหลังจากปรับขนาด คุณต้องเรียกใช้ getBoundingClientRect()
หรือ getComputedStyle()
ซึ่งอาจทําให้เลย์เอาต์ทำงานหนักหากไม่จัดกลุ่มการอ่านทั้งหมดและการเขียนทั้งหมด
ปัญหานี้ยังไม่ครอบคลุมถึงกรณีที่องค์ประกอบเปลี่ยนขนาดโดยที่หน้าต่างหลักไม่ได้ปรับขนาด ตัวอย่างเช่น การเพิ่มองค์ประกอบย่อยใหม่ การตั้งค่าสไตล์ display
ขององค์ประกอบเป็น none
หรือการดำเนินการที่คล้ายกันอาจเปลี่ยนขนาดขององค์ประกอบ องค์ประกอบพี่น้อง หรือองค์ประกอบหลัก
ด้วยเหตุนี้ ResizeObserver
จึงเป็นแบบอย่างที่มีประโยชน์ โดยจะตอบสนองต่อการเปลี่ยนแปลงขนาดขององค์ประกอบที่สังเกตได้ โดยไม่คำนึงถึงสาเหตุของการเปลี่ยนแปลง
รวมถึงให้สิทธิ์เข้าถึงขนาดใหม่ขององค์ประกอบที่สังเกตได้
API
API ทั้งหมดที่มีส่วนต่อท้าย Observer
ที่เรากล่าวถึงข้างต้นใช้การออกแบบ API แบบง่าย ResizeObserver
ก็ไม่มีข้อยกเว้น คุณสร้างออบเจ็กต์ ResizeObserver
และส่งการเรียกกลับไปยังคอนสตรัคเตอร์ จะมีการส่งอาร์เรย์ของออบเจ็กต์ ResizeObserverEntry
ไปยังการเรียกกลับ โดยแต่ละรายการจะสอดคล้องกับองค์ประกอบที่สังเกตได้ 1 รายการ ซึ่งจะมีมิติข้อมูลใหม่ขององค์ประกอบ
var ro = new ResizeObserver(entries => {
for (let entry of entries) {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
}
});
// Observe one or multiple elements
ro.observe(someElement);
รายละเอียดบางส่วน
มีการรายงานอะไร
โดยทั่วไปแล้ว ResizeObserverEntry
จะรายงานกล่องเนื้อหาขององค์ประกอบผ่านพร็อพเพอร์ตี้ที่ชื่อ contentRect
ซึ่งจะแสดงผลออบเจ็กต์ DOMRectReadOnly
กล่องเนื้อหาคือกล่องสำหรับวางเนื้อหา ซึ่งก็คือกล่องเส้นขอบลบระยะห่างจากขอบ
โปรดทราบว่าแม้ว่า ResizeObserver
จะรายงานทั้งมิติข้อมูลของ contentRect
และระยะห่างจากขอบ แต่ contentRect
จะเป็นรายการที่ตรวจสอบเท่านั้น
อย่าสับสนระหว่าง contentRect
กับกล่องขอบขององค์ประกอบ กล่องขอบเขตตามที่ getBoundingClientRect()
รายงานคือกล่องที่มีองค์ประกอบทั้งหมดและองค์ประกอบที่สืบทอด SVG เป็นข้อยกเว้นของกฎนี้ โดย ResizeObserver
จะรายงานขนาดของกล่องขอบเขต
ตั้งแต่ Chrome 84 ResizeObserverEntry
มีพร็อพเพอร์ตี้ใหม่ 3 รายการเพื่อให้ข้อมูลโดยละเอียดมากขึ้น พร็อพเพอร์ตี้เหล่านี้แต่ละรายการจะแสดงผลออบเจ็กต์ ResizeObserverSize
ที่มีพร็อพเพอร์ตี้ blockSize
และพร็อพเพอร์ตี้ inlineSize
ข้อมูลนี้เกี่ยวกับองค์ประกอบที่สังเกตได้ ณ เวลาที่เรียกใช้การเรียกกลับ
borderBoxSize
contentBoxSize
devicePixelContentBoxSize
รายการทั้งหมดเหล่านี้จะแสดงผลอาร์เรย์แบบอ่านอย่างเดียว เนื่องจากเราหวังว่าในอนาคตรายการเหล่านี้จะรองรับองค์ประกอบที่มีหลายส่วน ซึ่งเกิดขึ้นในสถานการณ์หลายคอลัมน์ ขณะนี้อาร์เรย์เหล่านี้จะมีองค์ประกอบเพียงรายการเดียว
แพลตฟอร์มรองรับพร็อพเพอร์ตี้เหล่านี้อย่างจำกัด แต่Firefox รองรับพร็อพเพอร์ตี้ 2 รายการแรกอยู่แล้ว
มีการรายงานเมื่อใด
ข้อกำหนดระบุว่า ResizeObserver
ควรประมวลผลเหตุการณ์การปรับขนาดทั้งหมดก่อนการวาดภาพและหลังการจัดวาง ซึ่งทำให้การเรียกกลับของ ResizeObserver
เป็นตําแหน่งที่เหมาะสมในการทําการเปลี่ยนแปลงเลย์เอาต์ของหน้า เนื่องจากResizeObserver
การประมวลผลเกิดขึ้นระหว่างเลย์เอาต์กับการแสดงผล การดําเนินการดังกล่าวจะทำให้เลย์เอาต์ไม่ถูกต้องเท่านั้น ไม่ใช่การแสดงผล
เข้าใจแล้ว
คุณอาจสงสัยว่าจะเกิดอะไรขึ้นหากฉันเปลี่ยนขนาดขององค์ประกอบที่สังเกตภายในการเรียกกลับเป็น ResizeObserver
คำตอบคือ คุณจะทำให้มีการเรียกใช้การโทรกลับอีกสายทันที แต่โชคดีที่ ResizeObserver
มีกลไกในการหลีกเลี่ยงลูปการเรียกกลับแบบไม่มีที่สิ้นสุดและการขึ้นต่อกันแบบวนซ้ำ ระบบจะประมวลผลการเปลี่ยนแปลงในเฟรมเดียวกันก็ต่อเมื่อองค์ประกอบที่มีการปรับขนาดอยู่ลึกใน DOM มากกว่าองค์ประกอบที่ตื้นที่สุดซึ่งประมวลผลในการเรียกกลับก่อนหน้า
มิเช่นนั้น ระบบจะเลื่อนการดำเนินการไปยังเฟรมถัดไป
แอปพลิเคชัน
ResizeObserver
ช่วยให้คุณสามารถใช้การค้นหาสื่อต่อองค์ประกอบ การสังเกตองค์ประกอบช่วยให้คุณกำหนดจุดพักของการออกแบบและเปลี่ยนรูปแบบขององค์ประกอบได้ ในตัวอย่างต่อไปนี้ กล่องที่ 2 จะเปลี่ยนรัศมีของเส้นขอบตามความกว้าง
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
entry.target.style.borderRadius =
Math.max(0, 250 - entry.contentRect.width) + 'px';
}
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));
อีกตัวอย่างที่น่าสนใจคือหน้าต่างแชท ปัญหาที่เกิดขึ้นในเลย์เอาต์การสนทนาแบบบนลงล่างทั่วไปคือการจัดตำแหน่งการเลื่อน หน้าต่างควรติดอยู่ที่ด้านล่างของการสนทนาเพื่อให้ผู้ใช้ไม่สับสน เนื่องจากข้อความใหม่จะปรากฏที่ด้านล่าง นอกจากนี้ การเปลี่ยนแปลงเลย์เอาต์ทุกประเภท (เช่น โทรศัพท์เปลี่ยนจากแนวนอนเป็นแนวตั้งหรือในทางกลับกัน) ควรได้ผลลัพธ์เดียวกัน
ResizeObserver
ช่วยให้คุณเขียนโค้ดรายการเดียวที่จัดการกับทั้ง 2 สถานการณ์ได้ การปรับขนาดหน้าต่างเป็นเหตุการณ์ที่ ResizeObserver
สามารถบันทึกได้ตามคำจำกัดความ แต่การเรียกใช้ appendChild()
จะปรับขนาดองค์ประกอบนั้นด้วย (เว้นแต่จะมีการตั้งค่า overflow: hidden
) เนื่องจากต้องจัดสรรพื้นที่สำหรับองค์ประกอบใหม่ เมื่อพิจารณาจากข้อเท็จจริงนี้ คุณจึงใช้บรรทัดโค้ดเพียงไม่กี่บรรทัดเพื่อให้ได้ผลลัพธ์ที่ต้องการได้
const ro = new ResizeObserver(entries => {
document.scrollingElement.scrollTop =
document.scrollingElement.scrollHeight;
});
// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);
// Observe the timeline to process new messages
ro.observe(timeline);
เจ๋งไปเลยใช่ไหม
จากตรงนี้ เราสามารถเพิ่มโค้ดเพิ่มเติมเพื่อจัดการกรณีที่ผู้ใช้เลื่อนขึ้นด้วยตนเองและต้องการให้การเลื่อนอยู่ตรงข้อความนั้นเมื่อข้อความใหม่เข้ามา
กรณีการใช้งานอีกอย่างหนึ่งคือสำหรับองค์ประกอบที่กำหนดเองทุกประเภทที่ใช้เลย์เอาต์ของตัวเอง
ก่อนหน้านี้ ResizeObserver
ไม่มีวิธีที่น่าเชื่อถือในการรับการแจ้งเตือนเมื่อขนาดขององค์ประกอบมีการเปลี่ยนแปลงเพื่อให้จัดวางองค์ประกอบย่อยอีกครั้งได้
ผลต่อ Interaction to Next Paint (INP)
Interaction to Next Paint (INP) คือเมตริกที่วัดการตอบสนองโดยรวมของหน้าเว็บต่อการโต้ตอบของผู้ใช้ หาก INP ของหน้าเว็บอยู่ในเกณฑ์ "ดี" ซึ่งก็คือ 200 มิลลิวินาทีหรือน้อยกว่า แสดงว่าหน้าเว็บตอบสนองต่อการโต้ตอบของผู้ใช้ได้อย่างน่าเชื่อถือ
แม้ว่าระยะเวลาที่ใช้ในการเรียกเหตุการณ์ให้แสดงผลเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้จะส่งผลอย่างมากต่อเวลาในการตอบสนองโดยรวมของการโต้ตอบ แต่นั่นไม่ใช่แง่มุมเดียวของ INP ที่ต้องพิจารณา นอกจากนี้ INP ยังพิจารณาระยะเวลาที่ใช้ในการแสดงผลภาพถัดไปของการโต้ตอบด้วย ระยะเวลาที่ใช้ในการแสดงผลที่จำเป็นในการอัปเดตอินเทอร์เฟซผู้ใช้เพื่อตอบสนองต่อการโต้ตอบให้เสร็จสมบูรณ์
ในส่วนที่เกี่ยวข้องกับ ResizeObserver
ข้อมูลนี้สำคัญเนื่องจากคอลแบ็กที่เรียกใช้อินสแตนซ์ ResizerObserver
จะทำงานก่อนการเรนเดอร์ การดำเนินการนี้เป็นไปตามการออกแบบ เนื่องจากต้องพิจารณางานที่เกิดขึ้นใน Callback เนื่องจากผลลัพธ์ของงานนั้นอาจจำเป็นต้องมีการเปลี่ยนแปลงอินเทอร์เฟซผู้ใช้
โปรดใช้การเรนเดอร์ให้น้อยที่สุดเท่าที่จำเป็นใน ResizeObserver
callbacks เนื่องจากการใช้การเรนเดอร์มากเกินไปอาจทําให้เบราว์เซอร์ทำงานที่สำคัญล่าช้า เช่น หากการโต้ตอบใดๆ มีการเรียกกลับที่ทำให้การเรียกกลับ ResizeObserver
ทำงาน ให้ตรวจสอบว่าคุณทําสิ่งต่อไปนี้เพื่อให้ได้รับประสบการณ์การใช้งานที่ราบรื่นที่สุด
- ตรวจสอบว่าตัวเลือก CSS ของคุณเรียบง่ายที่สุดเท่าที่จะเป็นไปได้เพื่อหลีกเลี่ยงการคำนวณสไตล์ใหม่มากเกินไป การคํานวณสไตล์ใหม่จะเกิดขึ้นก่อนการจัดวาง และตัวเลือก CSS ที่ซับซ้อนอาจทําให้การดำเนินการจัดวางล่าช้า
- หลีกเลี่ยงการทํางานใดๆ ใน
ResizeObserver
callback ที่อาจทริกเกอร์การจัดเรียงใหม่แบบบังคับ - โดยทั่วไปแล้ว เวลาที่ใช้อัปเดตเลย์เอาต์ของหน้าเว็บจะเพิ่มขึ้นตามจำนวนองค์ประกอบ DOM ในหน้า แม้ว่าจะเป็นเช่นนั้นไม่ว่าหน้าเว็บจะใช้
ResizeObserver
หรือไม่ แต่งานที่ทําในResizeObserver
callback อาจกลายเป็นเรื่องสําคัญเมื่อโครงสร้างของหน้าเว็บมีความซับซ้อนมากขึ้น
บทสรุป
ResizeObserver
พร้อมใช้งานในเบราว์เซอร์หลักๆ ทั้งหมด และเป็นวิธีที่มีประสิทธิภาพในการตรวจสอบการปรับขนาดองค์ประกอบที่ระดับองค์ประกอบ แต่โปรดระมัดระวังอย่าให้การแสดงผลล่าช้ามากเกินไปด้วย API ที่มีประสิทธิภาพนี้