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

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

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

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

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

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

  • Chrome: 64
  • ขอบ: 79
  • Firefox: 69
  • Safari: 13.1

แหล่งที่มา

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

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

โปรดทราบว่าแม้ 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 หรือไม่ แต่งานที่ทำใน Callback ResizeObserver อาจมีความสำคัญเนื่องจากความซับซ้อนของโครงสร้างเพิ่มขึ้น

บทสรุป

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