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

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

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

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

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

Browser Support

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

Source

API

API ทั้งหมดที่มีคำต่อท้าย Observer ตามที่เรากล่าวถึงข้างต้นใช้การออกแบบ API ที่เรียบง่าย ResizeObserver ก็เช่นกัน คุณสร้างออบเจ็กต์ ResizeObserver และส่ง 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จะรายงานขนาดของกรอบล้อมรอบ

ตั้งแต่ Chrome 84 เป็นต้นไป ResizeObserverEntry จะมีพร็อพเพอร์ตี้ใหม่ 3 รายการเพื่อให้ข้อมูลที่ละเอียดยิ่งขึ้น พร็อพเพอร์ตี้แต่ละรายการจะแสดงออบเจ็กต์ ResizeObserverSize ที่มีพร็อพเพอร์ตี้ blockSize และพร็อพเพอร์ตี้ inlineSize ข้อมูลนี้เป็นข้อมูลเกี่ยวกับองค์ประกอบที่สังเกตได้ ณ เวลาที่เรียกใช้ Callback

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

รายการทั้งหมดนี้จะแสดงอาร์เรย์แบบอ่านอย่างเดียว เนื่องจากในอนาคตเราหวังว่า รายการเหล่านี้จะรองรับองค์ประกอบที่มีหลายส่วน ซึ่งเกิดขึ้นใน สถานการณ์แบบหลายคอลัมน์ ขณะนี้อาร์เรย์เหล่านี้จะมีองค์ประกอบเพียงรายการเดียว

แพลตฟอร์มรองรับพร็อพเพอร์ตี้เหล่านี้อย่างจำกัด แต่ Firefox รองรับ 2 รายการแรกอยู่แล้ว

มีการรายงานเมื่อใด

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

รับทราบ

คุณอาจสงสัยว่าจะเป็นอย่างไรหากเปลี่ยนขนาดขององค์ประกอบที่สังเกตการณ์ ภายในโค้ดเรียกกลับเป็น 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 ยังพิจารณาจำนวน เวลาที่ใช้ในการเกิด Next Paint ของการโต้ตอบด้วย ซึ่งเป็น ระยะเวลาที่ใช้ในการแสดงผลที่จำเป็นต่อการอัปเดตส่วนติดต่อผู้ใช้ เพื่อตอบสนองต่อการโต้ตอบให้เสร็จสมบูรณ์

ในส่วนของ ResizeObserver นั้น การดำเนินการนี้มีความสำคัญเนื่องจาก Callback ที่อินสแตนซ์ ResizerObserver เรียกใช้จะเกิดขึ้นก่อนการทำงานของการแสดงผล การทำงานนี้เป็นไปตามที่ออกแบบไว้ เนื่องจากต้องคำนึงถึงงานที่เกิดขึ้นใน Callback และผลลัพธ์ของงานนั้นมีแนวโน้มสูงที่จะต้องมีการเปลี่ยนแปลงในอินเทอร์เฟซผู้ใช้

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

  • ตรวจสอบว่าตัวเลือก CSS นั้นเรียบง่ายที่สุดเท่าที่จะเป็นไปได้เพื่อหลีกเลี่ยง การคำนวณรูปแบบใหม่มากเกินไป การคำนวณรูปแบบใหม่จะเกิดขึ้นก่อนเลย์เอาต์ และตัวเลือก CSS ที่ซับซ้อนอาจ ทำให้การดำเนินการเลย์เอาต์ล่าช้า
  • หลีกเลี่ยงการทำงานในโค้ดเรียกกลับของ ResizeObserver ที่อาจทําให้เกิดการจัดวางใหม่แบบบังคับ
  • โดยทั่วไปแล้ว เวลาที่ต้องใช้ในการอัปเดตเลย์เอาต์ของหน้าจะเพิ่มขึ้นตาม จำนวนองค์ประกอบ DOM ในหน้า แม้ว่าจะเป็นจริงไม่ว่าหน้าเว็บจะใช้ ResizeObserver หรือไม่ก็ตาม แต่การทำงานในแฮนเดิล ResizeObserver อาจมีความสำคัญมากขึ้นเมื่อความซับซ้อนเชิงโครงสร้างของหน้าเว็บเพิ่มขึ้น

บทสรุป

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