ความซับซ้อนของตัวเลื่อนได้ไม่รู้จบ

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

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

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

The right thing™

เราคิดว่าเหตุผลนี้เพียงพอที่จะทำให้เราคิดค้นการติดตั้งใช้งานอ้างอิง ที่แสดงวิธีจัดการปัญหาทั้งหมดนี้ในลักษณะที่นำกลับมาใช้ซ้ำได้ พร้อมทั้งรักษามาตรฐานด้านประสิทธิภาพ

เราจะใช้เทคนิค 3 อย่างเพื่อให้บรรลุเป้าหมาย ได้แก่ การรีไซเคิล DOM, Tombstone และการยึดตำแหน่งการเลื่อน

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

ภาพหน้าจอแอปแชท

การรีไซเคิล DOM

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

อุปสรรคแรกคือการเลื่อนเอง เนื่องจากเราจะมีเพียงชุดย่อยเล็กๆ ของรายการทั้งหมดที่พร้อมใช้งานใน DOM ในเวลาใดก็ตาม เราจึงต้องหาวิธีอื่น เพื่อให้แถบเลื่อนของเบราว์เซอร์แสดงปริมาณเนื้อหาที่ มีอยู่ตามทฤษฎีอย่างถูกต้อง เราจะใช้องค์ประกอบ Sentinel ขนาด 1x1 พิกเซลที่มีการเปลี่ยนรูปแบบ เพื่อบังคับให้องค์ประกอบที่มีรายการ ซึ่งก็คือรันเวย์ มีความสูงตามที่ต้องการ เราจะเลื่อนระดับองค์ประกอบทุกอย่างในรันเวย์ไปยังเลเยอร์ของตัวเองเพื่อให้ มั่นใจว่าเลเยอร์ของรันเวย์เองจะว่างเปล่าโดยสมบูรณ์ ไม่มีสีพื้นหลัง ไม่มีอะไรเลย หากเลเยอร์ของรันเวย์ไม่ว่าง เลเยอร์ดังกล่าวจะไม่มีสิทธิ์รับการเพิ่มประสิทธิภาพของเบราว์เซอร์ และเราจะต้องจัดเก็บพื้นผิวในการ์ดกราฟิกที่มี ความสูงหลายแสนพิกเซล ใช้ในอุปกรณ์เคลื่อนที่ไม่ได้แน่นอน

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

Runway Sentinel Viewport

เช่นเดียวกับการเลื่อนไปในทิศทางอื่น อย่างไรก็ตาม เราจะไม่ลดระยะเวลาในการใช้งาน ในการติดตั้งใช้งาน เพื่อให้ตำแหน่งแถบเลื่อนยังคง สอดคล้องกัน

Tombstone

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

Such
tomb. แข็งมาก ว้าว

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

การยึดตำแหน่งการเลื่อน

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

แผนภาพการยึดตำแหน่งการเลื่อน

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

เลย์เอาต์

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

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

การปรับแต่งเวอร์ชันล่าสุด

เมื่อเร็วๆ นี้ Chrome ได้เพิ่มการรองรับการจำกัดขอบเขต CSS ซึ่งเป็นฟีเจอร์ ที่ช่วยให้นักพัฒนาแอปอย่างเราบอกเบราว์เซอร์ได้ว่าองค์ประกอบหนึ่งๆ เป็นขอบเขตสำหรับ งานเลย์เอาต์และการวาด เนื่องจากเรากำลังจัดเลย์เอาต์ด้วยตนเองที่นี่ จึงเป็นแอปพลิเคชันหลักสำหรับการจำกัด เมื่อใดก็ตามที่เราเพิ่มองค์ประกอบลงในรันเวย์ เราจะทราบ ว่ารายการอื่นๆ ไม่จำเป็นต้องได้รับผลกระทบจากการจัดวางใหม่ ดังนั้นแต่ละรายการควรมีcontain: layout นอกจากนี้ เรายังไม่ต้องการให้กระทบกับส่วนอื่นๆ ของเว็บไซต์ ดังนั้น Runway เองก็ควรได้รับคำสั่งสไตล์นี้ด้วย

อีกสิ่งหนึ่งที่เราพิจารณาคือการใช้ IntersectionObservers เป็นกลไกในการตรวจหาเมื่อผู้ใช้เลื่อนไปไกลพอที่เราจะเริ่มรีไซเคิลองค์ประกอบและโหลดข้อมูลใหม่ อย่างไรก็ตาม IntersectionObserver ได้รับการระบุว่ามีความหน่วงสูง (ราวกับใช้ requestIdleCallback) ดังนั้นเราอาจรู้สึกว่าการตอบสนองของ IntersectionObserver น้อยกว่าเมื่อไม่ได้ใช้ แม้แต่การติดตั้งใช้งานปัจจุบันของเราที่ใช้เหตุการณ์ scroll ก็ยังประสบปัญหานี้ เนื่องจากเหตุการณ์การเลื่อนจะส่งตามหลักการ "พยายามอย่างเต็มที่" ในที่สุด Compositor Worklet ของ Houdini ก็จะเป็นโซลูชันที่มีความเที่ยงตรงสูงสำหรับปัญหานี้

แต่ก็ยังไม่สมบูรณ์แบบ

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

เราหวังว่าคุณจะเห็นว่าปัญหาที่ดูเหมือนจะง่ายนั้นอาจกลายเป็นเรื่องท้าทายเพียงใดเมื่อคุณต้องการ ผสานรวมประสบการณ์ของผู้ใช้ที่ยอดเยี่ยมเข้ากับมาตรฐานด้านประสิทธิภาพสูง เมื่อ Progressive Web App กลายเป็นประสบการณ์หลักบนโทรศัพท์มือถือ การเพิ่มประสิทธิภาพจะมีความสําคัญมากขึ้น และนักพัฒนาเว็บจะต้องลงทุนต่อไปในการใช้รูปแบบที่คํานึงถึงข้อจํากัดด้านประสิทธิภาพ

ดูโค้ดทั้งหมดได้ในที่เก็บข้อมูล เราพยายามอย่างเต็มที่เพื่อให้โค้ดนี้นำกลับมาใช้ใหม่ได้ แต่จะไม่เผยแพร่เป็นไลบรารีจริงใน npm หรือเป็นที่เก็บข้อมูลแยกต่างหาก การใช้งานหลักคือเพื่อการศึกษา