Core Web Vitals

איך לשפר Interaction to Next Paint: אבחון עיכובי קלט, עיבוד והצגה

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

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

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

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

מבינים מה INP מודד ומה אינו כולל

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

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

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

INP החליף את First Input Delay כמדד Core Web Vital במרץ 2024. ‏FID כיסה רק את השהיית הקלט הראשונית בפעולה המתאימה הראשונה, ולא את התגובה המלאה לאורך הביקור. עצות ישנות שממוקדות ב-FID עלולות להחמיץ פעולה מאוחרת איטית או עבודת הצגה יקרה.

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

מחלקים את ההשהיה לשלושה שלבים

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

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

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

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

מקליטים את הפעולה במצב מייצג

משתמשים בלוח Performance של Chrome DevTools כדי להקליט את הפעולה עצמה. מתחילים הקלטה, משחזרים את הפעולה, עוצרים ובודקים את ציר הזמן. כוללים את המצב החשוב: תפריט פתוח, עגלה מלאה, רשימה מורחבת או דף שצבר תוכן בעקבות גלילה.

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

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

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

מוסיפים attribution מעבר לדוחות המצטברים

CrUX ו-Search Console יכולים לזהות בעיה ברמת אוכלוסייה בלי לזהות את הרכיב או הגרסה שגרמו לה. מדידה עצמאית יכולה לקשור פעולות איטיות להקשר ביישום, בכפוף לפרטיות ולתמיכת הדפדפן.

מודול האבחון הבא משתמש בגרסת attribution של web-vitals בפרויקט עם חבילה תואמת מותקנת. הוא רושם זמני שלבים במקום לשלוח פרטי אינטראקציה לשרת.

js
import { onINP } from 'web-vitals/attribution';

onINP(({ value, attribution }) => {
  console.table({
    inp: value,
    inputDelay: attribution.inputDelay,
    processingDuration: attribution.processingDuration,
    presentationDelay: attribution.presentationDelay
  });
});

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

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

מסירים עבודה שמונעת את תחילת הקלט

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

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

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

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

מוותרים על שליטה בין חלקי עבודה עצמאיים

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

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

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

html
<button id="process" type="button">Process example data</button>
<p id="progress" role="status">Ready</p>
<script type="module">
  const button = document.querySelector('#process');
  const progress = document.querySelector('#progress');
  const values = Array.from({ length: 10000 }, (_, index) => index);

  async function yieldToBrowser() {
    if (globalThis.scheduler?.yield) {
      await globalThis.scheduler.yield();
    } else {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }

  button.addEventListener('click', async () => {
    button.disabled = true;
    progress.textContent = 'Processing';
    try {
      let total = 0;
      for (let start = 0; start < values.length; start += 250) {
        await yieldToBrowser();
        const end = Math.min(start + 250, values.length);
        for (let index = start; index < end; index++) {
          total += values[index];
        }
      }
      progress.textContent = `Total: ${total}`;
    } finally {
      button.disabled = false;
    }
  });
</script>

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

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

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

שומרים על תגובה מיידית קטנה ומועילה

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

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

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

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

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

מצמצמים את הרינדור שאחרי המטפל

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

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

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

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

בודקים צדדים שלישיים במהלך אותה פעולה

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

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

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

מאמתים תגובתיות מעבר להקלטה טובה אחת

הרצת ניווט רגילה של Lighthouse מודדת טעינה ומדווחת TBT כסימן לחסימה. היא אינה יוצרת את פעולות המבקר הדרושות ל-INP בשטח. אין להמיר TBT ל-INP או לתאר ציון משופר כהוכחה שהמסנן האיטי תוקן.

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

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

מדריכים קשורים