เจาะลึก RenderingNG: การกระจายตัวของบล็อก LayoutNG

การกระจาย Fragment ของการบล็อกใน LayoutNG เสร็จสมบูรณ์แล้ว เรียนรู้วิธีการทำงานและความสำคัญได้ในบทความนี้

Morten Stenshorne
Morten Stenshorne

ผมชื่อ Morten Stenshorne เป็นวิศวกรเลย์เอาต์ของทีมการแสดงภาพ Blink ที่ Google ฉันมีส่วนร่วมในการพัฒนาเครื่องมือของเบราว์เซอร์มาตั้งแต่ต้นปี 2000 และก็สนุกมาก เช่น ช่วยทำให้การทดสอบ acid2 ผ่านในเครื่องมือค้นหา Presto (Opera 12 และรุ่นก่อนหน้า) และการทำวิศวกรรมย้อนกลับกับเบราว์เซอร์อื่นๆ เพื่อแก้ไขเลย์เอาต์ตารางใน Presto และใช้เวลาหลายปีกว่าที่อยากจะยอมรับเกี่ยวกับการกระจายตัวบล็อก โดยเฉพาะ multicol ใน Presto, WebKit และ Blink ในช่วง 2-3 ปีที่ผ่านมานี้ Google มุ่งเน้นที่การเพิ่มการรองรับการกระจายตัวบล็อกใน LayoutNG เป็นหลัก มาร่วมเจาะลึกการใช้งานการบล็อก Fragment ในครั้งนี้กับผมหน่อย เพราะนี่อาจเป็นครั้งสุดท้ายที่ผมเริ่มใช้การกระจาย Fragment บล็อก :)

การกระจาย Fragment ของบล็อกคืออะไร

การกระจาย Fragment คือการแยกช่องระดับบล็อกของ CSS (เช่น ส่วนหรือย่อหน้า) ออกเป็นหลายๆ ส่วนย่อยเมื่อไม่พอดีกับส่วนทั้งหมดภายในคอนเทนเนอร์ Fragment เดียวที่เรียกว่าส่วนย่อย แฟรกเมนต์ไม่ใช่องค์ประกอบ แต่แสดงถึงคอลัมน์ในเลย์เอาต์หลายคอลัมน์หรือหน้าในสื่อที่มีการแบ่งหน้า หากต้องการให้มีการกระจายข้อมูล เนื้อหาจะต้องอยู่ในบริบทของการกระจายข้อมูล โดยทั่วไปแล้ว บริบทการกระจายสินค้ามักสร้างโดยคอนเทนเนอร์แบบหลายคอลัมน์ (เนื้อหาจะแบ่งออกเป็นคอลัมน์) หรือเมื่อพิมพ์ (เนื้อหาจะแบ่งออกเป็นหน้าต่างๆ) ย่อหน้ายาวๆ ที่มีหลายบรรทัดอาจต้องแยกออกเป็นส่วนย่อยๆ หลายส่วนเพื่อให้บรรทัดแรกวางอยู่ในส่วนย่อยแรก และวางบรรทัดที่เหลือไว้ในส่วนย่อยต่อๆ ไป

ย่อหน้าของข้อความที่แบ่งออกเป็น 2 คอลัมน์
ในตัวอย่างนี้ ย่อหน้าแบ่งออกเป็น 2 คอลัมน์โดยใช้เลย์เอาต์หลายคอลัมน์ แต่ละคอลัมน์คือ Fragainer ซึ่งแสดงถึง Fragment ของโฟลว์ที่แตกออกเป็นส่วนๆ

การกระจาย Fragment ของการบล็อกมีลักษณะคล้ายๆ กับการกระจาย Fragment อีกประเภทหนึ่ง นั่นคือ การแยกส่วน (หรือเรียกว่า "ตัวแบ่งบรรทัด") องค์ประกอบในบรรทัดที่ประกอบด้วยคำมากกว่า 1 คำ (โหนดข้อความใดๆ องค์ประกอบ <a> เป็นต้น) และอาจมีการแบ่งบรรทัดออกเป็นส่วนย่อยๆ หลายส่วน ส่วนย่อยแต่ละส่วนจะอยู่ในช่องบรรทัดที่ต่างกัน ช่องบรรทัดคือการกระจาย Fragment แบบในหน้า ซึ่งเทียบเท่ากับ Farainer สำหรับคอลัมน์และหน้า

การกระจายตัวบล็อก LayoutNG คืออะไร

LayoutNGBlockFragmentation คือระบบการเขียนการกระจาย Fragment สำหรับ LayoutNG ใหม่ และหลังจากทำงานมาหลายปี ชิ้นส่วนแรกก็จัดส่งใน Chrome 102 เมื่อต้นปีนี้ ปัญหานี้ได้รับการแก้ไขเป็นระยะเวลานานซึ่งแก้ไขไม่ได้ในเครื่องมือ "เดิม" ของเรา ในแง่ของโครงสร้างข้อมูล ฟีเจอร์นี้จะแทนที่โครงสร้างข้อมูลก่อน NG หลายรายการด้วยส่วนย่อย NG ที่แสดงโดยตรงในแผนผังส่วนย่อย

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

ตัวอย่างแรกแสดงส่วนหัวที่ด้านล่างของหน้า ตัวอย่างที่สองแสดงส่วนหัวที่ด้านบนของหน้าถัดไปพร้อมด้วยเนื้อหาที่เกี่ยวข้อง

Chrome 102 ยังรองรับการกระจาย Fragment เกินมา เพื่อไม่ให้เนื้อหาแบบโมโนลิธ (ไม่ควรแตก) ถูกตัดออกเป็นหลายคอลัมน์ และมีการใช้เอฟเฟกต์สี เช่น เงาและการเปลี่ยนรูปแบบอย่างถูกต้อง

การกระจาย Fragment ใน LayoutNG เสร็จสมบูรณ์แล้ว

ในขณะที่เขียนบทความนี้ เราได้รองรับการกระจาย Fragment ของบล็อกอย่างเต็มรูปแบบใน LayoutNG แล้ว การกระจาย Fragment หลัก (คอนเทนเนอร์แบบบล็อก รวมถึงเลย์เอาต์แถว การแสดงแบบลอย และการวางตำแหน่งนอกแนว) ที่จัดส่งใน Chrome 102 Flex และ Fragment แบบตารางกริดที่จัดส่งใน Chrome 103 และการแยกส่วนตารางจัดส่งใน Chrome 106 สุดท้าย การพิมพ์จะจัดส่งใน Chrome 108 การกระจาย Fragment ของการบล็อกเป็นฟีเจอร์สุดท้ายที่ต้องอาศัยเครื่องมือเดิมเพื่อทำเลย์เอาต์ ซึ่งหมายความว่าตั้งแต่ Chrome 108 เป็นต้นไป ระบบจะไม่ใช้เครื่องมือแบบเดิมเพื่อทำเลย์เอาต์อีกต่อไป

นอกจากการจัดวางเนื้อหาจริงๆ แล้ว โครงสร้างข้อมูล LayoutNG ยังรองรับการวาดภาพและการทดสอบ Hit แต่เรายังคงอาศัยโครงสร้างข้อมูลแบบเดิมบางอย่างสำหรับ JavaScript API ที่อ่านข้อมูลเลย์เอาต์ เช่น offsetLeft และ offsetTop

การวางผังทุกอย่างด้วย NG จะช่วยให้คุณใช้งานและจัดส่งฟีเจอร์ใหม่ๆ ที่มีการใช้งาน LayoutNG เท่านั้น (และไม่มีการทำงานแบบเดียวกันกับเครื่องมือค้นหาแบบเดิม) เช่น การค้นหาคอนเทนเนอร์ CSS, การกำหนดตำแหน่ง Anchor, MathML และเลย์เอาต์ที่กำหนดเอง (Houdini) สำหรับการค้นหาคอนเทนเนอร์ เราได้จัดส่งคำสั่งซื้อล่วงหน้าเล็กน้อยพร้อมเตือนนักพัฒนาซอฟต์แวร์ว่ายังไม่รองรับการพิมพ์

เราจัดส่งส่วนแรกของ LayoutNG ในปี 2019 ซึ่งประกอบด้วยเลย์เอาต์คอนเทนเนอร์แบบบล็อกปกติ เลย์เอาต์แบบอินไลน์ แบบลอยและการวางตำแหน่งนอกแนว แต่ไม่รองรับ Flex, ตารางกริด หรือตาราง รวมถึงไม่รองรับการกระจาย Fragment เลย เราจะกลับไปใช้เครื่องมือเลย์เอาต์แบบเดิมสำหรับ Flex, ตารางกริด, ตาราง รวมถึงองค์ประกอบอื่นๆ ที่เกี่ยวข้องกับการกระจาย Fragment ของบล็อก แม้จะเป็นองค์ประกอบแบบบล็อก อินไลน์ ลอย และหลุดลอยภายในเนื้อหาที่แยกส่วนก็ตาม ดังที่เห็นได้ว่าการอัปเกรดเครื่องมือเลย์เอาต์ที่ซับซ้อนเช่นนี้ในสถานที่เป็นขั้นตอนที่ทำได้ยากมาก

นอกจากนี้ เชื่อหรือไม่ว่าภายในกลางปี 2019 ฟังก์ชันการทำงานหลักส่วนใหญ่ของเลย์เอาต์การกระจายแบบบล็อก LayoutNG มีการใช้งานไปแล้ว (หลัง Flag) ทำไมการจัดส่งจึงใช้เวลานานมาก คำตอบสั้นๆ คือ การกระจาย Fragment ต้องอยู่ร่วมกันอย่างถูกต้องกับส่วนต่างๆ ของระบบเดิม ซึ่งไม่สามารถนำออกหรืออัปเกรดได้จนกว่าจะมีการอัปเกรดทรัพยากร Dependency ทั้งหมด สำหรับคำตอบแบบยาว โปรดดูรายละเอียดต่อไปนี้

การโต้ตอบกับเครื่องมือเดิม

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

การตรวจจับและการจัดการสำรองของเครื่องมือเดิม

เราต้องกลับไปใช้เครื่องมือ Layout Engine แบบเดิมเมื่อมีเนื้อหาภายในที่ยังจัดการกับการกระจายตัวบล็อกของ LayoutNG ไม่ได้ ในช่วงที่มีการแยกส่วนบล็อก LayoutNG หลัก (ฤดูใบไม้ผลิปี 2022) ซึ่งประกอบไปด้วย Flex, ตารางกริด, ตาราง และทุกอย่างที่พิมพ์ เรื่องนี้ค่อนข้างซับซ้อนเนื่องจากเราต้องตรวจหาความจำเป็นในการใช้วิดีโอสำรองแบบเดิมก่อนที่จะสร้างออบเจ็กต์ในโครงสร้างเลย์เอาต์ เช่น เราต้องตรวจสอบก่อนที่จะรู้ว่ามีคอนเทนเนอร์ระดับบนแบบหลายคอลัมน์หรือไม่ และก่อนจะรู้ว่าโหนด DOM ใดจะกลายเป็นบริบทการจัดรูปแบบหรือไม่ ปัญหานี้ก็คือปัญหาไก่กับไข่ที่ไม่มีวิธีแก้ปัญหาที่สมบูรณ์แบบ ตราบใดที่การทำงานผิดปกติเพียงอย่างเดียวก็เป็นผลบวกลวง (กลับไปใช้แบบเดิมที่ไม่มีความจำเป็นจริงๆ) ก็ไม่เป็นไร เพราะข้อบกพร่องใดๆ ในการทำงานของเลย์เอาต์นั้นเป็นข้อบกพร่องที่ Chromium มีอยู่ ไม่ใช่ของใหม่

การเดินชมต้นไม้ล่วงหน้า

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

และจะซับซ้อนยิ่งขึ้นเมื่อมีองค์ประกอบที่จัดวางตำแหน่งนอกแนวภายใน Fragment ภายใน Fragment เนื่องจากทำให้ Fragment ออกนอกกระแสจะกลายเป็นส่วนย่อยของ Farainer (ไม่ใช่ระดับย่อยของสิ่งที่ CSS คิดว่าเป็นบล็อกที่มี) ปัญหานี้เป็นปัญหาที่ต้องได้รับการแก้ไขเพื่อให้สามารถทำงานร่วมกับเครื่องมือเดิมได้โดยไม่พบปัญหาจนเกินไป ในอนาคต เราน่าจะลดความซับซ้อนของโค้ดนี้จำนวนมากได้ เนื่องจาก LayoutNG ออกแบบมาให้รองรับโหมดเลย์เอาต์สมัยใหม่ทั้งหมดได้อย่างยืดหยุ่น

ปัญหาที่เกิดกับเครื่องมือการเผยแพร่ Fragment แบบเดิม

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

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

เครื่องมือเดิมจะติดตามขอบเขตของหน้าหรือคอลัมน์สมมติในแนวเส้น วิธีนี้จะช่วยให้เกิดการกระตุ้นเนื้อหาที่ไม่พอดีผ่านขอบเขตไปยังหน้าหรือคอลัมน์ถัดไป ตัวอย่างเช่น หากเฉพาะครึ่งบนของบรรทัดจึงจะพอดีกับสิ่งที่เครื่องคิดว่าเป็นหน้าปัจจุบัน ก็จะแทรก "โครงสร้างใส่เลขหน้า" เพื่อดันลงไปตรงตำแหน่งที่เครื่องยนต์สันนิษฐานว่าด้านบนของหน้าถัดไปคือ จากนั้น การกระจาย Fragment ส่วนใหญ่ที่เกิดขึ้นจริง ("การตัดด้วยกรรไกรและการวางตำแหน่ง") จะเกิดขึ้นหลังจากการจัดวางหน้าในระหว่างการลงสีและการลงสีในส่วนสูง โดยแปลเนื้อหาบางส่วนก่อนการลงสีและระบายสี การดำเนินการดังกล่าวทำให้บางอย่างเป็นไปไม่ได้เลย เช่น การนำการเปลี่ยนรูปแบบและการวางตำแหน่งที่เกี่ยวข้องไปใช้หลังการแยกส่วน (ซึ่งเป็นสิ่งที่ข้อกำหนดจำเพาะ) นอกจากนี้ แม้จะมีการรองรับบางส่วนสําหรับการกระจายตัวของตารางในเครื่องมือเดิม แต่ก็ไม่มีการรองรับการกระจายข้อมูลแบบ Flex หรือตารางกริดเลย

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

การนำเสนอภายในเป็นคอลัมน์ 1 คอลัมน์ที่มีการแบ่งหน้าเมื่อเนื้อหาแบ่ง และการนำเสนอบนหน้าจอเป็น 3 คอลัมน์

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

ต่อไปนี้เป็นตัวอย่างง่ายๆ ที่มีข้อความเงา

เครื่องมือแบบเดิมทำงานได้ไม่ดีนัก:

เงาข้อความที่ตัดออกที่วางไว้ในคอลัมน์ที่ 2

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

ซึ่งควรมีลักษณะดังนี้ (และจะแสดงพร้อมกับ NG)

ข้อความ 2 คอลัมน์ที่แสดงเงาอย่างถูกต้อง

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

กล่องแบ่งอย่างไม่ถูกต้องใน 2 คอลัมน์

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

หากชิ้นส่วนของเนื้อหาแบบโมโนลิธสูงเกินกว่าที่จะใส่ลงในคอลัมน์ได้ เครื่องมือแบบเดิมจะตัดชิ้นส่วนชิ้นนั้นอย่างโหดเหี้ยม (นำไปสู่ลักษณะการทำงานที่ "น่าสนใจ" มากเมื่อพยายามเลื่อนคอนเทนเนอร์ที่เลื่อนได้) ดังนี้

แทนที่จะปล่อยให้ล้นคอลัมน์แรก (เหมือนกับที่มีการกระจายตัวบล็อก LayoutNG)

ALT_TEXT_HERE

เครื่องมือเดิมรองรับการบังคับช่วงพัก ตัวอย่างเช่น <div style="break-before:page;"> จะแทรกตัวแบ่งหน้าก่อน DIV อย่างไรก็ตาม มีการรองรับอย่างจำกัดสำหรับการค้นหาช่วงพักที่ไม่มีการบังคับใช้ที่เหมาะสมที่สุด โดยจะรองรับ break-inside:avoid และเด็กกำพร้าและแม่ม่าย แต่ไม่รองรับการหลีกเลี่ยงการคั่นระหว่างการบล็อก หากส่งคำขอผ่าน break-before:avoid เป็นต้น ลองดูตัวอย่างนี้

ข้อความแบ่งออกเป็น 2 คอลัมน์

ในตัวอย่างนี้ องค์ประกอบ #multicol มีที่ว่างสำหรับ 5 บรรทัดในแต่ละคอลัมน์ (เนื่องจากมีความสูง 100 พิกเซล และความสูงบรรทัดเท่ากับ 20 พิกเซล) ดังนั้น #firstchild ทั้งหมดจึงพอดีกับคอลัมน์แรก อย่างไรก็ตาม #secondchild ระดับข้างเคียงมี break-before:avoid ซึ่งหมายความว่าเนื้อหาไม่ต้องการให้มีการหยุดพักระหว่างเนื้อหา เนื่องจากค่าของ widows คือ 2 เราจึงต้องพุช #firstchild 2 บรรทัดไปยังคอลัมน์ที่ 2 เพื่อดำเนินการตามคำขอให้หลีกเลี่ยงช่วงพักทั้งหมด Chromium เป็นเครื่องมือเบราว์เซอร์ตัวแรกที่รองรับการผสมผสานฟีเจอร์ต่างๆ อย่างลงตัว

วิธีการทำงานของการกระจาย NG

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

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

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

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

ระบบจะแทรกตัวแบ่งเมื่อเรามีพื้นที่สำหรับ Fragainer เต็ม (ช่วงพักโฆษณาที่ไม่มีการบังคับ) หรือเมื่อมีการขอช่วงพักแบบบังคับ

มีกฎในข้อกำหนดสำหรับช่วงพักที่มีประสิทธิภาพสูงสุดซึ่งไม่มีการบังคับ และเพียงการแทรกช่วงพักในที่ที่ไม่มีพื้นที่เต็มก็ไม่ใช่สิ่งที่ควรทำเสมอไป ตัวอย่างเช่น พร็อพเพอร์ตี้ CSS ต่างๆ อย่างเช่น break-before มีผลต่อการเลือกตำแหน่งช่วงพักโฆษณา ดังนั้น ระหว่างเลย์เอาต์ในการใช้งานส่วนข้อกำหนดของช่วงพักโฆษณาที่ไม่มีการบังคับอย่างถูกต้อง เราจึงต้องติดตามเบรกพอยท์ที่น่าจะเป็นที่ดี ระเบียนนี้หมายความว่าเราสามารถย้อนกลับไปใช้เบรกพอยท์ที่ดีที่สุดล่าสุดที่พบได้ หากเราไม่มีพื้นที่เหลือในช่วงที่เราละเมิดข้อกำหนด (เช่น break-before:avoid หรือ orphans:7) เบรกพอยท์ที่เป็นไปได้แต่ละจุดจะได้รับคะแนน ตั้งแต่ "เลือกตัวเลือกนี้เป็นวิธีสุดท้ายเท่านั้น" ไปจนถึง "จุดพักที่เหมาะสม" โดยมีค่าบางอย่างอยู่ระหว่างกลาง หากตำแหน่งการพักคะแนน "สมบูรณ์แบบ" หมายความว่าจะไม่มีการละเมิดกฎใดๆ หากเราฝ่าฝืนจุดดังกล่าว (และหากเราได้คะแนนนี้ ณ จุดที่เราพื้นที่เต็ม ก็ไม่จำเป็นต้องย้อนกลับไปดูสิ่งที่ดีกว่า) หากคะแนนคือ "ตำแหน่งล่าสุด" แสดงว่าเบรกพอยท์จะไม่ใช่จุดที่ถูกต้อง แต่ก็อาจยังมีจุดนั้นหากไม่พบสิ่งที่ดีที่สุด เพื่อหลีกเลี่ยงการเกิดส่วนเกิน

เบรกพอยท์ที่ถูกต้องมักเกิดขึ้นระหว่างพี่น้อง (ช่องบรรทัดหรือบล็อก) เท่านั้น ไม่ใช่ระหว่างระดับบนสุดและย่อยแรก (ยกเว้นเบรกพอยท์คลาส C แต่เราไม่จำเป็นต้องพูดถึงเบรกพอยท์เหล่านี้ที่นี่) เช่น มีเบรกพอยท์ที่ถูกต้องก่อนบล็อกระดับเดียวกันที่มี break-before:avoid แต่อยู่ระหว่าง "perfect" และ "last-resort"

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

ในกรณีนี้ พื้นที่ว่างของเราจะหมดก่อนถึงเวลา #second แต่แอปมี "break-before:avoid" ซึ่งได้รับคะแนนตำแหน่งการพักเป็น "การละเมิดการหลีกเลี่ยงการพัก" ณ จุดนั้น เรามีห่วงโซ่ NGEarlyBreak เป็น "inside #outer > ภายใน #middle > ภายใน #inner > ก่อนหน้า "บรรทัดที่ 3"" โดยมีคำว่า "perfect" ดังนั้น เราจึงขอตัดตอนดังกล่าวสักครู่ เราจึงต้องกลับมาเรียกใช้เลย์เอาต์ตั้งแต่ต้นของ #outer (และตอนนี้ได้ผ่าน NGEarlyBreak ที่เราพบ) เพื่อให้เราเบรกก่อน "บรรทัดที่ 3" ใน #inline (เราจะแบ่งก่อน "บรรทัดที่ 3" เพื่อให้อีก 4 บรรทัดที่เหลือไปอยู่ใน Fragainer ถัดไป) และเพื่อเป็น widows:4)

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

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

ตรงนี้มีพื้นที่เต็มก่อนวันที่ #second แต่มี "break-before:avoid" อยู่ ซึ่งแปลเป็น "การหลีกเลี่ยงการละเมิด" เหมือนกับตัวอย่างล่าสุด นอกจากนี้เรายังมี NGEarlyBreak ที่มี "การละเมิดเด็กกำพร้าและแม่ม่าย" (ภายใน #first > ก่อน "บรรทัดที่ 2") ซึ่งยังคงไม่สมบูรณ์แบบ แต่ดีกว่า "การละเมิดการหลีกเลี่ยงการพัก" ดังนั้นเราจะหยุดก่อน "บรรทัดที่ 2" ซึ่งเป็นการละเมิดคำขอของเด็กกำพร้า / แม่ม่าย ข้อมูลจำเพาะเกี่ยวกับผลิตภัณฑ์นี้ใน 4.4 Unforced Breaks ซึ่งระบุเงื่อนไขที่ระบบจะไม่สนใจกฎการละเมิดใดก่อนหากเรามีเบรกพอยท์ไม่เพียงพอที่จะหลีกเลี่ยงการดำเนินการเกินขีดจำกัดของ Fragainer

สรุป

เป้าหมายหลักด้านฟังก์ชันการทำงานของโปรเจ็กต์การกระจาย Fragment แบบบล็อก LayoutNG คือการติดตั้งใช้งานที่รองรับสถาปัตยกรรม LayoutNG สำหรับทุกอย่างที่เครื่องมือเดิมรองรับ และทำน้อยที่สุดเท่าที่จะทำได้ นอกเหนือจากการแก้ไขข้อบกพร่อง ข้อยกเว้นหลักในที่นี้คือการรองรับการหลีกเลี่ยงช่วงพักที่ดีกว่า (เช่น break-before:avoid) เนื่องจากนี่เป็นส่วนหลักของเครื่องมือการกระจาย Fragment จึงต้องมีอยู่ในเครื่องมือดังกล่าวตั้งแต่ต้น เนื่องจากการเพิ่มในภายหลังจะหมายถึงการเขียนใหม่อีกแบบหนึ่ง

ตอนนี้เมื่อแบ่งบล็อกออกเป็นส่วนๆ ของ LayoutNG เสร็จแล้ว เราก็สามารถเริ่มเพิ่มฟังก์ชันใหม่ๆ ได้ เช่น การรองรับขนาดหน้าแบบผสมเมื่อพิมพ์, @page กล่องระยะขอบเมื่อพิมพ์, box-decoration-break:clone และอื่นๆ และเช่นเดียวกับ LayoutNG ทั่วไป เราคาดว่าอัตราข้อบกพร่องและภาระในการบำรุงรักษาของระบบใหม่จะลดลงอย่างมากเมื่อเวลาผ่านไป

ขอขอบคุณที่อ่าน

ข้อความแสดงการยอมรับ

  • Una Kravets สำหรับ "ภาพหน้าจอแฮนด์เมด" สวยๆ
  • Chris Harrelson สำหรับการพิสูจน์อักษร ความคิดเห็น และคำแนะนำ
  • Philip Jägenstedt สำหรับความคิดเห็นและคำแนะนำ
  • Rachel Andrew สำหรับการแก้ไขและตัวเลขตัวอย่างแบบหลายคอลัมน์แรก