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

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

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

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

ערכים טובים של INP הם 200 אלפיות שנייה או פחות, ערכים גרועים הם יותר מ-500 אלפיות שנייה, וכל ערך בטווח שביניהם טעון שיפור.

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

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

איך בודקים מה גורם ל-INP נמוך

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

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

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

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

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

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

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

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

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

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

זיהוי וצמצום של עיכוב קלט

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  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);
  });
});

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

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

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

הימנעות מטרחה מיותרת בפריסה

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

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

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

צמצום ההשהיה בהצגת התגובה

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

צמצום גודל ה-DOM

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

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

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

שימוש ב-content-visibility כדי ליצור עיבוד גרפי (render) עצל של רכיבים מחוץ למסך

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

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

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

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

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

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

סיכום

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

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

התמונה הראשית (Hero) מ-Unsplash, מאת David Pisnoy ושונה בהתאם לרישיון Unsplash.