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

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

โจ เมดเลย์
โจ เมดเลย์
เจเรมี แวกเนอร์
เจเรมี แวกเนอร์

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

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

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

การสนับสนุนเบราว์เซอร์

  • 64
  • 79
  • 69
  • 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 จะรายงานขนาดของกรอบล้อมรอบ

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