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

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

סטטוס ה-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 מאפשר לכם לעקוב אחרי משימות ארוכות – משימות שמשתלטות על השרשור הראשי למשך 50 אלפיות השנייה או יותר. אפשר לעקוב אחרי משימות ארוכות באמצעות הממשק PerformanceLongTaskTiming, עם PeformanceObserver:

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

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

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

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

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

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

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

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

לרוב, כלים למעקב אחר משתמשים אמיתיים (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.
  • Edge: ‏ 123.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

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

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

Long Animation Frames API היא גישה חלופית למדידת זמן העבודה של חסימה. במקום למדוד את המשימות הנפרדות, Long Animation Frames API – כפי ששמו מרמז – מודד פריימים ארוכים של אנימציה. מסגרת אנימציה ארוכה היא מצב שבו עדכון הרינדור מתעכב מעבר ל-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: שעת ההתחלה של מחזור הרינדור, שכולל קריאות חזרה (callbacks) של requestAnimationFrame, חישוב סגנון ופריסה, קריאות חזרה של משקיף שינוי הגודל ושל משקיף הצטלבות.
  • 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 הוא כדי לעזור באבחון ובפתרון בעיות של אינטראקציה לצביעה הבאה (INP), וזו הייתה אחת מהסיבות העיקריות לכך שצוות Chrome פיתח את ה-API הזה. אינטראקציה מהירה היא אינטראקציה שמתקבלת אליה תגובה תוך 200 אלפיות שנייה או פחות מרגע האינטראקציה ועד לציור הפריים. מאחר שממשק ה-API לפריימים ארוכים של אנימציה מודד את כל הפריים שנדרשות להם 50 אלפיות שנייה או יותר, רוב האינטראקציות הבעייתיות צריכות לכלול נתוני 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 איטי.

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

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

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

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

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

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

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

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

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

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

זו גם שיטה קלה יותר למעקב אחרי אירועי LoAF ב-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;
}

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

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

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 הבקשות עם זמן האחזור הארוך ביותר, עם אינטראקציות שנמשכות יותר מ-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.
יומני המסוף של תוסף Core Web Vitals מציגים נתוני LoAF.

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

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

שאלות נפוצות

ריכזנו כאן כמה מהשאלות הנפוצות בנושא ה-API הזה:

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

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

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

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

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

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

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

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

גם פרטי הסקריפטים יוגבלו רק ל-sourceURL (לא כולל הפניות אוטומטיות) עבור סקריפטים של no-cors cross-origin, עם מחרוזת ריקה עבור sourceFunctionName ו--1 עבור sourceCharPosition. כדי לפתור את הבעיה, אפשר לאחזר את הסקריפטים האלה באמצעות 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.