תיקון חוסר יציבות בפריסה

הסבר מפורט על שימוש ב-WebPageTest כדי לזהות ולתקן בעיות של חוסר יציבות בפריסה.

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

מדידת שינויי פריסה

באמצעות Layout Instability API, אפשר לקבל רשימה של כל אירועי שינוי הפריסה בדף:

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

הפונקציה הזו מחזירה מערך של שינויי פריסה שלא קדמו להם אירועי קלט:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

בדוגמה הזו, היה שינוי קטן מאוד של 0.01% ב-210ms.

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

מדידת שינויי פריסה ב-WebPageTest

בדומה למדידת CLS ב-WebPageTest, כדי למדוד שינויים נפרדים בפריסה צריך להשתמש במדד מותאם אישית. למזלכם, התהליך פשוט יותר עכשיו כש-Chrome 77 יציב. ה-API של חוסר יציבות הפריסה מופעל כברירת מחדל, כך שאפשר להריץ את קטע ה-JS הזה בכל אתר ב-Chrome 77 ולקבל תוצאות באופן מיידי. ב-WebPageTest, אפשר להשתמש בדפדפן Chrome שמוגדר כברירת מחדל ולא צריך לדאוג לגבי דגלים של שורת הפקודה או שימוש ב-Canary.

לכן נשנה את הסקריפט הזה כדי ליצור מדד מותאם אישית עבור WebPageTest:

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

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

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

זיהוי הסיבות לחוסר יציבות בפריסה

בתוצאות אפשר לראות שהערך של המדד המותאם אישית LayoutShifts הוא:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

לסיכום, יש שינוי פריסה אחד של 34.2% שמתרחש ב-3087ms. כדי לזהות את הגורם הבעייתי, נשתמש בתצוגת רצועת השקפים של WebPageTest.

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

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

כותרת של גופן אינטרנט מופיעה משום מקום.
כותרת של גופן אינטרנט מופיעה משום מקום.

אבל זה לא הכול. אחרי כ-4.3 שניות, כשהדף הושלם מבחינה ויזואלית, אפשר לראות שהדף <h1> 'Is my host fast yet?‎' מופיע משום מקום. הסיבה לכך היא שהאתר משתמש בגופן אינטרנט ולא ננקטו בו צעדים לאופטימיזציה של העיבוד. הפריסה לא משתנה בפועל כשזה קורה, אבל עדיין מדובר בחוויית משתמש לא טובה כי צריך לחכות כל כך הרבה זמן עד שאפשר לקרוא את הכותרת.

תיקון חוסר יציבות בפריסה

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

הנה הקוד ליצירת נתוני placeholder:

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

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

המראה של ה-placeholders שבהם אתם משתמשים לא משפיע על יציבות הפריסה. מטרת ה-placeholder היא להבטיח למשתמשים שתוכן יוצג והדף לא פגום.

כך נראים ה-placeholder בזמן טעינת נתוני ה-JSON:

טבלת הנתונים מוצגת עם נתוני placeholder.
טבלת הנתונים מוצגת עם נתוני placeholder.

הטיפול בבעיה של גופן האינטרנט פשוט יותר. בגלל שהאתר משתמש בגופנים של Google, אנחנו צריכים להעביר את המאפיין display=swap בבקשת ה-CSS. זה הכול. ‫Fonts API יוסיף את הסגנון font-display: swap בהצהרת הגופן, וכך הדפדפן יוכל לעבד את הטקסט בגופן חלופי באופן מיידי. כך ייראה אותו פריט עם התיקון:

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

אימות האופטימיזציות

אחרי שמריצים מחדש את הדף דרך WebPageTest, אפשר ליצור השוואה בין המצב לפני לבין המצב אחרי כדי לראות את ההבדל ולמדוד את רמת חוסר היציבות החדשה של הפריסה:

רצועת סרטונים ב-WebPageTest שמציגה את שני האתרים נטענים זה לצד זה עם אופטימיזציות של פריסות ובלי אופטימיזציות של פריסות.
רצועת סרט של WebPageTest שמציגה את שני האתרים נטענים זה לצד זה עם אופטימיזציות של פריסה ובלי אופטימיזציות של פריסה.
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

לפי המדד המותאם אישית, עדיין מתרחש שינוי פריסה אחרי 3,071 אלפיות השנייה (בערך באותו זמן כמו קודם), אבל חומרת השינוי הרבה יותר נמוכה: 0.005%. אני יכול לחיות עם זה.

בנוסף, אפשר לראות ב-filmstrip שהגופן <h1> עובר מיד לגופן מערכת, וכך המשתמשים יכולים לקרוא אותו מוקדם יותר.

סיכום

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

(עוד דבר אחד) מדידת חוסר היציבות של הפריסה שחווים משתמשים אמיתיים

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

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

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