ResizeObserver
แจ้งให้คุณทราบเมื่อขนาดขององค์ประกอบมีการเปลี่ยนแปลง
ก่อน ResizeObserver
คุณต้องแนบ Listener กับresize
เหตุการณ์ของเอกสารเพื่อรับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงขนาดของวิวพอร์ต ในตัวแฮนเดิลเหตุการณ์ คุณจะต้องหาว่าองค์ประกอบใดได้รับผลกระทบจากการเปลี่ยนแปลงนั้น แล้วเรียกใช้รูทีนเฉพาะเพื่อตอบสนองอย่างเหมาะสม หากต้องการใช้ขนาดใหม่ขององค์ประกอบหลังจากปรับขนาด คุณต้องเรียกใช้ getBoundingClientRect()
หรือ getComputedStyle()
ซึ่งอาจทำให้เกิดการขว้างเลย์เอาต์หากคุณไม่สนใจการจัดกลุ่มการอ่านและการเขียนทั้งหมดทั้งหมด
ซึ่งไม่ครอบคลุมกรณีที่องค์ประกอบเปลี่ยนขนาดโดยไม่มีการปรับขนาดหน้าต่างหลักด้วย ตัวอย่างเช่น การเพิ่มองค์ประกอบย่อยใหม่ การตั้งค่าสไตล์ display
ขององค์ประกอบเป็น none
หรือการดำเนินการที่คล้ายกันอาจเปลี่ยนขนาดขององค์ประกอบ องค์ประกอบพี่น้อง หรือองค์ประกอบหลัก
ด้วยเหตุนี้ ResizeObserver
จึงเป็นแบบอย่างที่มีประโยชน์ โดยจะตอบสนองต่อการเปลี่ยนแปลงขนาดขององค์ประกอบที่สังเกตได้ โดยไม่ขึ้นอยู่กับสิ่งที่ทำให้เกิดการเปลี่ยนแปลง
รวมถึงให้สิทธิ์เข้าถึงขนาดใหม่ขององค์ประกอบที่สังเกตได้
API
API ทั้งหมดที่มีส่วนต่อท้าย Observer
ที่เรากล่าวถึงข้างต้นใช้การออกแบบ API แบบง่าย ResizeObserver
ก็ไม่มีข้อยกเว้น คุณสร้างออบเจ็กต์ ResizeObserver
และส่ง Callback ไปยังตัวสร้าง Callback จะส่งผ่านอาร์เรย์ของออบเจ็กต์ 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
จะรายงานขนาดของกล่องขอบเขต
ResizeObserverEntry
มีพร็อพเพอร์ตี้ใหม่ 3 รายการตั้งแต่ Chrome 84 เป็นต้นไปเพื่อแสดงข้อมูลที่ละเอียดยิ่งขึ้น พร็อพเพอร์ตี้เหล่านี้แต่ละรายการจะแสดงผลออบเจ็กต์ ResizeObserverSize
ที่มีพร็อพเพอร์ตี้ blockSize
และพร็อพเพอร์ตี้ inlineSize
ข้อมูลนี้เกี่ยวกับองค์ประกอบที่สังเกตได้ ณ เวลาที่เรียกใช้การเรียกกลับ
borderBoxSize
contentBoxSize
devicePixelContentBoxSize
รายการทั้งหมดเหล่านี้จะแสดงผลอาร์เรย์แบบอ่านอย่างเดียว เนื่องจากเราหวังว่าในอนาคตรายการเหล่านี้จะรองรับองค์ประกอบที่มีหลายส่วน ซึ่งเกิดขึ้นในสถานการณ์หลายคอลัมน์ ขณะนี้อาร์เรย์เหล่านี้จะมีองค์ประกอบเพียงรายการเดียว
แพลตฟอร์มรองรับพร็อพเพอร์ตี้เหล่านี้อย่างจำกัด แต่Firefox รองรับพร็อพเพอร์ตี้ 2 รายการแรกอยู่แล้ว
มีการรายงานเมื่อใด
ข้อกำหนดระบุว่า ResizeObserver
ควรประมวลผลเหตุการณ์การปรับขนาดทั้งหมดก่อนลงสีและหลังเลย์เอาต์ ซึ่งทำให้การเรียกกลับของ ResizeObserver
เป็นตําแหน่งที่เหมาะสมในการทําการเปลี่ยนแปลงเลย์เอาต์ของหน้า เนื่องจากResizeObserver
การประมวลผลเกิดขึ้นระหว่างเลย์เอาต์กับการแสดงผล การดําเนินการดังกล่าวจะทำให้เลย์เอาต์ไม่ถูกต้องเท่านั้น แต่จะไม่ได้ทำให้การแสดงผลไม่ถูกต้อง
เข้าใจแล้ว
คุณอาจถามตัวเองว่าจะเกิดอะไรขึ้นหากฉันเปลี่ยนขนาดขององค์ประกอบที่พบภายใน Callback เป็น ResizeObserver
คำตอบคือ คุณเรียกใช้การโทรกลับอีกสายทันที แต่โชคดีที่ ResizeObserver
มีกลไกในการหลีกเลี่ยงลูปการเรียกกลับแบบไม่มีที่สิ้นสุดและการขึ้นต่อกันแบบวนซ้ำ ระบบจะประมวลผลการเปลี่ยนแปลงในเฟรมเดียวกันเฉพาะเมื่อองค์ประกอบที่ปรับขนาดอยู่ในแผนผัง DOM ซึ่งอยู่ลึกกว่าองค์ประกอบที่ตื้นที่สุดที่ประมวลผลใน Callback ก่อนหน้านี้
มิเช่นนั้น ระบบจะเลื่อนการดำเนินการไปยังเฟรมถัดไป
แอปพลิเคชัน
สิ่งหนึ่งที่ 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
เนื่องจากการแสดงผลมากเกินไปอาจทำให้เบราว์เซอร์ทำงานสำคัญล่าช้า เช่น หากการโต้ตอบใดๆ มีการเรียกกลับที่ทำให้การเรียกกลับ ResizeObserver
ทำงาน ให้ตรวจสอบว่าคุณทําสิ่งต่อไปนี้เพื่อให้ได้รับประสบการณ์การใช้งานที่ราบรื่นที่สุด
- ตรวจสอบว่าตัวเลือก CSS ของคุณเรียบง่ายที่สุดเท่าที่จะเป็นไปได้เพื่อหลีกเลี่ยงการคำนวณสไตล์ใหม่มากเกินไป การคํานวณสไตล์ใหม่จะเกิดขึ้นก่อนการจัดวาง และตัวเลือก CSS ที่ซับซ้อนอาจทําให้การดำเนินการจัดวางล่าช้า
- หลีกเลี่ยงการทํางานใดๆ ใน
ResizeObserver
callback ที่อาจทริกเกอร์การจัดเรียงใหม่แบบบังคับ - โดยทั่วไปแล้ว เวลาที่ใช้ในการอัปเดตการออกแบบหน้าเว็บจะเพิ่มขึ้นตามจำนวนองค์ประกอบ DOM ในหน้าเว็บ แม้จะเป็นความจริงไม่ว่าหน้าเว็บจะใช้
ResizeObserver
หรือไม่ แต่งานที่ทำใน CallbackResizeObserver
อาจมีความสำคัญเนื่องจากความซับซ้อนของโครงสร้างเพิ่มขึ้น
บทสรุป
ResizeObserver
พร้อมใช้งานในเบราว์เซอร์หลักทั้งหมดและเป็นวิธีที่มีประสิทธิภาพในการตรวจสอบการปรับขนาดองค์ประกอบในระดับองค์ประกอบ แต่โปรดระมัดระวังอย่าให้การแสดงผลล่าช้ามากเกินไปด้วย API ที่มีประสิทธิภาพนี้