פיצול בלוקים הוא פיצול של תיבה ברמת הבלוק ב-CSS (כמו קטע או פסקה) למספר קטעים, אם היא לא נכנסת בשלמותה לתוך מאגר קטעים אחד שנקרא fragmentainer. רכיב חלוקת פריטים הוא לא אלמנט, אלא מייצג עמודה בפריסה של כמה עמודות, או דף במדיה שמחולקת לדפים.
כדי שהתוכן יתפרק, הוא צריך להיות בהקשר של פיצול. הקשר של פרגמנטציה מוגדר בדרך כלל על ידי מאגר מרובה-עמודות (התוכן מחולק לעמודות) או בזמן ההדפסה (התוכן מפוצל לדפים). יכול להיות שיהיה צורך לפצל פסקה ארוכה עם הרבה שורות לכמה קטעים, כך שהשורות הראשונות יופיעו בחלק הראשון, והשורות הנותרות יופיעו בחלקים הבאים.
פיצול בלוקים דומה לסוג אחר של פיצול ידוע: פיצול שורות, שנקרא גם 'הפסקת שורה'. כל רכיב בשורה אחת שמכיל יותר ממילה אחת (כל צומת טקסט, כל רכיב <a>
וכו') ומאפשר הפסקות שורה, יכול להיות מפוצל למספר קטעים. כל קטע ממוקם בתיבת שורה אחרת. תיבת שורה היא הפיצול המוטבע שוות-ערך לפרגמנטציה של עמודות ודפים.
פרגמנטציה של בלוקים ב-LayoutNG
LayoutNGBlockFragmentation הוא לשכתוב של מנוע המקטעים עבור LayoutNG, שנשלח בשלב הראשון ב-Chrome 102. מבחינת מבני נתונים, המערכת החליפה כמה מבני נתונים מדור קודם ב-NG בקטעי NG שמיוצגים ישירות בעץ הקטעים.
לדוגמה, עכשיו אנחנו תומכים בערך 'avoid' למאפייני ה-CSS 'break-before' ו-'break-after', שמאפשרים לכותבים להימנע מקצוות אחרי כותרת. לרוב, המראה של דף שבו הדבר האחרון הוא כותרת, בעוד שתוכן הקטע מתחיל בדף הבא, נראה לא נוח. עדיף לעשות הפסקות לפני הכותרת.
ב-Chrome יש גם תמיכה בחריגה ממלאי המידע, כך שתוכן מונוליתי (שנועד שלא ניתן לשבירה) לא ייחתך לכמה עמודות, ואפקטים של צביעה כמו צללים וטרנספורמציות יוחלו בצורה נכונה.
השלמת הפיצול של בלוקים ב-LayoutNG
פיצול הליבה (קונטיינרים של בלוקים, כולל פריסה של שורות, פריטים צפים ומיקום מחוץ לזרימה) נשלחו ב-Chrome 102. מקטעים של רשת ו-Flex נשלחו ב-Chrome 103, והמקטעים בטבלה נשלחו בגרסה 106 של Chrome. לבסוף, הדפסה נוספה לגרסה 108 של Chrome. פירוקי בלוקים הייתה התכונה האחרונה שהסתמכה על המנוע הקודם לביצוע הפריסה.
החל מגרסה 108 של Chrome, המנוע הקודם כבר לא משמש לביצוע פריסה.
בנוסף, מבני הנתונים של LayoutNG תומכים בציור ובבדיקת היטים, אבל אנחנו מסתמכים על כמה מבני נתונים מדור קודם לממשקי API של JavaScript שקוראים פרטי פריסה, כמו offsetLeft
ו-offsetTop
.
פריסה של כל המידע ב-NG תאפשר להטמיע ולשלוח תכונות חדשות שיש להן רק הטמעות LayoutNG (ולא מקבילה של מנוע חיפוש), כמו שאילתות קונטיינר של CSS, מיקום עוגן, MathML ופריסה מותאמת אישית (Houdini). לשאילתות בקונטיינרים, השקנו את התכונה קצת מראש, עם אזהרה למפתחים שהדפסה עדיין לא נתמכת.
השקנו את החלק הראשון של LayoutNG ב-2019, שכלל פריסה רגילה של מאגר בלוקים, פריסה בתוך שורה, פריסה צפה ומיקום מחוץ לזרימה, אבל ללא תמיכה ב-flex, ב-grid או בטבלאות, וללא תמיכה בכלל בפיצול בלוקים. נחזור להשתמש במנוע הפריסה הישן ליצירת לוחות גמישים, רשת, טבלאות וכל מה שקשור לפיצול בלוקים. זה היה נכון גם לגבי רכיבים מסוג בלוק, בתוך שורה, צפים ורכיבים מחוץ לזרימה בתוכן מקוטע – כפי שאתם רואים, שדרוג של מנוע פריסה מורכב כל כך במקום הוא תהליך עדין מאוד.
כמו כן, עד אמצע 2019, רוב הפונקציונליות העיקרית של פריסת המקטעים של LayoutNG כבר הוטמעה (מאחורי דגל). למה זה לקח כל כך הרבה זמן לשלוח את ההזמנה? התשובה הקצרה היא: הפיצול צריך להתקיים בצורה תקינה לצד חלקים שונים מדור קודם של המערכת, שאי אפשר להסיר או לשדרג אותם עד שכל יחסי התלות משודרגים.
אינטראקציה עם מנוע מדור קודם
מבני הנתונים הקודמים עדיין אחראים לממשקי ה-API של JavaScript שקוראים את פרטי הפריסה, לכן אנחנו צריכים לכתוב חזרה נתונים למנוע הקודם באופן שהוא מבין. זה כולל עדכון תקין של מבני הנתונים הקודמים עם כמה עמודות, כמו LayoutMultiColumnFlowThread.
זיהוי וטיפול בחלופות מדור קודם
נאלצנו לחזור למנוע הפריסה הקודם כשהיה בתוכן תוכן שעדיין לא ניתן היה לטפל בו באמצעות פיצול בלוקים של LayoutNG. בזמן השליחה, חלוקת הבלוק של הליבה של LayoutNG כללה גמישות, רשת, טבלאות וכל מה שמודפס. זה היה מאתגר במיוחד כי היינו צריכים לזהות את הצורך באפשרות חלופית מדור קודם לפני יצירת אובייקטים בעץ הפריסה. לדוגמה, היינו צריכים לזהות לפני שידענו אם יש ישות אב של קונטיינר מרובה-עמודות, ולפני שידענו אילו צומתי DOM יהפכו להקשר עיצוב או לא. זוהי בעיה מסוג 'התרנגולת והביצה' שאין לה פתרון מושלם, אבל כל עוד ההתנהגות השגויה היחידה היא תוצאות חיוביות שגויות (חזרה לגרסה הקודמת כשאין בכלל צורך), זה בסדר, כי כל הבאגים בהתנהגות של הפריסה הזו הם באגים שכבר קיימים ב-Chromium, ולא באגים חדשים.
הליכה בין העצים לפני הצביעה
אנחנו מבצעים את ההכנה לקראת הצביעה אחרי הפריסה, אבל לפני הצביעה. האתגר העיקרי הוא שאנחנו עדיין צריכים לעבור על עץ אובייקטי הפריסה, אבל עכשיו יש לנו קטעי NG – אז איך אנחנו מתמודדים עם זה? אנחנו הולכים גם את אובייקט הפריסה וגם את עצי המקטע NG בו-זמנית! זהו תהליך מורכב למדי, כי המיפוי בין שני העצים הוא לא טריוויאלי.
מבנה עץ אובייקטי הפריסה דומה מאוד למבנה של עץ ה-DOM, אבל עץ הפסקולים הוא פלט של הפריסה, ולא קלט שלה. בנוסף לשיקוף בפועל של ההשפעה של כל פיצול, כולל פיצול בתוך שורה (קטעי שורה) ופיצול בתוך בלוק (קטעי עמודה או דף), לעץ הקטעים יש גם קשר ישיר של הורה-צאצא בין בלוק מכיל לבין הצאצאים של DOM שהקטע הזה הוא הבלוק המכיל שלהם. לדוגמה, בעץ הקטעים, קטע שנוצר על ידי רכיב שממוקם באופן מוחלט הוא צאצא ישיר של קטע הבלוק שמכיל אותו, גם אם יש צמתים אחרים בשרשרת האב בין הצאצא שממוקם מחוץ לזרימה לבין הבלוק שמכיל אותו.
המצב יכול להיות מסובך עוד יותר כשיש רכיב שממוקם מחוץ לזרימה בתוך הפיצול, כי אז החלקים מחוץ לזרימה הופכים לצאצאים ישירים של ה-fragmentainer (ולא לצאצא של מה ש-CSS חושב שהוא הבלוק המכיל). זו הייתה בעיה שצריך היה לפתור כדי לאפשר קיום משותף עם המנוע הקודם. בעתיד נוכל לפשט את הקוד הזה, כי LayoutNG תוכנן לתמוך בצורה גמישה בכל מצבי הפריסה המודרניים.
הבעיות במנוע הפרגמנטציה הקודם
במנוע הקודם, שתוכנן בתקופה מוקדמת יותר של האינטרנט, אין ממש מושג של פיצול, גם אם פיצול היה קיים מבחינה טכנית גם אז (כדי לתמוך בהדפסה). תמיכה בפיצול הייתה רק משהו שנוסף (הדפסה) או נוסף במהלך השנים (עמודות מרובות).
כשמגדירים את הפריסה של תוכן שניתן לפצל, המנוע הקודם מפרס את כל התוכן ברצועה גבוהה שרוחבה הוא הגודל בתוך השורה של עמודת או דף, והגובה הוא הגובה הנדרש כדי להכיל את התוכן. הפסים הארוכים האלה לא עוברים עיבוד בדף. אפשר לחשוב עליהם כעיבוד בדף וירטואלי שמאוחר יותר עבר סידור מחדש להצגה הסופית. הרעיון דומה להדפסת כתבה שלמה בעיתון מודפס בעמודה אחת, ולאחר מכן חיתוך שלה למספר עמודות בשלב השני. (בעבר, חלק מהעיתונים השתמשו בטכניקות דומות!)
המנוע מהדור הקודם עוקב אחר גבולות של דף או עמודה דמיוניים בפס. כך אפשר לדחוף תוכן שלא נכנס לגבול אל הדף או העמודה הבאים. לדוגמה, אם רק החלק העליון של הקו יתאים למה שחושב המנוע הוא הדף הנוכחי, הוא יוסיף "רצועת עימוד" כדי לדחוף אותו למטה למיקום שבו המנוע מניח שראש הדף הבא נמצא. לאחר מכן, רוב העבודה בפועל של המקטע ("חיתוך באמצעות מספריים ומיקום") מתבצעת לאחר הפריסה במהלך שרטוט הדפים הגבוהים או שרטוט העמודות, על ידי תרגום החלק העליון של הדף או שרטוט העמודות). כתוצאה מכך, אי אפשר היה לבצע כמה פעולות, כמו החלת טרנספורמציות ומיקום יחסי אחרי הפיצול (כפי שנדרש במפרט). בנוסף, למרות שבמנוע מהדור הקודם יש תמיכה מסוימת בפיצול טבלה, אין בכלל תמיכה בפרגמנטציה של רשת או גמיש.
איור שמראה איך פריסה של שלוש עמודות מיוצגת באופן פנימי במנוע הקודם, לפני שמשתמשים במספריים, במיקום ובדבק (יש לנו גובה מוגדר, כך שרק ארבע שורות נכנסות, אבל יש קצת מקום עודף בתחתית):
מנוע הפריסה הקודם לא מפצל את התוכן בפועל במהלך הפריסה, ולכן יש הרבה פריטים מוזרים, כמו מיקום יחסי וטרנספורמציות שחלות בצורה שגויה, וצלליות תיבה שנחתכות בקצוות העמודות.
הנה דוגמה עם הצללה של טקסט:
מנוע המורשת לא מטפל בזה בצורה טובה:
האם אתה רואה איך צל הטקסט מהשורה בעמודה הראשונה נחתך, ובמקום זאת מוצב בחלק העליון של העמודה השנייה? הסיבה לכך היא שמנוע הפריסה הקודם לא מבין את הפיצול.
היא אמורה להיראות כך:
בשלב הבא נוסיף קצת יותר מורכבות באמצעות טרנספורמציות ו-box-shadow. שימו לב שבמנוע הקודם יש קיצוץ שגוי וחפיפה בין עמודות. הסיבה לכך היא שלפי המפרט, טרנספורמציות אמורות להיות חלות כאפקט לאחר הפריסה ולאחר הפיצול. עם פיצול LayoutNG, שניהם פועלים בצורה תקינה. כך אפשר לשפר את יכולת הפעולה ההדדית עם Firefox, שבו יש תמיכה טובה בפיצול כבר זמן מה, ורוב הבדיקות בתחום הזה עוברות גם שם.
מנוע המורשת גם נתקל בבעיות עם תוכן מונוליתי גבוה. תוכן הוא מונוליתי אם אי אפשר לפצל אותו למספר קטעים. רכיבים עם גלילה של עודף תוכן הם מונוליטיים, כי אין טעם למשתמשים לגלול באזור לא מלבני. קופסאות קווים ותמונות הן דוגמאות נוספות לתוכן מונוליתי. לדוגמה:
אם קטע התוכן המונוליתי גבוה מדי ולא נכנס לעמודה, המנוע הקודם יחלק אותו באופן אגרסיבי (מה שמוביל להתנהגות "מעניינת" מאוד כשמנסים לגלול בקונטיינר שניתן לגלילה):
במקום לאפשר לו לחרוג מהעמודה הראשונה (כפי שקורה בחלוקה של בלוקים ב-LayoutNG):
המנוע הקודם תומך בהפסקות מאולצות. לדוגמה, <div style="break-before:page;">
יתווסף מעבר דף לפני ה-DIV. עם זאת, יש לו תמיכה מוגבלת בלבד במציאת הפסקות לא מאולצות אופטימליות. יש תמיכה ב-break-inside:avoid
וב-יתומות ויתומים, אבל אין תמיכה במניעת הפסקות בין בלוקים, אם מבקשים זאת באמצעות break-before:avoid
, לדוגמה. עיין בדוגמה זו:
כאן, לאלמנט #multicol
יש מקום ל-5 שורות בכל עמודה (כי הוא בגובה 100px והערך של line-height הוא 20px), כך שכל #firstchild
יכול להיכנס לעמודה הראשונה. עם זאת, בקוד של הדף ה'אח' #secondchild
מופיעה ההוראה break-before:avoid, כלומר המערכת צריכה להימנע מהוספת הפסקה בין שני הדפים. מכיוון שהערך של widows
הוא 2, אנחנו צריכים להעביר 2 שורות של #firstchild
לעמודה השנייה כדי למלא את כל הבקשות להימנעות מהפסקות. Chromium הוא מנוע הדפדפן הראשון שתומך באופן מלא בשילוב התכונות הזה.
איך פועלת הפיצול של מודעות ה-NG
בדרך כלל, מנוע הפריסה של NG מתכנן את המסמך על ידי סריקה של עץ התיבות של CSS לפי עומק. כשכל הצאצאים של צומת מסוים מופיעים בפריסה, אפשר להשלים את הפריסה של הצומת הזה על ידי יצירת NGPhysicalFragment וחזרה לאלגוריתם הפריסה של ההורה. האלגוריתם הזה מוסיף את הפלח לרשימת הפלחיו הצאצאים, וכאשר כל הצאצאים הושלמו, הוא יוצר לעצמו פלח עם כל הפלחיו הצאצאים. באמצעות השיטה הזו, המערכת יוצרת עץ קטעים לכל המסמך. עם זאת, זוהי פישוט יתר: לדוגמה, רכיבים שממוקמים מחוץ לזרימה יצטרכו להופיע בבועות מהמקום שבו הם קיימים בעץ ה-DOM עד לבלוק שמכיל אותם, לפני שניתן יהיה לפרוס אותם. אני מתעלם מהפרט המתקדם הזה כאן כדי לשמור על פשטות.
בנוסף לתיבת ה-CSS עצמה, LayoutNG מספק מרחב אילוצים לאלגוריתם של הפריסה. כך האלגוריתם מקבל מידע כמו המרחב הזמין לפריסה, אם הוגדר הקשר חדש של עיצוב ותוצאות של צמצום שוליים ביניים מהתוכן הקודם. מרחב האילוצים יודע גם את גודל הבלוק המתוכנן של ה-fragmentainer ואת ההיסט הנוכחי של הבלוק בתוכו. כאן המערכת מציינת איפה אפשר לקרוס.
כשיש פיצול של בלוקים, הפריסה של הצאצאים צריכה להפסיק בהפסקה. הסיבות לחלוקה כוללות מצב שבו נגמר המרחב בדף או בעמודה, או חלוקה מאולצת. לאחר מכן אנחנו יוצרים קטעי קוד (fragments) עבור הצמתים שבהם ביקרנו, וחוזרים כל הדרך עד לשורש ההקשר של הפיצול (קונטיינר של עמודות מרובות, או, במקרה של הדפסה, שורש המסמך). לאחר מכן, ברמה הבסיסית של הקשר הפיצול, אנחנו מתכוננים לקונטיינר חדש של קטעי קוד, יורדים שוב לעץ וממשיכים מהמקום שבו הפסקנו לפני ההפסקה.
מבנה הנתונים החיוני לאספקת האמצעים לחידוש הפריסה אחרי ההפסקה נקרא NGBlockBreakToken. הוא מכיל את כל המידע שדרוש כדי להמשיך את הפריסה בצורה נכונה במקטע הבא. ה-NGBlockBreakToken משויך לצומת, והוא יוצר עץ NGBlockBreakToken, כך שכל צומת שצריך להמשיך ממנו מיוצג. ה-NGBlockBreakToken מצורף ל-NGPhysicalBoxFragment שנוצר עבור צמתים שמתנתקים בתוכם. אסימוני ההפסקה מועברים להורים, ויוצרים עץ של אסימוני הפסקה. אם אנחנו צריכים להפסיק לפני צומת (במקום בתוכו), לא נוצר שבר, אבל צומת ההורה עדיין צריך ליצור אסימון הפסקה מסוג 'break-before' עבור הצומת, כדי שנוכל להתחיל בתכנון שלו כשנגיע לאותה מיקום בעץ הצמתים בקונטיינר הבא של השברים.
ההפסקות מתווספות כשנגמר לנו מקום ב-Fragmentainer (הפסקה לא מאולצת) או כשמתבצעת בקשה להפסקה מאולצת.
במפרט יש כללים להוספת הפסקות אופטימליות ללא כפייה, ולא תמיד נכון להוסיף הפסקה בדיוק במקום שבו נגמר לנו המקום. לדוגמה, יש מאפייני CSS שונים כמו break-before
שמשפיעים על בחירת מיקום ההפסקה.
במהלך הגדרת הפריסה, כדי ליישם בצורה נכונה את הקטע הפסקות לא מאולצות במפרט, אנחנו צריכים לעקוב אחרי נקודות עצירה אפשריות. המשמעות של הרשומה הזו היא שאנחנו יכולים לחזור ולהשתמש בנקודת העצירה האחרונה הטובה ביותר שנמצאה, אם ייגמר לנו המקום בשלב שבו נפר את בקשות ההימנעות מההפסקה (לדוגמה, break-before:avoid
או orphans:7
). לכל נקודת עצירה אפשרית ניתן ניקוד, החל מ'עשה זאת כמוצא אחרון בלבד' ועד 'מקום מושלם להפסקה', עם כמה ערכים ביניהם. אם מיקום ההפסקה מקבל ציון 'מושלם', סימן שאין הפרה של כללי ההפסקות אם נפסיק שם (ואם אנחנו מקבלים את הציון הזה בדיוק בנקודה שבה נגמרה לנו המרחב, אין צורך לחפש אחורה משהו טוב יותר). אם הדירוג הוא 'האפשרות האחרונה', נקודת העצירה אפילו לא תקפה, אבל יכול להיות שנפסיק שם אם לא נמצא משהו טוב יותר, כדי למנוע עומס יתר ב-fragmentainer.
נקודות עצירה תקינות מופיעות בדרך כלל רק בין אחים (קופסאות שורה או בלוקים), ולא, למשל, בין הורה לבין הצאצא הראשון שלו (נקודות עצירה מסוג C הן יוצאות דופן, אבל אין צורך לדון בהן כאן). יש נקודת עצירה (breakpoint) חוקית, למשל לפני בלוק אחות עם break-before:aנייד, אבל היא נמצאת במקום כלשהו בין 'מושלם' ל'אחרון'.
במהלך הפריסה אנחנו עוקבים אחרי נקודת העצירה הטובה ביותר שנמצאה עד כה במבנה שנקרא NGEarlyBreak. עצירה מוקדמת היא נקודת עצירה אפשרית לפני צומת בלוק או בתוך צומת בלוק, או לפני שורה (שורה של מאגר בלוקים או שורה גמישה). יכול להיות שנוצר שרשרת או נתיב של אובייקטים מסוג NGEarlyBreak, למקרה שנקפיץ על נקודת העצירה הטובה ביותר עמוק בתוך משהו שעברנו על פניו קודם, כשנגמר לנו המקום. לדוגמה:
במקרה כזה, נגמר לנו המקום ממש לפני #second
, אבל יש בו את התג break-before:avoid, שמקבל ציון של מיקום הפסקה מסוג 'הפרה של break avoid'. בשלב הזה יש לנו שרשרת NGEarlyBreak של "inside #outer
> inside #middle
> inside #inner
> before "line 3"', עם 'perfect', לכן עדיף שנפסיק שם. לכן צריך לחזור ולהריץ מחדש את הפריסה מתחילת #outer (והפעם להעביר את NGEarlyBreak שמצאנו), כדי שנוכל להפסיק לפני 'שורה 3' ב-#inner. (אנו מבצעים הפסקות לפני "שורה 3", כך שארבע השורות הנותרות יסתיימו בקטע הבא, וכדי לכבד את widows:4
).
האלגוריתם מתוכנן תמיד להפסיק בנקודת העצירה הטובה ביותר האפשרית – כפי שמוגדרת במפרט – על ידי ביטול הכללים בסדר הנכון, אם לא ניתן לעמוד בכל הכללים. חשוב לזכור שאנחנו צריכים לבצע פריסה מחדש רק פעם אחת לכל תהליך פיצול. עד שמגיעים לסבב השני של יצירת הפריסה, מיקום ההפסקה הטוב ביותר כבר הועבר לאלגוריתמים של הפריסה. זהו מיקום ההפסקה שזוהה בסבב הראשון של יצירת הפריסה, ונמסר כחלק מהפלט של הפריסה בסיבוב הזה. במינוי הפריסה השני, אנחנו לא פורסים עד שייגמר לנו המקום — למעשה לא צפוי שיגמר לנו מקום (זו באמת שגיאה), מפני שסיפקנו מקום מתוק (כמו שהיה זמין) כדי להוסיף הפסקה מוקדמת כדי להימנע מהפרה של כללים שלא לצורך. אז פשוט נשיק את הנקודה הזו ונקטע.
לצד זה, לפעמים אנחנו צריכים להפר חלק מהבקשות להימנעות מהפסקות, אם זה עוזר למנוע עומס יתר ב-fragmentainer. לדוגמה:
כאן נגמר נפח האחסון ממש לפני #second
, אבל יש בו 'break before:aflight'. התרגום הוא 'הפרת ההנחיה לגבי הימנעות מפסקות', בדיוק כמו בדוגמה האחרונה. יש לנו גם NGEAP עם "יתומים ואלמנים מפרים" (בתוך #first
> לפני "שורה 2"), שעדיין לא מושלם אבל עדיף על "הימנעות מהפסקה ברצף". לכן נפסיק לפני 'שורה 2', ונפגע בבקשה לגבי 'יתומים' או 'אלמנות'. המפרט עוסק בנושא הזה בקטע 4.4. Unforced Breaks, שבו מוגדר אילו כללי פיצול יתעלמו קודם אם אין לנו מספיק נקודות עצירה כדי למנוע עומס יתר ב-fragmentainer.
סיכום
המטרה הפונקציונלית של פרויקט הפיצול של בלוקים ב-LayoutNG הייתה לספק הטמעה שתומכת בארכיטקטורה של LayoutNG של כל מה שהמנוע הקודם תומך בו, ורק כמה שפחות דברים אחרים, מלבד תיקוני באגים. היוצא מן הכלל העיקרי הוא תמיכה טובה יותר במניעת הפסקות (break-before:avoid
, למשל), מפני שזהו חלק ליבה של מנגנון המקטעים, לכן הוא צריך להיות שם מלכתחילה, כי אם מוסיפים אותו מאוחר יותר המשמעות היא שכתוב נוסף.
עכשיו, אחרי שסיימנו את הפיצול של בלוקים ב-LayoutNG, נוכל להתחיל לעבוד על הוספת פונקציונליות חדשה, כמו תמיכה בגדלים שונים של דפים בזמן ההדפסה, תיבות שוליים של @page
בזמן ההדפסה, box-decoration-break:clone
ועוד. וכמו ב-LayoutNG בדרך כלל, אנחנו צופים ששיעור הבאגים ונטל התחזוקה של המערכת החדשה יהיו נמוכים באופן משמעותי לאורך זמן.
אישורים
- Una Kravets על 'צילום המסך הידני' הנפלא.
- כריס הרלסון על הגהה, משוב והצעות.
- Philip Jägenstedt על משוב והצעות.
- Rachel Andrew על העריכה ועל הדוגמה הראשונה עם כמה עמודות.