איך מבצעים אופטימיזציה של זמן האינטראקציה עד הצגת התגובה באתר
מהירות התגובה לאינטראקציה באתר (INP) הוא מדד יציב של המדדים הבסיסיים של חוויית המשתמש (Core Web Vitals) שמאפשר להעריך את מידת הרספונסיביות הכוללת של דף לאינטראקציות של משתמשים, על סמך משך זמן האחזור של כל האינטראקציות שעומדות בדרישות שמתרחשות במהלך משך החיים של ביקור משתמש בדף. ערך ה-INP הסופי הוא משך הזמן הארוך ביותר של אינטראקציה שזוהה (לפעמים ללא חריגים חשודים בטעות).
כדי לספק חוויית משתמש טובה, צריך לשאוף לכך שהזמן מאינטראקציה לציור הבא יהיה 200 אלפיות השנייה או פחות. כדי לוודא שאתם עומדים ביעד הזה עבור רוב המשתמשים, סף טוב למדידה הוא הפרמטר 75 במדד החמישיםון של טעינת הדפים, שמחולק לפי מכשירים ניידים ומחשבים.
בהתאם לאתר, יכול להיות שיהיה בו מעט אינטראקציות או שאין בו אינטראקציות בכלל – למשל, דפים שכוללים בעיקר טקסט ותמונות, עם מעט אלמנטים אינטראקטיביים או ללא אלמנטים כאלה. לחלופין, באתרים כמו כלי לעריכת טקסט או משחקים, יכולות להיות מאות ואפילו אלפי אינטראקציות. בכל מקרה שבו הערך של INP גבוה, חוויית המשתמש נמצאת בסיכון.
שיפור מדד INP דורש זמן ומאמץ, אבל התוצאה היא חוויית משתמש טובה יותר. במדריך הזה נסביר איך לשפר את INP.
איך בודקים מה גורם ל-INP נמוך
כדי לפתור בעיות של אינטראקציות איטיות, צריך נתונים שיאפשרו לכם לדעת אם מדד ה-INP של האתר שלכם נמוך או שצריך לשפר אותו. אחרי שתקבלו את המידע הזה, תוכלו לעבור למעבדה כדי להתחיל לאבחן אינטראקציות איטיות ולהגיע לפתרון.
איתור אינטראקציות איטיות בשטח
באופן אידיאלי, תהליך האופטימיזציה של מודעות ה-INP יתחיל בנתוני שדה. במקרה הטוב, נתוני שדה מספק של מעקב אחר משתמשים אמיתיים (RUM) יספקו לכם לא רק את ערך ה-INP של הדף, אלא גם נתוני הקשר שמציינים איזו אינטראקציה ספציפית אחראית לערך ה-INP עצמו, אם האינטראקציה התרחשה במהלך טעינת הדף או אחריה, סוג האינטראקציה (קליק, הקשה על מקש או הקשה) ומידע חשוב נוסף.
אם אתם לא מסתמכים על ספק RUM כדי לקבל נתוני שדה, במדריך לנתוני שדה ב-INP מומלץ להשתמש בדוח לגבי חוויית המשתמש ב-Chrome (CrUX) דרך PageSpeed Insights כדי למלא את הפערים. CrUX הוא מערך הנתונים הרשמי של תוכנית מדדי הליבה לבדיקת חוויית המשתמש באתר, והוא מספק סיכום ברמה גבוהה של מדדים לגבי מיליוני אתרים, כולל INP. עם זאת, בדרך כלל הנתונים הקשורים להקשר שספק RUM מספק לא זמינים ב-CrUX, ולכן הם לא עוזרים לנתח בעיות. לכן, אנחנו עדיין ממליצים לאתרים להשתמש בספק RUM כשהדבר אפשרי, או להטמיע פתרון RUM משלהם כדי להשלים את מה שזמין ב-CrUX.
אבחון אינטראקציות איטיות במעבדה
מומלץ להתחיל את הבדיקות ב-Labs אחרי שיש לכם נתונים מהשטח שמצביעים על אינטראקציות איטיות. אם אין נתונים מהשטח, יש כמה אסטרטגיות לזיהוי אינטראקציות איטיות ב-Labs. אסטרטגיות כאלה כוללות מעקב אחרי תהליכי שימוש נפוצים ובדיקת אינטראקציות לאורך הדרך, וגם אינטראקציה עם הדף במהלך הטעינה – כשהפעילות בשרשור הראשי היא לרוב הכי ערה – כדי לזהות אינטראקציות איטיות במהלך החלק הזה, הקריטי, בחוויית המשתמש.
ביצוע אופטימיזציה של אינטראקציות
אחרי שמזהים אינטראקציה איטית ומצליחים לשחזר אותה באופן ידני בסביבת ה-Lab, השלב הבא הוא לבצע אופטימיזציה שלה. האינטראקציות מחולקות לשלושה שלבים:
- הזמן שחלף מהקלט, שמתחיל כשהמשתמש מתחיל אינטראקציה עם הדף ומסתיים כשהקריאות החוזרות (callbacks) של האירוע לאינטראקציה מתחילות לפעול.
- משך העיבוד, שמכיל את הזמן שנדרש להרצה של פונקציות הקריאה החוזרת של האירועים עד להשלמה.
- הזמן שחולף מהצגת הבקשה להצגת המסך ועד להצגת המסך, שהוא הזמן שנדרש לדפדפן להציג את המסגרת הבאה שמכילה את התוצאה החזותית של האינטראקציה.
הסכום של שלושת השלבים האלה הוא זמן האחזור הכולל של האינטראקציה. כל שלב באינטראקציה תורם זמן מסוים לזמן האחזור הכולל של האינטראקציה, ולכן חשוב לדעת איך לבצע אופטימיזציה של כל חלק באינטראקציה כדי שהיא תימשך זמן קצר ככל האפשר.
זיהוי והפחתה של השהיה לאחר קלט
כשמשתמש יוצר אינטראקציה עם דף, החלק הראשון של האינטראקציה הזו הוא השהיית הקלט. בהתאם לפעילות אחרת בדף, עיכובי הקלט יכולים להיות ארוכים. הסיבה לכך יכולה להיות פעילות שמתרחשת ב-thread הראשי (אולי בגלל טעינת סקריפטים, ניתוח וקמפלור שלהם), טיפול באחזור, פונקציות של טיימר או אפילו אינטראקציות אחרות שמתרחשות ברצף מהיר וחופפות זו לזו.
לא משנה מה המקור של עיכוב הקלט של אינטראקציה, מומלץ לצמצם את עיכוב הקלט למינימום כדי שאפשר יהיה להתחיל להריץ קריאות חזרה (callbacks) של אירועים מהאינטראקציות בהקדם האפשרי.
הקשר בין הערכת הסקריפט לבין משימות ארוכות במהלך ההפעלה
אחד מהיבטים קריטיים של האינטראקטיביות במחזור החיים של הדף הוא בזמן ההפעלה. כשדף נטען, הוא עובר עיבוד ראשוני, אבל חשוב לזכור שגם אם הדף עבר עיבוד, לא בטוח שהדף הסתיים לטעינת. בהתאם למספר המשאבים הדרושים לדף כדי שיהיה פעיל במלואו, יכול להיות שמשתמשים ינסו לבצע אינטראקציה עם הדף בזמן שהוא עדיין נטען.
אחת מהסיבות שיכולות להאריך את עיכוב הקלט של אינטראקציה בזמן טעינת דף היא הערכת סקריפט. אחרי שמאחזרים קובץ JavaScript מהרשת, עדיין יש ל-JavaScript עבודה לעשות לפני שהוא יכול לפעול. העבודה הזו כוללת ניתוח סקריפט כדי לוודא שהסינטקס שלו תקין, הידור שלו לקוד בייט ואז הפעלה שלו.
בהתאם לגודל הסקריפט, הפעולה הזו עלולה להוסיף משימות ארוכות לשרשור הראשי, וכתוצאה מכך הדפדפן יתעכב בתגובה לאינטראקציות אחרות של משתמשים. כדי שהדף ימשיך להגיב לקלט של המשתמשים במהלך טעינת הדף, חשוב להבין מה אפשר לעשות כדי לצמצם את הסבירות למשימות ארוכות במהלך טעינת הדף, וכך לשמור על מהירות הדף.
אופטימיזציה של קריאות חוזרות (callbacks) של אירועים
השהיה לאחר קלט היא רק החלק הראשון של המדד INP. בנוסף, צריך לוודא שהקריאות החוזרות (callbacks) של האירועים שפועלות בתגובה לאינטראקציה של משתמש יכולות להסתיים מהר ככל האפשר.
להעביר את הבעלות ל-thread הראשי לעיתים קרובות
העצה הכללית הטובה ביותר לביצוע אופטימיזציה של קריאות חזרה (callbacks) של אירועים היא לבצע בהן כמה שפחות עבודה. עם זאת, לוגיקת האינטראקציה עשויה להיות מורכבת, ויכול להיות שתוכלו לצמצם את העבודה שהם מבצעים רק במידה קטנה.
אם גיליתם שהמצב הזה רלוונטי לאתר שלכם, הדבר הבא שתוכלו לנסות הוא לפצל את העבודה בקריאות חזרה (callbacks) של אירועים למשימות נפרדות. כך העבודה המשותפת לא הופכת למשימה ארוכה שגורמת לחסימה של ה-thread הראשי, ומאפשרת לבצע אינטראקציות אחרות שבלעדיהן היו צריכות להמתין ל-thread הראשי.
setTimeout
היא אחת הדרכים לפצל משימות, כי פונקציית ה-callback שהועברה אליה פועלת במשימה חדשה. אפשר להשתמש ב-setTimeout
בפני עצמה או להעביר את השימוש בה לפונקציה נפרדת כדי לקבל תוצאות ארגונומיות יותר.
העברה ללא הבחנה היא טובה יותר מאשר לא להעביר בכלל – עם זאת, יש דרך מורכבת יותר להעביר את השליטה לשרשור הראשי, והיא כוללת העברה רק מיד אחרי קריאה חוזרת (callback) של אירוע שמעדכנת את ממשק המשתמש, כדי שהלוגיקה של הרינדור תוכל לפעול מוקדם יותר.
מניעת עיבוד כדי לאפשר לעיבוד להתבצע מוקדם יותר
שיטה מתקדמת יותר של העברת השליטה היא מבוססת על בניית הקוד בקריאות החזרה (callbacks) של האירועים, כדי להגביל את מה שיופעל רק ללוגיקה הנדרשת כדי להחיל עדכונים חזותיים על המסגרת הבאה. את כל שאר הדברים אפשר לדחות למשימה הבאה. כך לא רק שהקריאות החוזרות יהיו קלות וגמישות, אלא גם זמן הרינדור של האינטראקציות ישתפר, כי לא תתאפשר חסימה של קוד הקריאה החוזרת של האירוע על ידי עדכונים חזותיים.
לדוגמה, נניח שיש כלי לעריכת טקסט עשיר שמעצב את הטקסט בזמן ההקלדה, אבל גם מעדכן היבטים אחרים של ממשק המשתמש בתגובה למה שכתבתם (כמו מספר המילים, הדגשת שגיאות איות ומשוב חזותי חשוב אחר). בנוסף, יכול להיות שהאפליקציה תצטרך לשמור את מה שכתבתם כדי שאם תצאו ותחזרו, לא תאבדו את העבודה.
בדוגמה הזו, ארבעת הדברים הבאים צריכים לקרות בתגובה לתווים שהמשתמש הקליד. עם זאת, רק הפריט הראשון צריך להתבצע לפני הצגת המסגרת הבאה.
- מעדכנים את תיבת הטקסט במה שהמשתמש הקליד ומחילים את העיצוב הנדרש.
- מעדכנים את החלק של ממשק המשתמש שבו מוצג מספר המילים הנוכחי.
- מריצים לוגיקה כדי לבדוק אם יש שגיאות איות.
- שומרים את השינויים האחרונים (מקומית או במסד נתונים מרוחק).
הקוד שצריך להזין כדי לעשות זאת עשוי להיראות כך:
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);
});
});
בתרשים ההמחשה הבא אפשר לראות איך דחיית עדכונים לא קריטיים עד אחרי המסגרת הבאה יכולה לקצר את משך העיבוד, וכך את זמן האחזור הכולל של האינטראקציה.
השימוש ב-setTimeout()
בתוך קריאה ל-requestAnimationFrame()
בדוגמת הקוד הקודמת הוא אמנם קצת מסתורי, אבל זוהי שיטה יעילה שפועלת בכל הדפדפנים כדי לוודא שהקוד שאינו קריטי לא חוסם את המסגרת הבאה.
הימנעות מטרחה מיותרת בפריסה
עומס פריסות של פריסות, שלפעמים נקרא פריסה מסונכרנת מאולצת, הוא בעיה בביצועי העיבוד, שבה הפריסה מתרחשת באופן סינכרוני. הוא מתרחש כשמעדכנים סגנונות ב-JavaScript ולאחר מכן קוראים אותם באותה משימה – ויש הרבה מאפיינים ב-JavaScript שיכולים לגרום לטרחה בפריסת האתר.
'טראשינג של פריסה' הוא צוואר בקבוק בביצועים, כי כשמעדכנים סגנונות ומבקשים מיד את הערכים של אותם סגנונות ב-JavaScript, הדפדפן נאלץ לבצע עבודה סינכרנית של פריסה, שאפשר היה לבצע באופן אסינכרוני מאוחר יותר אחרי שהפעלת ה-callbacks של האירועים תסתיים.
צמצום ההשהיה בהצגת התגובה
העיכוב בהצגה של סימני אינטראקציה מתחיל מהרגע שבו פונקציות ה-call back של האירועים של האינטראקציה מסתיימות לפעול, ועד לנקודה שבה הדפדפן יכול לצייר את המסגרת הבאה שמוצגים בה השינויים החזותיים.
צמצום גודל ה-DOM
כשה-DOM של דף קטן, עיבוד הגרפיקה בדרך כלל מסתיים במהירות. עם זאת, כשה-DOMs גדלים מאוד, עבודת העיבוד נוטה להתרחב ככל שה-DOM נעשה גדול יותר. הקשר בין עבודת העיבוד לבין גודל ה-DOM הוא לא לינארי, אבל עיבוד DOM גדול דורש יותר עבודה מאשר עיבוד DOM קטן. DOM גדול הוא בעייתי בשני מקרים:
- במהלך העיבוד הראשוני של הדף, שבו DOM גדול דורש הרבה עבודה כדי לעבד את המצב הראשוני של הדף.
- בתגובה לאינטראקציה של משתמש, שבה 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.