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

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

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

מהי השהיה לאחר קלט?

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

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

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

איך חושבים על השהיה לאחר קלט

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

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

איך מצמצמים את השהיית הקלט

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

מומלץ להימנע מטיימרים חוזרים שמפעילים עבודה מוגזמת ב-thread הראשי

יש שתי פונקציות נפוצות של טיימר ב-JavaScript שיכולות לתרום לעיכוב בקלט: setTimeout ו-setInterval. ההבדל בין שתי הפונקציות הוא ש-setTimeout מתזמנת קריאה חוזרת להפעלה אחרי פרק זמן מוגדר. לעומת זאת, הפונקציה setInterval מתזמנת קריאה חוזרת להפעלה כל n אלפיות השנייה ללא הגבלת זמן, או עד שהטיימר מופסק באמצעות clearInterval.

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

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

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

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

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

הימנעות ממשימות ארוכות

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

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

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

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

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

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

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

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

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

סיכום

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

תמונה ראשית (Hero) מ-Unsplash, מאת Erik Mclean.