הסבר על האינטראקציה עד השלב הבא (INP)

הסבר על המדד 'מהירות התגובה לאינטראקציה באתר (INP)'

מידע על Codelab זה

subjectהעדכון האחרון: ינו׳ 9, 2025
account_circleנכתב על ידי Michal Mocny, Brendan Kenny

1.‏ מבוא

הדגמה אינטראקטיבית ו-codelab ללימוד על מהירות התגובה לאינטראקציה באתר (INP).

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

דרישות מוקדמות

מה לומדים

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

מה צריך

  • מחשב עם אפשרות לשכפל קוד מ-GitHub ולהריץ פקודות npm.
  • כלי לעריכת טקסט.
  • גרסה עדכנית של Chrome כדי שכל המדידות של האינטראקציות יפעלו.

2.‏ להגדרה

קבלת הקוד והרצתו

הקוד נמצא במאגר web-vitals-codelabs.

  1. משכפלים את המאגר בטרמינל: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. עוברים לספרייה המשוכפלת: cd web-vitals-codelabs/understanding-inp
  3. מתקינים את שאר הספריות הדרושות לצורך יצירת ספריות הלקוח: npm ci
  4. מפעילים את שרת האינטרנט: npm run start
  5. עוברים לכתובת http://localhost:5173/understanding-inp/ בדפדפן

סקירה כללית של האפליקציה

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

צילום מסך של אפליקציית ההדגמה של ה-Codelab הזה

מתחת ללחצן יש ארבעה מדדים:

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

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

רוצה לנסות?

נסו ללחוץ על הלחצן הוספה ולראות את הניקוד עולה. האם הערכים של INP ושל Interaction משתנים עם כל עלייה?

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

3.‏ מדידת אינטראקציות באמצעות כלי הפיתוח ל-Chrome

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

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

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

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

  1. לוחצים על 'הקלטה'.
  2. מבצעים אינטראקציה עם הדף (לוחצים על הלחצן Increment).
  3. מפסיקים את ההקלטה.

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

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

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

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

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

4.‏ פונקציות event listener שפועלות לאורך זמן

פותחים את הקובץ index.js ומבטלים את ההערה של הפונקציה blockFor בתוך מאזין האירועים.

הקוד המלא: click_block.html

button.addEventListener('click', () => {
  blockFor
(1000);
  score
.incrementAndUpdateUI();
});

שומרים את הקובץ. השרת יזהה את השינוי וירענן את הדף בשבילכם.

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

יומן למעקב ביצועים

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

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

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

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

5.‏ Experiment: processing duration

כדאי לנסות לשנות את סדר הפעולות של event-listener כדי לראות את ההשפעה על INP.

קודם צריך לעדכן את ממשק המשתמש

מה קורה אם מחליפים את הסדר של קריאות ה-JS – מעדכנים קודם את ממשק המשתמש ואז חוסמים?

הקוד המלא: ui_first.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  blockFor
(1000);
});

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

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

מאזינים נפרדים

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

הקוד המלא: two_click.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('click', () => {
  blockFor
(1000);
});

איך זה נראה עכשיו בחלונית הביצועים?

סוגים שונים של אירועים

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

להרבה דפים אמיתיים יש listeners להרבה אירועים שונים.

מה קורה אם משנים את סוגי האירועים של פונקציות event listener? לדוגמה, להחליף את אחת הפונקציות event listener של click בפונקציה pointerup או mouseup?

הקוד המלא: diff_handlers.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('pointerup', () => {
  blockFor
(1000);
});

אין עדכון בממשק המשתמש

מה קורה אם מסירים את הקריאה לעדכון ממשק המשתמש מ-event listener?

הקוד המלא: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});

6.‏ תוצאות הניסוי של משך העיבוד

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

הקוד המלא: ui_first.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  blockFor
(1000);
});

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

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

יומן למעקב ביצועים: מאזינים נפרדים

הקוד המלא: two_click.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('click', () => {
  blockFor
(1000);
});

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

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

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

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

מעקב אחר ביצועים: סוגים שונים של אירועים

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('pointerup', () => {
  blockFor
(1000);
});

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

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

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

הקוד המלא: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});
  • הציון לא מתעדכן, אבל הדף כן.
  • אנימציות, אפקטים של CSS, פעולות ברירת מחדל של רכיבי אינטרנט (קלט טופס), הזנת טקסט והדגשת טקסט ממשיכים להתעדכן.

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

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

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

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

טייק אוויי

כל קוד שפועל ב-event listener כלשהו יעכב את האינטראקציה.

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

זו בעיה נפוצה!

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

7.‏ Experiment: input delay

מה לגבי קוד שפועל לאורך זמן מחוץ לפונקציות event listener? לדוגמה:

  • אם יש לכם <script> שנטען מאוחר וחוסם את הדף באופן אקראי במהלך הטעינה.
  • קריאה ל-API, כמו setInterval, שחוסמת את הדף באופן תקופתי?

כדאי לנסות להסיר את blockFor מה-event listener ולהוסיף אותו ל-setInterval():

הקוד המלא: input_delay.html

setInterval(() => {
  blockFor
(1000);
}, 3000);


button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

מה קורה

8.‏ תוצאות הניסוי של השהיה לאחר קלט

הקוד המלא: input_delay.html

setInterval(() => {
  blockFor
(1000);
}, 3000);


button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

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

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

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

החלונית &#39;ביצועים&#39; בכלי הפיתוח מציגה משימת חסימה של שנייה אחת, אינטראקציה שמתרחשת באמצע המשימה ואינטראקציה של 642 אלפיות שנייה, שרובה משויכת לעיכוב קלט

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

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

9.‏ הצגה איטית

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

ובכן, לעדכן את הדף עם אפקטים יקרים!

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

ב-thread הראשי:

  • מסגרות ממשק משתמש שצריכות לעבד עדכונים אחרי שינויים במצב
  • שינויים ב-DOM או החלפה בין הרבה סלקטורים יקרים של שאילתות CSS יכולים להפעיל הרבה פעולות של Style,‏ Layout ו-Paint.

מחוץ ל-thread הראשי:

  • שימוש ב-CSS להפעלת אפקטים של GPU
  • הוספת תמונות גדולות מאוד ברזולוציה גבוהה
  • שימוש ב-SVG/Canvas כדי לצייר סצנות מורכבות

סקיצה של האלמנטים השונים של רינדור באינטרנט

RenderingNG

דוגמאות נפוצות שמצאנו באינטרנט:

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

10.‏ Experiment: presentation delay

requestAnimationFrame איטי

נניח שרוצים לדמות עיכוב ארוך בהצגת המודעה באמצעות requestAnimationFrame() API.

מעבירים את הקריאה blockFor ל-requestAnimationFrame callback כדי שהיא תפעל אחרי שהפונקציה event listener תחזיר ערך:

הקוד המלא: presentation_delay.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

מה קורה

11.‏ תוצאות הניסוי בנושא עיכוב בהצגת התגובה

הקוד המלא: presentation_delay.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

האינטראקציה נמשכת שנייה אחת, אז מה קרה?

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

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

עם זאת, חשוב לשים לב לשני דברים:

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

12.‏ אבחון אינטראקציות

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

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

  • השהיה לאחר קלט?
  • מה משך העיבוד של האירוע?
  • השהיה של הצגת תגובה?

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

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

כל העיכובים

כדאי לנסות להוסיף לדף קצת מכל הבעיות האלה:

הקוד המלא: all_the_things.html

setInterval(() => {
  blockFor
(1000);
}, 3000);

button
.addEventListener('click', () => {
  blockFor
(1000);
  score
.incrementAndUpdateUI();

  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

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

13.‏ ניסוי: עבודה אסינכרונית

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

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

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

הקוד המלא: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

מה קורה עכשיו?

14.‏ תוצאות הניסוי של עבודה אסינכרונית

הקוד המלא: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

אינטראקציה של 27 אלפיות השנייה עם משימה באורך שנייה אחת שמתרחשת עכשיו מאוחר יותר ב-trace

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

לקח: אם אי אפשר להסיר את הפריט, לפחות צריך להזיז אותו!

Methods

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

יעד:

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

כמה דרכים לעשות את זה:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

‪"requestPostAnimationFrame"

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

הקוד המלא: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame
(() => {
    setTimeout
(callback, 0);
 
});
}

button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  afterNextPaint
(() => {
    blockFor
(1000);
 
});
});

כדי לשפר את הארגונומיה, אפשר אפילו להשתמש ב-Promise:

הקוד המלא: raf+task2.html

async function nextPaint() {
 
return new Promise(resolve => afterNextPaint(resolve));
}

button
.addEventListener('click', async () => {
  score
.incrementAndUpdateUI();

  await nextPaint
();
  blockFor
(1000);
});

15.‏ כמה אינטראקציות (וקליקים זועמים)

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

מנסים שוב את הגרסה של הדף עם חסימה אסינכרונית (או גרסה משלכם אם יצרתם וריאציה משלכם לדחיית עבודה בשלב האחרון):

הקוד המלא: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

מה קורה אם לוחצים כמה פעמים במהירות?

יומן למעקב ביצועים

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

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

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

שיטות

האידיאל הוא להסיר לחלוטין משימות ארוכות.

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

16.‏ אסטרטגיה 1: ביטול כפילויות

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

  • אפשר להשתמש ב-setTimeout כדי לדחות את ההתחלה של עבודה יקרה, עם טיימר, אולי 500 עד 1,000 אלפיות שנייה.
  • כשעושים את זה, שומרים את מזהה הטיימר.
  • אם מגיעה אינטראקציה חדשה, מבטלים את הטיימר הקודם באמצעות clearTimeout.

הקוד המלא: debounce.html

let timer;
button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

 
if (timer) {
    clearTimeout
(timer);
 
}
  timer
= setTimeout(() => {
    blockFor
(1000);
 
}, 1000);
});

יומן למעקב ביצועים

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

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

17.‏ אסטרטגיה 2: הפרעה לעבודה שמתבצעת במשך זמן רב

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

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

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

המון setTimeout

ניסיון ראשון: עושים משהו פשוט.

הקוד המלא: small_tasks.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  requestAnimationFrame
(() => {
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
 
});
});

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

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

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

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

עם זאת, השיטה הזו לא מתאימה לפיצול קוד עם צימוד הדוק, כמו לולאת for שמשתמשת במצב משותף.

עכשיו עם yield()

עם זאת, אפשר להשתמש ב-async וawait מודרניים כדי להוסיף בקלות 'נקודות החזרה' לכל פונקציית JavaScript.

לדוגמה:

הקוד המלא: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

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

עכשיו עם AbortContoller()

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

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

הקוד המלא: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

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

כשמתקבלת לחיצה שנייה, הלולאה הראשונה מסומנת כבוטלה עם הערך AbortController ומתחילה לולאה חדשה blockInPiecesYieldyAborty – בפעם הבאה שהלולאה הראשונה מתוזמנת לפעול שוב, היא מבחינה בכך שהערך signal.aborted הוא עכשיו true וחוזרת מיד בלי לבצע עבודה נוספת.

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

18.‏ סיכום

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

חשוב לזכור

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

שיטות

  • אל תשתמשו בקוד שפועל לאורך זמן (משימות ארוכות) בדפים שלכם.
  • להעביר קוד מיותר מתוך פונקציות event listener עד אחרי הצביעה הבאה.
  • מוודאים שעדכון העיבוד עצמו יעיל לדפדפן.

מידע נוסף