אופטימיזציה של האינטראקציה עד הצגת התגובה הבאה

איך לשפר את מהירות התגובה לאינטראקציה באתר (INP).

תאריך פרסום: 19 במאי 2023, תאריך עדכון אחרון: 2 בספטמבר 2025

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

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

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

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

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

איך לגלות מה גורם ל-INP נמוך

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

איך למצוא אינטראקציות איטיות בשטח

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

אם אתם לא מסתמכים על ספק RUM כדי לקבל נתוני שדה, במדריך לנתוני שדה של INP מומלץ להשתמש ב-PageSpeed Insights כדי להציג את הנתונים של הדוח לגבי חוויית המשתמש ב-Chrome ‏ (CrUX) כדי למלא את הפערים. ‫CrUX הוא מערך הנתונים של Google בתוכנית Core Web Vitals, והוא מספק סיכום ברמה גבוהה של מדדים למיליוני אתרים, כולל INP. עם זאת, לעיתים קרובות CrUX לא מספק את הנתונים לפי הקשר שאתם מקבלים מספק RUM, שיכולים לעזור לכם לנתח בעיות. לכן, אנחנו עדיין ממליצים לאתרים להשתמש בספק RUM כשאפשר, או להטמיע פתרון RUM משלהם כדי להשלים את מה שזמין ב-CrUX.

אבחון אינטראקציות איטיות במעבדה

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

אופטימיזציה של האינטראקציות

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

אפשר לחלק את האינטראקציות לשלושה חלקים:

  1. ההשהיה לאחר קלט, שמתחילה כשהמשתמש יוזם אינטראקציה עם הדף, ומסתיימת כשהקריאות החוזרות (callbacks) של האירוע לאינטראקציה מתחילות לפעול.
  2. משך העיבוד, שכולל את הזמן שלוקח להשלים את ההרצה של הקריאות החוזרות (callbacks) של האירועים.
  3. העיכוב בהצגה, שהוא משך הזמן שלוקח לדפדפן להציג את הפריים הבא שמכיל את התוצאה החזותית של האינטראקציה.
דוגמה לאינטראקציה בשרשור הראשי. המשתמש מבצע קלט בזמן שמשימות חסימה פועלות. הקלט מושהה עד שהמשימות האלה מסתיימות, ואז מופעלים הגורמים המטפלים באירועים pointerup,‏ mouseup ו-אירוע מסוג קליק. לאחר מכן מתחילות פעולות העיבוד והציור עד להצגת הפריים הבא.
מחזור החיים של אינטראקציה. השהיית קלט מתרחשת עד שהגורמים המטפלים באירועים מתחילים לפעול, ויכולה להיגרם מגורמים כמו משימות שנמשכות הרבה זמן ב-thread הראשי. לאחר מכן, פונקציות ה-callback של הגורם המטפל באירועים של האינטראקציה פועלות, ויש עיכוב לפני שהפריים הבא מוצג.

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

זיהוי וצמצום של השהיית הקלט

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

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

הקשר בין הערכת סקריפטים לבין משימות שנמשכות הרבה זמן במהלך ההפעלה

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

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

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

אופטימיזציה של קריאות חוזרות (callbacks) של אירועים

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

העבירו את השליטה ל-main thread לעיתים קרובות

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

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

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

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

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

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

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

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

  1. מעדכנים את תיבת הטקסט עם מה שהמשתמש הקליד ומחילים את העיצוב הנדרש.
  2. עדכון החלק בממשק המשתמש שבו מוצגת ספירת המילים הנוכחית.
  3. מפעילים לוגיקה כדי לבדוק אם יש שגיאות כתיב.
  4. שומרים את השינויים האחרונים (באופן מקומי או במסד נתונים מרוחק).

הקוד לביצוע הפעולה הזו עשוי להיראות בערך כך:

textBox.addEventListener('input', (inputEvent) => {
  // Update the UI immediately, so the changes the user made
  // are visible as soon as the next frame is presented.
  updateTextBox(inputEvent);

  // Use `setTimeout` to defer all other work until at least the next
  // frame by queuing a task in a `requestAnimationFrame()` callback.
  requestAnimationFrame(() => {
    setTimeout(() => {
      const text = textBox.textContent;
      updateWordCount(text);
      checkSpelling(text);
      saveChanges(text);
    }, 0);
  });
});

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

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

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

הימנעות משינויים תכופים בפריסה

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

המחשה של layout thrashing כפי שמוצג בחלונית הביצועים של כלי הפיתוח ל-Chrome.
דוגמה ל-layout thrashing, כפי שמוצג בחלונית הביצועים של כלי הפיתוח ל-Chrome. משימות רינדור שכוללות layout thrashing יסומנו במשולש אדום בפינה השמאלית העליונה של החלק של סטאק הביצוע, בדרך כלל עם התווית Recalculate Style או Layout.

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

צמצום העיכוב בהצגת התגובה

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

הקטנת גודל ה-DOM

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

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

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

שימוש ב-content-visibility כדי לעבד רכיבים מחוץ למסך בטעינה מדורגת (lazy loading)

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

חשוב לשים לב לעלויות הביצועים כשמעבדים HTML באמצעות JavaScript

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

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

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

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

סיכום

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

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