ממשק API למסגרות אנימציה ארוכות

Long Animation Frames API (‏LoAF – מבוטא Lo-Af) הוא עדכון ל-Long Tasks API שמאפשר הבנה טובה יותר של עדכונים איטיים בממשק המשתמש (UI). האפשרות הזו יכולה להיות שימושית כדי לזהות פריימים של אנימציה איטית שעשויים להשפיע על מדד Interaction to Next Paint (INP) – מדד ליבה לבדיקת חוויית המשתמש באתר. המדד הזה מודד את הרספונסיביות, או כדי לזהות בעיות בממשק המשתמש שמשפיעות על החלקה.

סטטוס ה-API

תמיכה בדפדפנים

  • Chrome:‏ 123.
  • Edge:‏ 123.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

אחרי גרסת מקור לניסיון מ-Chrome 116 עד Chrome 122, LoAF API שוחרר מ-Chrome 123.

רקע: Long Tasks API

תמיכה בדפדפנים

  • Chrome: 58.
  • Edge:‏ 79.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

Long Animation Frames API הוא חלופה ל-Long Tasks API, שזמין ב-Chrome כבר זמן מה (מאז Chrome 58). כפי ששמו מרמז, ה-Long Task API מאפשר לכם לעקוב אחרי משימות ארוכות, שהן משימות שתופסות את ה-thread הראשי במשך 50 אלפיות השנייה או יותר. אפשר לעקוב אחרי משימות ארוכות באמצעות הממשק PerformanceLongTaskTiming, עם PeformanceObserver:

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'longtask', buffered: true });

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

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

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

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

חסרונות של Long Tasks API

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

בדרך כלל, כלים של Real User Monitoring (RUM) משתמשים בנתון הזה כדי לזהות את מספר המשימות הארוכות או את משך הזמן שלהן, או לזהות באילו דפים הן מתרחשות. עם זאת, ללא הפרטים הבסיסיים שגרמו למשימה הארוכה, השימוש הוא מוגבל בלבד. ל-Long Tasks API יש רק מודל שיוך בסיסי, שמצביע על כך רק למאגר התגים של המשימה הארוכה (המסמך ברמה העליונה או <iframe>), אבל לא לסקריפט או לפונקציה שקראו לו, כפי שמוצג ברשומה אופיינית:

{
  "name": "unknown",
  "entryType": "longtask",
  "startTime": 31.799999997019768,
  "duration": 136,
  "attribution": [
    {
      "name": "unknown",
      "entryType": "taskattribution",
      "startTime": 0,
      "duration": 0,
      "containerType": "window",
      "containerSrc": "",
      "containerId": "",
      "containerName": ""
    }
  ]
}

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

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

Long Animation Frames API

תמיכה בדפדפנים

  • Chrome:‏ 123.
  • קצה: 123.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

Long Animation Frames API‏ (LoAF) הוא ממשק API חדש שמטרתו לטפל בחלק מהחסרונות של Long Tasks API, כדי לאפשר למפתחים לקבל תובנות מעשיות יותר שיעזרו להם לטפל בבעיות של תגובה מהירה ולשפר את INP, וגם לקבל תובנות לגבי בעיות של חלקות.

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

Long Animation Frames API הוא גישה חלופית למדידת זמן העבודה של חסימה. במקום למדוד את המשימות הנפרדות, ממשק ה-API של Long Animation Frames – כפי שמרמז השם שלו – מודד פריימים ארוכים של אנימציה. מסגרת אנימציה ארוכה היא מצב שבו עדכון הרינדור מתעכב מעבר ל-50 אלפיות השנייה (זהו הסף גם ל-Long Tasks API).

הזמן של פריימים ארוכים של אנימציה נמדד מתחילת המשימות שדורשות רינדור. אם המשימה הראשונה בפריים אנימציה ארוך פוטנציאלי לא דורשת רינדור, פריים האנימציה הארוך מסתיים עם השלמת המשימה שלא דורשת רינדור, ופריים אנימציה ארוך פוטנציאלי חדש מתחיל עם המשימה הבאה. פריים של אנימציה ארוכה שלא עבר רינדור עדיין נכלל ב-Long Animation Frames API אם הוא ארוך מ-50 אלפיות השנייה (עם זמן renderStart של 0), כדי לאפשר מדידה של עבודה שעלולה לחסום משימות אחרות.

אפשר לצפות בפריימים ארוכים של אנימציה באופן דומה למשימות ארוכות עם PerformanceObserver, אבל במקום זאת בודקים את הסוג long-animation-frame:

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'long-animation-frame', buffered: true });

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

const loafs = performance.getEntriesByType('long-animation-frame');

עם זאת, יש maxBufferSize של רשומות ביצועים, אחריו רשומות חדשות יותר נמחקות, ולכן הגישה של PerformanceObserver היא הגישה המומלצת. הגודל של מאגר הנתונים הזמני של long-animation-frame מוגדר ל-200, בדיוק כמו הגודל של long-tasks.

היתרונות של הצגת מסגרות במקום משימות

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

יתרון נוסף של התצוגה החלופית הזו במשימות ארוכות הוא היכולת לספק פירוט של תזמון של כל המסגרת. במקום לכלול רק startTime ו-duration, כמו ב-Long Tasks API, ב-LoAF יש פירוט מפורט הרבה יותר של החלקים השונים של משך הפריימים.

חותמות זמן ומשכי זמן של פריים

  • startTime: שעת ההתחלה של מסגרת האנימציה הארוכה ביחס לשעת ההתחלה של הניווט.
  • duration: משך הזמן של פריים האנימציה הארוך (לא כולל זמן הצגה).
  • renderStart: שעת ההתחלה של מחזור הרינדור, שכוללת קריאות חוזרות (callback) של requestAnimationFrame, חישוב סגנון ופריסה, שינוי גודל של אובייקט צפייה וקריאה חוזרת (callbacks) של צופה בהצטלבות.
  • styleAndLayoutStart: תחילת פרק הזמן שבו בוצעו חישובי סגנון פריסת המסך.
  • firstUIEventTimestamp: השעה של אירוע ממשק המשתמש הראשון (עכבר/מקלדת וכו') שטופל במהלך המסגרת הזו.
  • blockingDuration: משך הזמן הכולל באלפיות השנייה שבו מסגרת האנימציה תחסום עיבוד של קלט או של משימות אחרות בעדיפות גבוהה.

הסבר על blockingDuration

מסגרת אנימציה ארוכה עשויה להיות מורכבת ממספר משימות. הערך של blockingDuration הוא הסכום של משכי הזמן של משימות שנמשכות יותר מ-50 אלפיות השנייה (כולל משך העיבוד הסופי של המשימה הארוכה ביותר).

לדוגמה, אם פריים ארוך של אנימציה מורכב משתי משימות של 55 אלפיות השנייה ו-65 אלפיות השנייה, ואחריה עיבוד של 20 אלפיות השנייה, הערך של duration יהיה כ-140 אלפיות השנייה, והערך של blockingDuration יהיה (55 - 50) + (65 + 20 - 50) = 40 אלפיות השנייה. במשך 40 אלפיות השנייה במסגרת האנימציה באורך 140 אלפיות השנייה, המסגרת נחשבה חסומה לטיפול בקלט.

האם לבדוק את duration או את blockingDuration

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

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

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

תזמונים של פריימים

חותמות הזמן שצוינו קודם מאפשרות לפצל את הפריים הארוך של האנימציה לזמנים:

תזמון החישוב
שעת ההתחלה startTime
שעת סיום startTime + duration
משך העבודה renderStart ? renderStart - startTime : duration
משך העיבוד renderStart ? (startTime + duration) - renderStart: 0
רינדור: משך הפריסה מראש styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0
רינדור: משך הזמן של סגנון ופריסה styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0

שיוך משופר של סקריפטים

סוג הרשומה long-animation-frame כולל נתוני שיוך טובים יותר של כל סקריפט שתרם לפריים ארוך של אנימציה (במקרה של סקריפטים ארוכים מ-5 אלפיות השנייה).

בדומה ל-Long Tasks API, הנתונים האלה יסופקו במערך של רשומות שיוך, שבכל אחת מהן מפורטים:

  • גם name וגם EntryType יחזירו את הערך script.
  • ערך משמעותי של invoker, שמציין את אופן הקריאה לסקריפט (לדוגמה, 'IMG#id.onload',‏ 'Window.requestAnimationFrame' או 'Response.json.then').
  • invokerType של נקודת הכניסה לסקריפט:
    • user-callback: קריאה חוזרת ידועה שרשומה מ-API של פלטפורמת אינטרנט (לדוגמה, setTimeout,‏ requestAnimationFrame).
    • event-listener: האזנה לאירוע פלטפורמה (לדוגמה, click, load, keyup).
    • resolve-promise: הטיפול בהבטחה של פלטפורמה (לדוגמה, fetch(). הערה: במקרה של הבטחות, כל הטיפולים של אותן הבטחות מעורבבים יחד כ'סקריפט' אחד).
    • reject-promise: כמו ב-resolve-promise, אבל לגבי הדחייה.
    • classic-script: הערכת סקריפט (לדוגמה, <script> או import())
    • module-script: זהה ל-classic-script, אבל עבור סקריפטים של מודולים.
  • נתוני תזמון נפרדים לאותו סקריפט:
    • startTime: הזמן שבו הופעלה פונקציית הכניסה.
    • duration: משך הזמן שחלף בין startTime לבין הזמן שבו עיבוד המשימות הבאות בתור המשימות הזעירות הסתיים.
    • executionStart: השעה אחרי האיסוף.
    • forcedStyleAndLayoutDuration: משך הזמן הכולל של עיבוד הפריסה והסגנון המוגדרים מראש בתוך הפונקציה הזו (ראו טראשינג).
    • pauseDuration: משך הזמן הכולל שהוקדש לפעולות סינכרוניות ב'השהיה' (התראה, XHR סינכרוני).
  • פרטי מקור הסקריפט:
    • sourceURL: שם משאב הסקריפט, אם הוא זמין (או ריק אם הוא לא נמצא).
    • sourceFunctionName: שם פונקציית הסקריפט, אם הוא זמין (או ריק אם הוא לא נמצא).
    • sourceCharPosition: המיקום של התו בסקריפט, אם הוא זמין (או -1 אם הוא לא נמצא).
  • windowAttribution: מאגר הנתונים (המסמך ברמת העליונה או <iframe>) שבו התרחשה מסגרת האנימציה הארוכה.
  • window: הפניה לחלון מאותו מקור.

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

דוגמה לרשומה של ביצועים ב-long-animation-frame

דוגמה מלאה של רשומת ביצועים מסוג long-animation-frame, שמכילה סקריפט אחד, היא:

{
  "blockingDuration": 0,
  "duration": 60,
  "entryType": "long-animation-frame",
  "firstUIEventTimestamp": 11801.099999999627,
  "name": "long-animation-frame",
  "renderStart": 11858.800000000745,
  "scripts": [
    {
      "duration": 45,
      "entryType": "script",
      "executionStart": 11803.199999999255,
      "forcedStyleAndLayoutDuration": 0,
      "invoker": "DOMWindow.onclick",
      "invokerType": "event-listener",
      "name": "script",
      "pauseDuration": 0,
      "sourceURL": "https://web.dev/js/index-ffde4443.js",
      "sourceFunctionName": "myClickHandler",
      "sourceCharPosition": 17796,
      "startTime": 11803.199999999255,
      "window": [Window object],
      "windowAttribution": "self"
    }
  ],
  "startTime": 11802.400000000373,
  "styleAndLayoutStart": 11858.800000000745
}

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

שימוש ב-Long Animation Frames API בשדה

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

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

תמיכה בזיהוי תכונות ב-Long Animation Frames API

הקוד הבא יעזור לכם לבדוק אם ה-API נתמך:

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

התרחיש לדוגמה הבולט ביותר של Long Animation Frames API הוא לעזור לאבחן ולפתור בעיות מסוג Interaction to Next Paint (INP). זו הייתה אחת מהסיבות העיקריות לכך שצוות Chrome פיתח את ה-API הזה. מדד INP טוב הוא כזה שבו כל האינטראקציות מקבלות מענה תוך 200 אלפיות שנייה או פחות מרגע האינטראקציה ועד לציור הפריים. מאחר שממשק ה-API של Long Animation Frames מודד את כל הפריים שנדרשות להם 50 אלפיות שנייה או יותר, רוב המדדים הבעייתיים של INP אמורים לכלול נתוני LoAF שיעזרו לכם לאבחן את האינטראקציות האלה.

'INP LoAF' הוא ה-LoAF שכולל את האינטראקציה מסוג INP, כפי שמוצג בתרשים הבא:

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

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

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

במקרים נדירים, יכול להיות שהיא תכלול יותר משני מודעות LoAF.

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

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

אין ממשק API ישיר שמאפשר לקשר רשומה ב-INP לרשומה או לרשומות הרלוונטיות ב-LoAF, אבל אפשר לעשות זאת בקוד על ידי השוואה בין זמני ההתחלה והסיום של כל אחת מהן (ראו את הסקריפט לדוגמה WhyNp). הספרייה web-vitals כוללת את כל ה-LoAFs החופפים בנכס longAnimationFramesEntries של ממשק השיוך של INP מגרסה 4.

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

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

דיווח על נתוני אנימציה ארוכים יותר לנקודת קצה (endpoint) של ניתוח נתונים

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

לכן, במקום להסתכל רק על זמן הטעינה של INP, כדאי להביא בחשבון את כל זמני הטעינה במהלך משך החיים של הדף:

דף עם הרבה אירועי LoAF, חלקם מתרחשים במהלך אינטראקציות, גם אם לא במהלך האינטראקציה הראשונית באתר.
הצגת כל ה-LoAFs יכולה לעזור לזהות בעיות עתידיות במדד INP.

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

דוגמאות לדפוסים שיכולים לעזור לצמצם את כמות הנתונים של פריימים ארוכים של אנימציה:

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

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

צפייה בפריימים ארוכים של אנימציה עם אינטראקציות

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

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

הקוד הבא מתעד ביומן את כל הרשומות של LoAF עם blockingDuration גדול מ-100 אלפיות שנייה, שבהן התרחשה אינטראקציה במהלך המסגרת. הערך 100 נבחר כאן כי הוא נמוך מסף ה-INP 'הטוב' של 200 אלפיות השנייה. אפשר לבחור ערך גבוה או נמוך יותר בהתאם לצרכים שלכם.

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.blockingDuration > REPORTING_THRESHOLD_MS &&
      entry.firstUIEventTimestamp > 0
    ) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

מעקב אחרי פריימים ארוכים של אנימציה עם משכי זמן חסימה ארוכים

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

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

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.blockingDuration > REPORTING_THRESHOLD_MS) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

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

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

מכיוון שהנתונים האלה יכולים להיות רועשים מאוד, מומלץ להגביל את המדידות שלהם לנקודות מפתח לפי דפוס כזה:

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  if (measureImportantUIupdate) {
    for (const entry of list.getEntries()) {
      if (entry.duration > REPORTING_THRESHOLD_MS) {
        // Example here logs to console, but could also report back to analytics
        console.log(entry);
      }
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

async function doUIUpdatesWithMeasurements() {
  measureImportantUIupdate = true;
  await doUIUpdates();
  measureImportantUIupdate = false;
}

צפייה במסגרות האנימציה הארוכות ביותר

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

MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];

const observer = new PerformanceObserver(list => {
  longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
    (a, b) => b.blockingDuration - a.blockingDuration
  ).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });

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

בזמן המתאים (רצוי באירוע visibilitychange), האות חוזר ל-Analytics. אפשר להשתמש ב-console.table מדי פעם כדי לבצע בדיקות מקומיות:

console.table(longestBlockingLoAFs);

זיהוי דפוסים נפוצים בפריימים ארוכים של אנימציה

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

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

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

const observer = new PerformanceObserver(list => {
  const allScripts = list.getEntries().flatMap(entry => entry.scripts);
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));
  processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
  // Example here logs to console, but could also report back to analytics
  console.table(processedScripts);
});

observer.observe({type: 'long-animation-frame', buffered: true});

דוגמה לפלט הזה:

(index) sourceURL count totalDuration
0 'https://example.consent.com/consent.js' 1 840
1 'https://example.com/js/analytics.js' 7 628
2 'https://example.chatapp.com/web-chat.js' 1 5

שימוש ב-Long Animation Frames API בכלים

ה-API מאפשר גם כלים נוספים למפתחים לניפוי באגים מקומי. כלי מסוימים, כמו Lighthouse ו-Chrome DevTools, הצליחו לאסוף חלק גדול מהנתונים האלה באמצעות פרטי מעקב ברמה נמוכה יותר, אבל ממשק ה-API ברמה גבוהה יותר הזה עשוי לאפשר לכלים אחרים לגשת לנתונים האלה.

הצגת נתונים של פריימים ארוכים של אנימציה ב-DevTools

אתם יכולים להציג ב-DevTools פריימים ארוכים של אנימציה באמצעות ה-API של performance.measure(). לאחר מכן, הם יוצגו במסלול 'זמני השימוש של המשתמשים' ב-DevTools בנתוני המעקב אחר הביצועים, כדי להראות איפה כדאי להתמקד בשיפור הביצועים. באמצעות DevTools Extensibility API אפשר אפילו להציג אותם בטר'ק משלהם:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    performance.measure('LoAF', {
      start: entry.startTime,
      end: entry.startTime + entry.duration,
      detail: {
        devtools: {
          dataType: "track-entry",
          track: "Long animation frames",
          trackGroup: "Performance Timeline",
          color: "tertiary-dark",
          tooltipText: 'LoAF'
        }
      }
    });
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });
ניתוח בחלונית הביצועים של DevTools עם טראק מותאם אישית שמוצגים בו נתונים של פריימים ארוכים באנימציה, שניתן להשוות אותם לתרשים הלהבה הראשי.
הצגת נתונים ארוכים של פריימים של אנימציה ב-DevTools.

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

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

שימוש בנתונים של מסגרות אנימציה ארוכות בכלים אחרים למפתחים

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

עכשיו מוצגים גם נתונים של פריימים ארוכים של אנימציה לכל קריאה חוזרת של INP וכל אינטראקציה:

רישום ביומן של מסוף התוסף של Web Vitals.
יומני המסוף של תוסף Web Vitals מציגים נתוני LoAF.

שימוש בנתונים של פריימים ארוכים של אנימציה בכלים לבדיקות אוטומטיות

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

שאלות נפוצות

דוגמאות לשאלות נפוצות בנושא ה-API הזה:

למה לא פשוט להרחיב או לבצע איטרציה על Long Tasks API?

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

אפשר להפיק תועלת מ-Long Tasks API מחלק מהתכונות של LoAF (כמו מודל שיוך טוב יותר), אבל אנחנו מאמינים שהתמקדות בפריימים במקום במשימות מעניקה הרבה יתרונות שהופכים את ה-API הזה לשונה לחלוטין מה-API הקיים של Long Tasks.

למה אין לי רשומות של סקריפטים?

ייתכן שהסיבה לכך היא שפריים האנימציה הארוך לא נגרם בגלל JavaScript, אלא בגלל עיבוד (render) נרחב.

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

למה יש לי רשומות של סקריפטים אבל לא מידע על מקור, או מידע מוגבל על מקור?

יכולות להיות לכך כמה סיבות, למשל אין מקור טוב שאפשר להפנות אליו.

גם המידע על הסקריפטים יהיה מוגבל בסקריפטים של no-cors cross-origin, אבל אפשר לפתור את הבעיה על ידי אחזור הסקריפטים האלה באמצעות CORS, על ידי הוספת crossOrigin = "anonymous" לקריאה של <script>.

לדוגמה, סקריפט ברירת המחדל של Google Tag Manager שצריך להוסיף לדף:

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->

אפשר לשפר את הקוד ולהוסיף את j.crossOrigin = "anonymous" כדי לאפשר לספק פרטי שיוך מלאים ל-GTM

האם ה-API הזה יחליף את Long Tasks API?

אנחנו מאמינים ש-Long Animation Frames API הוא API טוב יותר ומלא יותר למדידת משימות ארוכות, בשלב זה, אנחנו לא מתכננים להוציא משימוש את Long Tasks API.

אנחנו מבקשים משוב

אפשר לשלוח משוב ברשימת הבעיות ב-GitHub. אפשר גם לדווח על באגים בהטמעה של ה-API ב-Chrome בכלי למעקב אחרי בעיות של Chrome.

סיכום

Long Animation Frames API הוא ממשק API חדש ומרגש עם יתרונות פוטנציאליים רבים על פני Long Tasks API הקודם.

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

עם זאת, היקף ה-Long Animation Frames API חורג ממדד ה-INP, והוא יכול לעזור לזהות סיבות אחרות לעדכונים איטיים שעשויות להשפיע על רמת החלקות הכוללת של חוויית המשתמש באתר.

תודות

התמונה הממוזערת היא של Henry Be ב-Unsplash.