פעימות

על CSS Containment ודרכים לאופטימיזציה ושיפור ביצועי רינדור

CSS Containment הוא מודול המתאר תכונת CSS יחידה הנקראית contain . תכונה זו מאפשרת לנו כמפתחים לבודד Subtree של אלמנט מסויים ב DOM ולהגדירו כעצמאי ונפרד משאר האלמנטים ב DOM.

המטרה העיקרית של CSS Containment היא שיפור ביצועי הרינדור (rendering) וקיצור זמן הטעינה של עמוד אינטרנטי על ידי מתן שליטה מסויימת בתהליך הרינדור שהדפדפן מבצע.

נדמיין רגע עמודHTML בעל DOM מורכב. נניח ואנו יודעים כי אלמנט מסויים בעמוד זה נפרד לגמרי משאר העמוד ועתיד לעבור שינוי בשלב כזה או אחר. כשאותו שינוי יתבצע ייאלץ הדפדפן לוודא כי הוא לא השפיע על אלמנטים אחרים בעמוד, וייעשה זאת על ידי רינדור מחדש של כל אחד מהאלמנטים ב DOM.

ולמרות שדפדפנים נמנעים עד כמה שניתן מעבודה מיותרת בעזרת האוריסטיקה מסויימת, בסיטואציות רבות הם נאלצים לרנדר מחדש עמוד שלם בעקבות שינוי מינורי לאלמנט מסויים. במקרים אלו נוכל להעזר בתכונה contain ולמנוע את אותן פעולות מיותרת שהדפדפן מבצע לשווא.

אך לפני שניגע בתכונה זו בואו ניתן מבט בתמונה המתארת את התהליך שעל הדפדפן לעבור בכדי לצייר לנו את הפיקסלים על המסך (pixel pipeline). בפוסט זה נתרכז בשלושת השלבים האמצעיים בלבד…

triggering layout and paint

CSS Containment אינה ספסיפקציה חדשה כפי שרבים חושבים והוצגה עוד בגירסה 52 של כרום שיצאה ביוני 2016.

התכונה contain

הספציפקציה של CSS Containment מדברת על תכונת CSS יחידה בשם contain ולה מספר ערכים, ערך אחד עבור כל אחד מסוגי ה containment הקיימים:

/* Keyword values */
contain: none;
contain: strict;
contain: content;
contain: size;
contain: layout;
contain: style;
contain: paint;

/* Multiple keywords */
contain: size paint;
contain: size layout paint;

/* Global values */
contain: inherit;
contain: initial;
contain: unset;

התכונה נתמכת בכל הדפדפנים המובילים חוץ מ Safari כפי שניתן לראות בטבלה הבאה:

נסביר כעת על הערכים שהתכונה contain יכולה לקבל, אלו בעצם סוגי ה containment הקיימים עבורינו…

שימו לב – כשאני אומר ״תוכן של אלמנט״ במהלך הפוסט אני מתכוון בעצם לכל הצאצאים (Descendants) של אותו אלמנט. כשאני אומר ״אלמנט ב DOM״ אני בעצם מתכוון ל Subtree של אותו אלמנט.

סוגי ה Containment הקיימים

את התכונה contain ניתן להגדיר בשתי דרכים. הראשונה היא קביעת ערך יחיד מבין הערכים none, strict, content, והשנייה היא קביעת ערך אחד או יותר מהערכים size, layout, style & paint.

  • הערך layout מציין כי שינוי התוכן של האלמנט המדובר לא ישפיע על אלמנטים אחרים בעמוד, ונהפוכו. לאלמנטים חיצוניים לא תהיה השפעה כלל על אלמנט זה.
  • הערך paint מציין כי התוכן של האלמנט לא יגלוש (overflow) בשום סיטואציה מחוץ לגבולות האלמנט. אם האלמנט מחוץ ל viewport הדפדפן לא יבצע את פעולת ה paint על התוכן של אותו אלמנט.
  • הערך style מציין כי האפקט של תכונות CSS מסויימות (counters & quotes) לא ישפיע על דבר מחוץ לגבולות האלמנט.
  • הערך size מציין כי ניתן לחשב את גודל האלמנט ללא צורך בבדיקת התוכן של אותו אלמנט.

אין חשיבות לסדר הכתיבה של הערכים ונזכיר שצורת הכתיבה נראית כך:

.item {
    contain: size layout paint;
}

נחמד לדעת כי הספסיפיקציה מספקת לנו שני קיצורי דרך, כלומר שני ערכים נוספים המייצגים שילוב מסויים, קומבינציה כלשהי הבנויה מערכי ה contaiment הקיימים:

  • הערך content מציין כל חוקי ה containment למעט style ו size מיושמים על האלמנט. זהה לשימוש בתכונה contain: layout paint.
  • הערך strict מציין כל חוקי ה containment למעט style מיושמים על האלמנט. זהה להגדרת התכונה contain: size layout paint.

במקרה זה מתבצע שימוש בערך אחד בלבד והקוד נראה כך:

    .item {
        contain: strict;
    }

    לא ציינתי אך קיים גם הערך none הקובע כי האלמנט ירונדר כרגיל וללא containment.

    דוגמה א׳ – Example A

    הנה דוגמה המציגה את שיפור הביצועים המתקבל כשאנו משתמשים ב containment. דמיינו לעצמיכם עמוד עם המון אלמנטים, 10,000 ליתר דיוק, וה markup של כל אחד מהאלמנטים נראה כך:

    <div class="item">
       <div class="content">לורם איפסום...</div>
    </div>

    בדוגמה זו אנו משנים באמצעות javascript את התוכן של אלמנט יחיד בעמוד, פעולה שתוביל את הדפדפן לרנדר שוב את כל האלמנטים ב DOM (שהוא מאד גדול במקרה זה). זו כמובן עבודה מיותרת מכיוון ולשינוי שביצענו לא הייתה השפעה כלל על אלמנטים אחרים בעמוד.

    בכדי להמנע מהסיטואציה ולחסוך את העבודה המיותרת הוספנו את התכונה contain: strict וקבענו לדפדפן כי אותו אלמנט שעתיד ללהשתנות הוא עצמאי ונפרד משאר העמוד. השימוש ב containment הוביל לשיפור מאד משמעותי בביצועים ובזמן הנדרש לדפדפן לבצע את פעולת ה layout.

    תנו מבט בתמונה מטה המציגה את התוצאה הממוצעת של הריצה הראשונה והריצה השנייה שביצענו:

    שיפור ביצועי rendering בעזרת CSS Containment

    קרדיט – הדוגמה הועתקה ונלקחה מהאתר blogs.igalia.com

    למרות שהאלמנטים בדוגמה זו מאד פשוטים, אנו חווים ירידה מ (17ms~) ל (0.5ms~) בזמן הנדרש לבצע את הפעולה.

    אך לא הכל ורוד חברים. מכיוון ואותן אופטימיזציות שהדפדפן מבצע נכנסות לפעולה רק בסיטואציות מסויימות ורק כשמתקיים סט מסויים של תנאים, ייתכן וההחלטה אילו ערכי containment נכונים עבור סיטואציה מסויימת לא תהיה פשוטה.

    לשמחתינו ישנה תכונת CSS חדשה הנקראית content-visibility הבאה לעזרתינו ומורידה מאיתנו את הצורך לקבל בעצמינו את אותה החלטה…

    התכונה content-visibility

    התכונה content-visibility תקבע עבורינו את תכונות ה containment האופטימליות. התכונה יכולה לקבל את הערכים visible, auto & hidden, אך לבטח הערך auto יהיה זה בו לרוב נשתמש.

    הוספת התכונה content-visibility: auto לאלמנט מקבילה להוספת התכונה contain: size layout paint. אז מה בעצם ההבדל בין אלו? מה היתרון של תכונה חדשה זו?

    ההבדל בין אלו מתבטא אך ורק במצבים בהם האלמנט לו מוסיפים את התכונה נמצא מחוץ ל viewport ואינו רלוונטי למשתמש. content-visibility: auto מאפשרת לדפדפן לבדוק באופן עצמאי אם האלמנט נמצא מחוץ ל viewport, ובמידה וכן הדפדפן יוסיף גם size containment לאלמנט המדובר.

    התוכן הקיים באלמנט כלל לא ירונדר והפעולה היחידה שהדפדפן יבצע הוא חישוב הגודל הנחוץ לקונטיינר בכדי להכיל את התוכן. פעולת ה paint תתבצע בדיוק בזמן ורק כשיגיע הצורך להציג למשתמש את האלמנט.

    אל תרוצו להשתמש בתכונה זו. היא חדשה והגיחה לעולם ממש לאחרונה. הדפדפן היחיד שתומך בזו נכון לזמן כתיבת הפוסט הוא גוגל כרום בגירסה 85 ומעלה.

    דוגמה ב׳ – Example B

    בסרטון הבא נראה עמוד המדמה בלוג טיפוסי. חלקו העליון של העמוד מכיל תמונה ראשית, טקסט קצר ומספר תמונות קטנות, ומתחת לאלו ישנם מספר פוסטים אינדיבידואלים המכילים גם כן טקסט ותמונות.

    הסרטון ודוגמה זו נלקחו במלואם מהפוסט הבא ב web.dev.

    בסרטון ניתן לראות את ההבדלים בזמן הנדרש לרנדר את העמוד כשאנו מוסיפים את התכונה content-visibility : auto לאותם פוסטים אינדיבידואלים לעומת הזמן הנדרש ללא תכונה זו.

    שימו לב לנתון rendering שמוצג לפניכם בסוף כל אחת מהבדיקות.

    הכנתי עבורכם דוגמה חיה ומדוייקת של אותם עמודים המופיעים בסרטון בכדי שתוכלו לבצע את הבדיקות בעצמכם. בדוגמה הראשונה לא נעשה שימוש ב containment, ובדוגמה השנייה כן. הנה התוצאה:

    ללא התכונה content-visibility
    עם התכונה content-visibility: auto

    שיפור ניכר בביצועים כפי שניתן לראות. כל מה שעשינו הוא להוסיף את התכונות הבאות לכל אחד מהפוסטים האינדיבידואלים הנמצאים מטה יותר בעמוד:

     content-visibility: auto;
      contain-intrinsic-size: 1000px; /* נסביר על תכונה זו הסקשיין הבא */
    

    ממה נובע השיפור בזמן הנחוץ לרנדר את העמוד?

    השימוש בתכונות ה containment על כל האלמנטים שנמצאים מחוץ ל viewport, הוביל לכך שהדפדפן ביצע את פעולות ה style וה layout אך ורק על הקונטיינר ולא על התוכן של אותם אלמנטים – כלומר חסכנו עבודה מיותרת שהובילה לשיפור שראינו.

    חשוב להבהיר כי פעולת הרינדור תתחיל ותפסיק לפי הצורך במהלך הגלילה והתזוזה של תוכן מחוץ ל viewport ובחזרה, אך אין להסיק מכך שהפעולה תתבצע שוב ושוב במהלך התזוזה מכיוון ומצב הרינדור נשמר בדפדפן כשקיימת האפשרות.

    ״מצב הרינדור״ שהזכרנו נקרא בשפה המקצועית cached rendering state. אותו מצב רינדור נשמר בזכרון הדפדפן ונגיש עבורינו (במצבים מסויימים).

    על כל מקרה, לבטח שמתם לב כי השתמשנו בתכונת CSS שאינה מוכרת בקוד המצורף מעלה, בואו נסביר על הצורך והתפקיד של התכונה contain-intrinsic-size.

    קביעת גודלו הטבעי של אלמנט עם contain-intrinsic-size

    contain-intrinsic-size מאפשרת לנו להגדיר מעין placeholder בו הדפדפן ישתמש כשמיישמים containment מסוג size על אלמנט. האלמנט יתפרס (מבחינת layout) כאילו היה לו צאצא יחיד עם הגודל שקבענו באמצעות התכונה, וזאת רק במידה ולאלמנט לא מוגדר גובה (height) בצורה מפורשת.

    השימוש ב contain-intrinsic-size מאפשר לנו לוודא שפעולת הרינדור (העתידית) של תוכן כלשהו לא תשפיע על גודל הקונטיינר המכיל אותו בשום צורה. באופן זה אנו מונעים תזוזה פתאומית ולא רצויה בעמוד או קפיצה בפסי הגלילה (scrollbars) בעקבות השינוי בגודל, סיטואציה שעלולה לפגוע בחווית המשתמש.

    בדוגמה ב׳ שהצגנו קודם קבענו ערך של 1000px לתכונה contain-intrinsic-size. זוהי הערכה בלבד של הגובה הנחוץ לתוכן האלמנט, כלומר לאותם פוסטים הנמצאים ״מחוץ למסך״ בטעינה הראשונית.

    הסתרת תוכן עם content-visibility: hidden

    ומה אם אנו מעוניינים שהתוכן של אלמנט מסויים לא ירונדר כלל (גם אם ב viewport), וזאת כשאנו עדיין יכולים לנצל את את מצב הרינדור, אותו cached rendering state שהזכרנו קודם?

    במקרה זה נשתמש בתכונה content-visibility: hidden המספקת את אותן היתרונות ש content-visibility: auto מספקת לאלמנטים מחוץ ל viewport, אך לעומתה זו תמנע מהדפדפן את רינדור התוכן גם כשיהיה ב viewport.

    התכונה מספקת לנו דרך נוספת להסתיר אלמנט, אך מאפשרת להציגו בשלב מאוחר יותר בצורה מהירה יותר מבחינת ביצועים.

    בואו נשווה זו לתכונות CSS אחרות המאפשרות להסתיר אלמנטים:

    ההבדלים בין שלושת התכונות להסתרת אלמנטים

    • display: none תסתיר את האלמנט ותהרוס את מצב הרינדור שלו. הצגת האלמנט לאחר מכן תעלה לנו ביוקר מבחינת עבודה שעל הדפדפן לבצע (כמו יצירת אלמנט חדש).
    • visibility: hidden תסתיר את האלמנט אך תשמור על מצב הרינדור. בפועל האלמנט לא יוסר ועדיין יתפוס שטח בעמוד. ניתן אף יהיה לבצע פעולות עם אלמנט זה (לחיצה למשל).  הדפדפן יעדכן בעת הצורך את מצב הרינדור של האלמנט גם כשהוא חבוי.
    • content-visibility: hidden תסתיר את האלמנט אך תשמור על מצב הרינדור שלו. אם יהיה צורך בשינוי של מצב הרינדור, הוא יתבצע רק לאחר שנסיר את התכונה המדוברת.

    לסיכום

    ניתן להסתכל על CSS Containment כעל מודול המאפשר לבצע lazy loading לתוכן. הוא יכול לעזור לכם לשפר את הנתון Time to Interactive ב Google PageSpeed, ובעצם להקטין את הזמן הנדרש לעמוד להיות אינטראקטיבי ולהגיב לפעולות שמבצע המשתמש בטעינה הראשונית.

     מידע נוסף על הנתון Time To Interactive ועל כיצד למדוד אותו ניתן למצוא תחת סעיף 5 בקישור המצורף.

    עד כאן חברים – לא נרחיב מעבר לכך. תאמינו או לא זה הפוסט שלקח לי הכי הרבה זמן לכתוב, הרבה יותר זמן מכל פוסט אחר בסאבי בלוג, ואני אפילו לא יודע למה… שאלות ותגובות יתקבלו בברכה.

    רועי יוסף
    רועי יוסף

    שמי רועי יוסף, מפתח אתרי וורדפרס בעל תשע שנות ניסיון. אני מפתח מנוסה המייצר תבניות יעודייות ותפורות ללקוח על בסיס עיצוב. אוהב טיפוגרפיה, צבעים וכל מה שבינהם ומכוון לספק אתרים רספונסיבים עם ביצועים גבוהים, בעלי קוד ולידי, סמנטי ונקי.

    1תגובות...
    • adiel 12 בספטמבר 2020, 23:41

      מצויין כמו תמיד

    השאירו תגובה

     

    Up!
    לבלוג