1. מבוא
הדגמה אינטראקטיבית ו-codelab ללימוד על מהירות התגובה לאינטראקציה באתר (INP).
דרישות מוקדמות
- ידע בפיתוח HTML ו-JavaScript.
- מומלץ: קוראים את המסמכים בנושא INP.
מה לומדים
- איך האינטראקציות של המשתמשים והטיפול שלכם באינטראקציות האלה משפיעים על רמת הרספונסיביות של הדף.
- איך אפשר לצמצם את העיכובים ולמנוע אותם כדי לשפר את חוויית המשתמש.
מה צריך
- מחשב עם אפשרות לשכפל קוד מ-GitHub ולהריץ פקודות npm.
- כלי לעריכת טקסט.
- גרסה עדכנית של Chrome כדי שכל המדידות של האינטראקציות יפעלו.
2. להגדרה
קבלת הקוד והרצתו
הקוד נמצא במאגר web-vitals-codelabs.
- משכפלים את המאגר בטרמינל:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git - עוברים לספרייה המשוכפלת:
cd web-vitals-codelabs/understanding-inp - מתקינים את שאר הספריות הדרושות לצורך יצירת ספריות הלקוח:
npm ci - מפעילים את שרת האינטרנט:
npm run start - עוברים לכתובת http://localhost:5173/understanding-inp/ בדפדפן
סקירה כללית של האפליקציה
בחלק העליון של הדף נמצא מונה הניקוד וכפתור ההגדלה. הדגמה קלאסית של תגובתיות ורספונסיביות!

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

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

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

כשמעבירים את העכבר מעל האינטראקציה, אפשר לראות שהאינטראקציה הייתה מהירה, שלא היה זמן שהוקדש למשך העיבוד, ושהוקדש זמן מינימלי להשהיית הקלט ולהשהיית ההצגה. משך הזמן המדויק תלוי במהירות המחשב.
4. פונקציות event listener שפועלות לאורך זמן
פותחים את הקובץ index.js ומבטלים את ההערה של הפונקציה blockFor בתוך event listener.
הקוד המלא: click_block.html
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
});
שומרים את הקובץ. השרת יזהה את השינוי וירענן את הדף בשבילכם.
מנסים שוב ליצור אינטראקציה עם הדף. האינטראקציות יהיו איטיות יותר באופן משמעותי.
נתוני מעקב אחר ביצועים
כדי לראות איך זה נראה שם, אפשר להקליט הקלטה נוספת בחלונית Performance (ביצועים).

מה שהיה פעם אינטראקציה קצרה נמשך עכשיו שנייה שלמה.
כשמעבירים את העכבר מעל האינטראקציה, אפשר לראות שרוב הזמן מושקע ב'משך העיבוד', שהוא משך הזמן שנדרש להרצת קריאות חוזרות (callback) של event listener. הקריאה החוסמת blockFor מתבצעת כולה בתוך הפונקציה event listener, ולכן הזמן מושקע שם.
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? לדוגמה, להחליף את אחת מפונקציות click event listener בפונקציה 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);
});
אם תצפו בהקלטה של חלונית הביצועים של לחיצה על הכפתור, תוכלו לראות שהתוצאות לא השתנו. למרות שהפעלנו עדכון של ממשק המשתמש לפני קוד החסימה, הדפדפן לא עדכן בפועל את מה שמוצג במסך עד שה-event listener סיים את הפעולה. כלומר, האינטראקציה עדיין נמשכה קצת יותר משנייה.

יומן למעקב ביצועים: listeners נפרדים
הקוד המלא: 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 החסימה.

מעקב אחר ביצועים: אין עדכון בממשק המשתמש
הקוד המלא: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
- הציון לא מתעדכן, אבל הדף כן.
- אנימציות, אפקטים של CSS, פעולות ברירת מחדל של רכיבי אינטרנט (קלט של טופס), הזנת טקסט והדגשת טקסט ממשיכים להתעדכן.
במקרה כזה, כשלוחצים על הלחצן הוא עובר למצב פעיל ואז חוזר למצב לא פעיל. כדי לעשות את זה, הדפדפן צריך לצייר אותו, ולכן עדיין יש INP.
מאחר שה-event listener חסם את ה-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 של החסימה פועלת, נוצרת אינטראקציה ארוכה, גם אם לא מתבצעת עבודת חסימה באינטראקציה עצמה.
התקופות הארוכות האלה נקראות לעיתים קרובות משימות ארוכות.
אם מעבירים את העכבר מעל האינטראקציה בכלי הפיתוח, אפשר לראות שזמן האינטראקציה משויך עכשיו בעיקר לעיכוב בקלט, ולא למשך העיבוד.

שימו לב שההגדרה הזו לא משפיעה על האינטראקציות תמיד. אם לא תלחצו בזמן שהמשימה פועלת, יכול להיות שתצליחו. קשה מאוד לאתר את מקור הבעיה של שיעולים או עיטושים 'אקראיים' כאלה, במיוחד אם הם גורמים לבעיות רק לפעמים.
אחת הדרכים לאתר את הבעיות האלה היא למדוד משימות ארוכות (או Long Animation Frames) וTotal Blocking Time.
9. הצגה איטית
עד עכשיו בדקנו את הביצועים של JavaScript באמצעות השהיית קלט או מאזיני אירועים, אבל מה עוד משפיע על הרינדור של הצביעה הבאה?
ובכן, לעדכן את הדף עם אפקטים יקרים!
גם אם עדכון הדף מגיע במהירות, יכול להיות שהדפדפן עדיין יצטרך לעבוד קשה כדי לבצע את הרינדור.
ב-thread הראשי:
- מסגרות ממשק משתמש שצריכות לעבד עדכונים אחרי שינויים במצב
- שינויים ב-DOM או החלפה בין הרבה סלקטורים יקרים של שאילתות CSS יכולים להפעיל הרבה פעולות של Style, Layout ו-Paint.
מחוץ ל-thread הראשי:
- שימוש ב-CSS להפעלת אפקטים של GPU
- הוספת תמונות גדולות מאוד ברזולוציה גבוהה
- שימוש ב-SVG/Canvas כדי לצייר סצנות מורכבות

דוגמאות נפוצות באינטרנט:
- אתר SPA שבו מתבצעת בנייה מחדש של כל ה-DOM אחרי לחיצה על קישור, בלי להשהות את הפעולה כדי לספק משוב חזותי ראשוני.
- דף חיפוש שמציע מסנני חיפוש מורכבים עם ממשק משתמש דינמי, אבל מפעיל מאזינים יקרים כדי לעשות זאת.
- מתג למצב כהה שמפעיל סגנון או פריסה לכל הדף
10. Experiment: presentation delay
requestAnimationFrame איטי
נניח שרוצים לדמות עיכוב ארוך בהצגת המודעות באמצעות requestAnimationFrame() API.
מעבירים את הקריאה blockFor ל-callback של requestAnimationFrame כדי שהיא תפעל אחרי שהפונקציה 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. אבחון אינטראקציות
בדף הבדיקה הזה, הרספונסיביות מאוד ויזואלית, עם הציונים, הטיימרים וממשק המשתמש של המונה...אבל כשבודקים את הדף הממוצע, זה יותר עדין.
כשאינטראקציות נמשכות זמן רב, לא תמיד ברור מה הגורם לכך. האם זה:
- השהיה לאחר קלט?
- מה משך העיבוד של האירוע?
- השהיה של הצגת תגובה?
אתם יכולים להשתמש בכלי הפיתוח בכל דף שתרצו כדי למדוד את הרספונסיביות. כדי להתרגל, אפשר לנסות את התהליך הבא:
- גולשים באינטרנט כרגיל.
- חשוב לעקוב אחרי יומן האינטראקציות בתצוגת המדדים בזמן אמת בחלונית 'ביצועים' בכלי הפיתוח.
- אם אתם רואים אינטראקציה עם ביצועים נמוכים, נסו לחזור עליה:
- אם לא הצלחתם לשחזר את הפעולה, תוכלו להשתמש ביומן האינטראקציות כדי לקבל תובנות.
- אם אתם מצליחים לשחזר את הבעיה, הקליטו תיעוד בחלונית Performance (ביצועים).
כל העיכובים
כדאי לנסות להוסיף לדף קצת מכל הבעיות האלה:
הקוד המלא: all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
אחרי זה, אפשר להשתמש במסוף ובחלונית הביצועים כדי לאבחן את הבעיות.
13. ניסוי: עבודה אסינכרונית
מכיוון שאפשר להפעיל אפקטים לא חזותיים במהלך אינטראקציות, כמו שליחת בקשות לרשת, הפעלת טיימרים או עדכון של מצב גלובלי, מה קורה כשהאפקטים האלה מעדכנים את הדף בסופו של דבר?
כל עוד מותר לבצע רינדור של הצגת התגובה אחרי אינטראקציה, גם אם הדפדפן מחליט שלא צריך עדכון רינדור חדש, המדידה של האינטראקציה נפסקת.
כדי לנסות את זה, ממשיכים לעדכן את ממשק המשתמש מ-listener, אבל מריצים את העבודה שחוסמת את התהליך מ-זמן קצוב לתפוגה.
הקוד המלא: 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);
});

האינטראקציה קצרה עכשיו כי ה-thread הראשי זמין מיד אחרי שממשק המשתמש מתעדכן. המשימה הארוכה שחוסמת את התהליך עדיין פועלת, אבל היא פועלת אחרי הצביעה, כך שהמשתמש יקבל משוב מיידי בממשק המשתמש.
לקח: אם אי אפשר להסיר את הפריט, לפחות כדאי להזיז אותו!
Methods
האם אפשר להשיג תוצאה טובה יותר מ-100 מילי-שניות קבועות setTimeout? סביר להניח שאנחנו עדיין רוצים שהקוד יפעל מהר ככל האפשר, אחרת היינו פשוט מסירים אותו!
יעד:
- האינטראקציה תפעל
incrementAndUpdateUI(). - הפונקציה
blockFor()תופעל בהקדם האפשרי, אבל לא תחסום את הציור הבא. - כך מתקבלת התנהגות צפויה ללא "זמני קצובים לתפוגה קסומים".
כדי לעשות את זה, אפשר:
setTimeout(0)Promise.then()requestAnimationFramerequestIdleCallbackscheduler.postTask()
"requestPostAnimationFrame"
בניגוד ל-requestAnimationFrame לבד (שמנסה לפעול לפני הצביעה הבאה ובדרך כלל עדיין גורם לאינטראקציה איטית), requestAnimationFrame + setTimeout יוצר פוליפיל פשוט ל-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 הראשי נחסם למשך זמן משמעותי.

כשהמשימות הארוכות האלה חופפות לקליקים חדשים שנכנסים, האינטראקציות איטיות, למרות שהמאזין לאירועים מחזיר כמעט מיידית. יצרנו את אותו מצב כמו בניסוי הקודם עם עיכובים בקלט. רק שהפעם, עיכוב הקלט לא מגיע מ-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 עד אחרי הצביעה הבאה.
- חשוב לוודא שעדכון העיבוד עצמו יעיל לדפדפן.