הימנעות מצבעים מיותרים

מבוא

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

ציור: סיור מהיר במיוחד

אחת מהמשימות העיקריות שדפדפן צריך לבצע היא המרת ה-DOM וה-CSS לתמונה של פיקסלים במסך, והוא עושה זאת באמצעות תהליך מורכב למדי. התהליך מתחיל בקריאת ה-Markup, וממנו נוצר עץ DOM. הוא מבצע פעולה דומה עם ה-CSS, וממנו יוצר את ה-CSSOM. לאחר מכן, DOM ו-CSSOM משולבים, ובסופו של דבר מגיעים למבנה שממנו אפשר להתחיל לצייר כמה פיקסלים.

תהליך הציור עצמו מעניין. ב-Chrome, העץ המשולב של DOM ו-CSS עובר רסטריזציה על ידי תוכנה שנקראת Skia. אם שיחקתם פעם עם האלמנט canvas, ממשק ה-API של Skia ייראה לכם מוכר מאוד. יש בו הרבה פונקציות בסגנון moveTo ו-lineTo, וגם כמה פונקציות מתקדמות יותר. בעיקרון, כל הרכיבים שצריך לצייר מתמצתים באוסף של קריאות ל-Skia שאפשר לבצע, והפלט הוא קבוצה של בייטים. קובצי ה-bitmap האלה מועלים ל-GPU, והוא עוזר בהרכבתם יחד כדי ליצור את התמונה הסופית במסך.

מ-DOM לפיקסלים

העיקרון הוא שעומס העבודה של Skia מושפע ישירות מהסגנונות שאתם מחילים על הרכיבים. אם אתם משתמשים בסגנונות כבדים מבחינה אלגוריתמית, ל-Skia תהיה יותר עבודה. Colt McAnlis כתב מאמר על האופן שבו CSS משפיע על משקל העיבוד של דף, מומלץ לקרוא אותו כדי לקבל תובנות נוספות.

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

גלילה

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

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

הצגת מלבנים של צבע בכלי הפיתוח ל-Chrome
הצגת מלבנים של משיכות צבע בכלי הפיתוח ל-Chrome

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

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

אינטראקציות

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

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

שילוב לא מוצלח

הדגמה עם צבעים יקרים
הדגמה עם צבעים יקרים

מה קורה אם גוללים ומזיזים את העכבר בו-זמנית? יכול להיות שאעשה באופן לא מכוון "אינטראקציה" עם רכיב בזמן שאעביר מעליו את הסמן, וכך אגרום להפעלה של פעולת צביעה יקרה. כתוצאה מכך, יכול להיות שאחרוג מתקציב הפריימים שלי של כ-16.7 אלפיות השנייה (הזמן שאנחנו צריכים להישאר מתחתיו כדי להגיע ל-60 פריימים לשנייה). יצרתי הדגמה כדי להראות לך בדיוק למה התכוונתי. כשאתם גוללים ומזיזים את העכבר, אתם אמורים לראות את האפקטים של העברת העכבר מעל הרכיבים. אבל בואו נראה מה מוצג בכלים למפתחים של Chrome:

פריימים יקרים שמוצגים בכלי הפיתוח של Chrome
כלי הפיתוח של Chrome שמוצגים בו פריימים יקרים

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

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

זה הקוד:

// Used to track the enabling of hover effects
var enableTimer = 0;

/*
 * Listen for a scroll and use that to remove
 * the possibility of hover effects
 */
window.addEventListener('scroll', function() {
  clearTimeout(enableTimer);
  removeHoverClass();

  // enable after 1 second, choose your own value here!
  enableTimer = setTimeout(addHoverClass, 1000);
}, false);

/**
 * Removes the hover class from the body. Hover styles
 * are reliant on this class being present
 */
function removeHoverClass() {
  document.body.classList.remove('hover');
}

/**
 * Adds the hover class to the body. Hover styles
 * are reliant on this class being present
 */
function addHoverClass() {
  document.body.classList.add('hover');
}

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

/* Expect the hover class to be on the body
 before doing any hover effects */
.hover .block:hover {
 
}

זהו, סיימתם.

סיכום

ביצועי הרינדור חיוניים כדי שהמשתמשים ייהנו מהאפליקציה, ותמיד כדאי לשאוף לשמור על עומס העבודה של הציור מתחת ל-16ms. כדי לעשות זאת, כדאי לשלב את DevTools במהלך תהליך הפיתוח כדי לזהות צווארי בקבוק ולתקן אותם כשהם מופיעים.

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

כדאי לבדוק את האתרים והאפליקציות שלכם. אולי הם זקוקים לקצת הגנה?