ปรับขนาดObserver: เหมือนกับ document.onresize สำหรับองค์ประกอบ

ResizeObserver แจ้งให้คุณทราบเมื่อขนาดขององค์ประกอบมีการเปลี่ยนแปลง

ก่อน ResizeObserver คุณต้องแนบ Listener กับresize เหตุการณ์ของเอกสารเพื่อรับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงขนาดของวิวพอร์ต ในตัวแฮนเดิลเหตุการณ์ คุณจะต้องหาว่าองค์ประกอบใดได้รับผลกระทบจากการเปลี่ยนแปลงนั้น แล้วเรียกใช้รูทีนเฉพาะเพื่อตอบสนองอย่างเหมาะสม หากต้องการทราบขนาดใหม่ขององค์ประกอบหลังจากปรับขนาด คุณต้องเรียกใช้ getBoundingClientRect() หรือ getComputedStyle() ซึ่งอาจทําให้เลย์เอาต์ทำงานหนักหากไม่จัดกลุ่มการอ่านทั้งหมดและการเขียนทั้งหมด

ปัญหานี้ยังไม่ครอบคลุมถึงกรณีที่องค์ประกอบเปลี่ยนขนาดโดยที่หน้าต่างหลักไม่ได้ปรับขนาด ตัวอย่างเช่น การเพิ่มองค์ประกอบย่อยใหม่ การตั้งค่าสไตล์ display ขององค์ประกอบเป็น none หรือการดำเนินการที่คล้ายกันอาจเปลี่ยนขนาดขององค์ประกอบ องค์ประกอบพี่น้อง หรือองค์ประกอบหลัก

ด้วยเหตุนี้ ResizeObserver จึงเป็นแบบอย่างที่มีประโยชน์ โดยจะตอบสนองต่อการเปลี่ยนแปลงขนาดขององค์ประกอบที่สังเกตได้ โดยไม่คำนึงถึงสาเหตุของการเปลี่ยนแปลง รวมถึงให้สิทธิ์เข้าถึงขนาดใหม่ขององค์ประกอบที่สังเกตได้

การรองรับเบราว์เซอร์

  • Chrome: 64
  • Edge: 79
  • Firefox: 69
  • Safari: 13.1

แหล่งที่มา

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 กล่องเนื้อหาคือกล่องสำหรับวางเนื้อหา ซึ่งก็คือกล่องเส้นขอบลบระยะห่างจากขอบ

แผนภาพรูปแบบกล่อง CSS

โปรดทราบว่าแม้ว่า 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 ที่มีประสิทธิภาพนี้