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

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

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

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

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

שימו לב, זה לא תמיד משפיע על האינטראקציות. אם לא תלחצו על המסך כשהמשימה פועלת, יכול להיות שיהיה לכם מזל. התעטשויות "אקראיות" כאלה יכולות להיות סיוט לניפוי באגים כשהן גורמות לבעיות רק לפעמים.
אחת מהדרכים לאתר את הבעיות האלה היא למדוד משימות שנמשכות הרבה זמן (או פריים אנימציה עם טעינה ארוכה (LoAF)) וסה"כ זמן חסימה (TBT).
9. הצגה איטית
עד עכשיו בדקנו את הביצועים של JavaScript, באמצעות השהיית קלט או רכיבי event listener, אבל מה עוד משפיע על עיבוד ה-paint הבא?
הוא מעדכן את הדף עם אפקטים יקרים.
גם אם עדכון הדף מתבצע במהירות, יכול להיות שהדפדפן עדיין יצטרך לעבוד קשה כדי לעבד אותם.
בשרשור הראשי:
- מסגרות של ממשק משתמש שצריכות לעבד עדכונים אחרי שינויים במצב
- שינויים ב-DOM או החלפה בין הרבה סלקטורים של שאילתות CSS שצורכים הרבה משאבים יכולים להפעיל הרבה סגנונות, פריסות וצביעות.
מחוץ ל-thread הראשי:
- שימוש ב-CSS כדי להפעיל אפקטים של GPU
- הוספת תמונות גדולות מאוד ברזולוציה גבוהה
- שימוש ב-SVG או ב-Canvas כדי לצייר סצנות מורכבות

דוגמאות נפוצות באינטרנט:
- אתר SPA שבונה מחדש את כל ה-DOM אחרי לחיצה על קישור, בלי להשהות כדי לספק משוב חזותי ראשוני.
- דף חיפוש שמציע מסנני חיפוש מורכבים עם ממשק משתמש דינמי, אבל מפעיל מאזינים יקרים כדי לעשות זאת.
- מתג למצב כהה שמפעיל סגנון או פריסה לכל הדף
10. ניסוי: עיכוב בהצגת התגובה
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 מבקש קריאה חוזרת (callback) לפני הציור הבא. מכיוון שה-INP מודד את הזמן מהאינטראקציה ועד להצגת התגובה, ה-blockFor(1000) ב-requestAnimationFrame ממשיך לחסום את הצגת התגובה הבאה למשך שנייה שלמה.

אבל שימו לב לשני דברים:
- כשמעבירים את העכבר מעל, רואים שכל מועד האינטראקציה מושקע עכשיו ב-presentation delay, כי החסימה של ה-main-thread מתרחשת אחרי שרכיב event listener מחזיר את התוצאה.
- השורש של הפעילות בשרשור הראשי הוא כבר לא אירוע מסוג קליק, אלא "Animation Frame Fired" (פריים האנימציה הופעל).
12. אבחון אינטראקציות
בדף הבדיקה הזה, התגובה היא מאוד ויזואלית, עם הניקוד, הטיימרים וממשק המשתמש של המונה...אבל כשבודקים דף ממוצע, זה יותר עדין.
כשהאינטראקציות נמשכות זמן רב, לא תמיד ברור מה הגורם לכך. האם זה:
- עיכוב בקלט?
- כמה זמן לוקח לעבד אירועים?
- עיכוב בהצגת התגובה?
אתם יכולים להשתמש בכלי הפיתוח בכל דף שתרצו כדי למדוד את מידת הרספונסיביות. כדי להתרגל לזה, נסו את התהליך הבא:
- גולשים באינטרנט כרגיל.
- כדאי לעקוב אחרי יומן האינטראקציות בתצוגת המדדים בזמן אמת בחלונית Performance בכלי הפיתוח.
- אם אתם רואים אינטראקציה עם ביצועים נמוכים, נסו לחזור עליה:
- אם אתם לא מצליחים לשחזר את הבעיה, אתם יכולים להשתמש ביומן האינטראקציות כדי לקבל תובנות.
- אם אתם יכולים לחזור על הפעולה, תעדו מעקב בחלונית הביצועים.
כל העיכובים
נסו להוסיף לדף קצת מכל הבעיות האלה:
הקוד המלא: all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
אחר כך משתמשים במסוף ובחלונית הביצועים כדי לאבחן את הבעיות.
13. ניסוי: עבודה אסינכרונית
אפשר להפעיל אפקטים לא חזותיים בתוך אינטראקציות, כמו שליחת בקשות לאחזור מהרשת, הפעלת טיימרים או עדכון המצב הגלובלי. מה קורה כשהפעולות האלה בסופו של דבר מעדכנות את הדף?
כל עוד מותר לעבד את הרינדור הבא אחרי אינטראקציה, גם אם הדפדפן מחליט שהוא לא באמת צריך עדכון רינדור חדש, מדידת האינטראקציה נפסקת.
כדי לנסות את זה, המשיכו לעדכן את ממשק המשתמש מרכיב ה-click 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 הראשי זמין מיד אחרי שממשק המשתמש מתעדכן. המשימה הארוכה שחוסמת את ה-UI עדיין פועלת, אבל היא פועלת זמן מה אחרי הרינדור, כך שהמשתמש יקבל משוב מיידי בממשק המשתמש.
הלקח: אם אי אפשר להסיר, לפחות אפשר להזיז.
Methods
האם אפשר לעשות משהו טוב יותר מ-setTimeout קבוע של 100 מילי-שניות? סביר להניח שאנחנו עדיין רוצים שהקוד יפעל כמה שיותר מהר, אחרת היינו פשוט מסירים אותו.
יעד:
- האינטראקציה תפעל
incrementAndUpdateUI(). - הפונקציה
blockFor()תפעל בהקדם האפשרי, אבל לא תחסום את הרינדור הבא. - כך אפשר לצפות את ההתנהגות בלי "זמנים קצובים לתפוגה".
כמה דרכים לעשות את זה:
setTimeout(0)Promise.then()requestAnimationFramerequestIdleCallbackscheduler.postTask()
"requestPostAnimationFrame"
בניגוד ל-requestAnimationFrame לבד (שינסה לפעול לפני הרינדור הבא ובדרך כלל עדיין יגרום לאינטראקציה איטית), requestAnimationFrame + setTimeout יוצר polyfill פשוט ל-requestPostAnimationFrame, שמפעיל את הקריאה החוזרת (callback) אחרי הרינדור הבא.
הקוד המלא: 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 הראשי נחסם למשך זמן משמעותי.

כשהמשימות שנמשכות הרבה זמן האלה חופפות לקליקים חדשים, האינטראקציות איטיות למרות שהרכיב event listener עצמו מחזיר תשובה כמעט מיד. יצרנו את אותו מצב כמו בניסוי הקודם עם עיכובים בקלט. אבל הפעם, העיכוב בקלט לא נובע מ-setInterval, אלא מעבודה שהופעלה על ידי רכיבי event listener קודמים.
שיטות
באופן אידיאלי, אנחנו רוצים להסיר לחלוטין משימות שנמשכות הרבה זמן.
- להסיר לגמרי קוד מיותר, במיוחד סקריפטים.
- לבצע אופטימיזציה של הקוד כדי להימנע מהרצת משימות שנמשכות הרבה זמן.
- לבטל עבודה לא רלוונטית כשמגיעות אינטראקציות חדשות.
16. אסטרטגיה 1: debounce
אסטרטגיה קלאסית. בכל פעם שאינטראקציות מגיעות ברצף מהיר, וההשפעות של העיבוד או הרשת הן יקרות, כדאי לעכב את תחילת העבודה בכוונה כדי שתוכלו לבטל ולהתחיל מחדש. התבנית הזו שימושית לממשקי משתמש כמו שדות של השלמה אוטומטית.
- אפשר להשתמש ב-
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 אלפיות השנייה. הדפדפן נותן עדיפות לרכיבי event listener הנכנסים על פני העבודה של 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);
});
כמו קודם, ה-main thread מניב אחרי חלק מהעבודה והדפדפן יכול להגיב לכל אינטראקציה נכנסת, אבל עכשיו כל מה שנדרש הוא 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 שמבצעת את כל העבודה שצריך לעשות, תוך כדי ויתור תקופתי על ה-thread הראשי כדי שהדפדפן ימשיך להגיב לאינטראקציות חדשות.
כשמגיע קליק שני, הלולאה הראשונה מסומנת כמבוטלת באמצעות AbortController ומתחילה לולאת blockInPiecesYieldyAborty חדשה. בפעם הבאה שהלולאה הראשונה מתוזמנת לפעול שוב, היא מזהה ש-signal.aborted הוא עכשיו true וחוזרת מיד בלי לבצע פעולות נוספות.

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