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
จะรายงานขนาดของกรอบล้อมรอบ
ResizeObserverEntry
มีพร็อพเพอร์ตี้ใหม่ 3 รายการสำหรับ Chrome 84 เพื่อให้ข้อมูลโดยละเอียดเพิ่มเติม แต่ละพร็อพเพอร์ตี้จะส่งกลับออบเจ็กต์ 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
ยังไม่มีวิธีที่เชื่อถือได้ในการได้รับการแจ้งเตือนเมื่อขนาดมีการเปลี่ยนแปลงเพื่อให้จัดองค์ประกอบของบุตรหลานได้อีกครั้ง
ผลกระทบต่อการโต้ตอบกับ Next Paint (INP)
การโต้ตอบกับ Next Paint (INP) เป็นเมตริกที่วัดการตอบสนองโดยรวมของหน้าเว็บต่อการโต้ตอบของผู้ใช้ หาก INP ของหน้าเว็บอยู่ในเกณฑ์ "ดี" นั่นคือไม่เกิน 200 มิลลิวินาที อาจกล่าวได้ว่าหน้าเว็บตอบสนองต่อการโต้ตอบของผู้ใช้อย่างถูกต้อง
แม้ว่าระยะเวลาที่การเรียกกลับของเหตุการณ์ใช้เพื่อตอบสนองต่อการโต้ตอบของผู้ใช้อาจส่งผลต่อเวลาในการตอบสนองรวมของการโต้ตอบได้อย่างมาก แต่นั่นไม่ใช่แง่มุมเดียวที่ INP ควรพิจารณา INP ยังพิจารณาระยะเวลาที่ใช้ในการทำให้ Paint ถัดไป ของการโต้ตอบเกิดขึ้น ซึ่งก็คือระยะเวลาที่ต้องใช้ในการแสดงผลที่ต้องใช้ในการอัปเดตอินเทอร์เฟซผู้ใช้เพื่อตอบสนองต่อการโต้ตอบที่เสร็จสมบูรณ์
ในกรณีที่เป็นกังวล ResizeObserver
กรณีนี้มีความสำคัญเนื่องจากการเรียกกลับที่อินสแตนซ์ ResizerObserver
เรียกใช้จะเกิดขึ้นก่อนการแสดงผล เราออกแบบให้เป็นเช่นนี้เพราะงานที่เกิดขึ้นในการเรียกกลับจะต้องนำมาพิจารณาด้วย การดำเนินการนั้นจึงน่าจะต้องมีการเปลี่ยนแปลงอินเทอร์เฟซผู้ใช้ด้วย
โปรดระมัดระวังในการแสดงผลให้น้อยที่สุดเท่าที่จำเป็นในการเรียกกลับ ResizeObserver
เนื่องจากการแสดงผลมากเกินไปอาจทำให้เบราว์เซอร์ทำงานสำคัญล่าช้าได้ ตัวอย่างเช่น หากการโต้ตอบใดๆ มีการติดต่อกลับที่ทำให้การเรียกกลับ ResizeObserver
ทำงาน ให้ตรวจสอบว่าคุณได้ทำสิ่งต่อไปนี้อยู่เพื่อให้ประสบการณ์การใช้งานเป็นไปอย่างราบรื่นที่สุด
- ตรวจสอบว่าตัวเลือก CSS เรียบง่ายที่สุดเพื่อหลีกเลี่ยงการคำนวณรูปแบบที่มากเกินไป การคำนวณรูปแบบใหม่จะเกิดขึ้นก่อนเลย์เอาต์ และตัวเลือก CSS ที่ซับซ้อนอาจทำให้การดำเนินการเลย์เอาต์ล่าช้าได้
- หลีกเลี่ยงการดําเนินการใดๆ ในโค้ดเรียกกลับ
ResizeObserver
ที่อาจทําให้เกิดการบังคับให้จัดเรียงใหม่ - โดยทั่วไปแล้ว เวลาที่ใช้ในการอัปเดตเลย์เอาต์ของหน้าเว็บจะเพิ่มขึ้นตามจำนวนองค์ประกอบ DOM ในหน้าเว็บ แม้ว่าหน้าเว็บจะใช้
ResizeObserver
หรือไม่ก็ตาม แต่งานที่ทำในโค้ดเรียกกลับResizeObserver
อาจมีนัยสำคัญเนื่องจากความซับซ้อนเชิงโครงสร้างของหน้าเว็บเพิ่มขึ้น
บทสรุป
ResizeObserver
พร้อมใช้งานในเบราว์เซอร์หลักทั้งหมด และเป็นวิธีที่มีประสิทธิภาพในการตรวจสอบการปรับขนาดองค์ประกอบที่ระดับองค์ประกอบ โปรดระมัดระวังไม่ให้แสดงผลมากเกินไปด้วย API ที่มีประสิทธิภาพนี้