אירועי קלט תואמים

דייב טפוסקה
דייב טפוסקה

אמ;לק

  • דפדפן Chrome 60 מפחית את קצב ה-jank על ידי הפחתת תדירות האירועים, וכך משפר את העקביות של תזמון הפריימים.
  • השיטה getCoalescedEvents() שהושקה ב-Chrome 58 מספקת את אותו מידע על אירועים שהיה לכם אי פעם.

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

כדי שחוויית המשתמש תהיה חלקה וביצועים, ב-Chrome 60 אנחנו מבצעים שינוי שיגרום לאירועים האלה להתרחש בתדירות נמוכה יותר ובמקביל להגדיל את רמת הפירוט של המידע שמוצג. בדומה לכך, לאחר שפרסמה את Jelly Bean והוציאה את הכוריאוגרף ב-Android, אנחנו משיקים באינטרנט קלט מותאם למסגרות בכל הפלטפורמות.

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

בואו נדבר קודם על תדירות אירועים.

הפחתה של תדירות האירועים

נבין כמה דברים בסיסיים: מסכי מגע מספקים קלט של 60-120Hz ועכברים מספקים קלט בדרך כלל ב-100Hz (אבל יכולים להיות בכל מקום עד 2,000Hz). עם זאת, קצב הרענון האופייני למסך הוא 60Hz. אז מה זה אומר בעצם? המשמעות היא שאנחנו מקבלים קלט בקצב גבוה יותר מזה שאנחנו מעדכנים את התצוגה בפועל. בואו נבחן ציר זמן של הביצועים מכלי פיתוח עבור אפליקציה פשוטה לציור על קנבס.

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

ציר זמן של ביצועים שבו מוצג תזמון פריימים לא עקבי

אז למה אנחנו עושים מאמצים נוספים שלא גורמים לעדכונים חזותיים? במצב אידיאלי, אנחנו לא רוצים לבצע פעולות שלא יועילו למשתמש בסופו של דבר. החל מגרסה Chrome 60, צינור עיבוד הנתונים יעכב את השליחה של אירועים רציפים (wheel, mousewheel, touchmove, pointermove, mousemove) וישלח אותם מיד לפני ביצוע requestAnimationFrame()הקריאה החוזרת. בתמונה למטה (כשהתכונה מופעלת), זמן רינדור פריים עקבי יותר ופחות זמן לעיבוד של אירועים.

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

הערה חשובה שמפתחי אתרים צריכים לדעת שכל אירוע נפרד (כמו keydown, keyup, mouseup, mousedown, touchstart, touchend) שמתרחש יישלח מיד יחד עם אירועים בהמתנה, תוך שמירה על הסדר היחסי. כשמפעילים את התכונה הזו, חלק גדול מהעבודה מפשט את לולאת האירועים הרגילה, וכך מתקבל מרווח קלט עקבי. כך אירועים רציפים מסודרים באירועי scroll ו-resize שכבר הותקנו בתהליך לולאת האירועים ב-Chrome.

ציר זמן לביצועים שמציג תזמון פריימים עקבי יחסית.

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

השיטה getCoalescedEvents()

כמו שאמרתי, יש תרחישים נדירים שבהם האפליקציה מעדיפה לדעת מה הנתיב המלא של הסמן. כדי לתקן את המקרים שבהם יש עליות גדולות ותדירות מופחתת של אירועים, ב-Chrome 58 השקנו תוסף בשם getCoalescedEvents() לאירועי מצביע. בהמשך מוצגת דוגמה לאופן שבו ה-jank ב-thread הראשי מוסתר מהאפליקציה אם משתמשים ב-API הזה.

השוואה בין אירועים רגילים לבין אירועים מאוחדים.

במקום לקבל אירוע אחד, אפשר לגשת למערך האירועים ההיסטוריים שגרמו לאירוע. ל-Android, ל-iOS ול-Windows יש ממשקי API דומים מאוד בערכות ה-SDK המקוריות, ואנחנו חושפים API דומה באינטרנט.

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

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

את הקוד הזה אפשר לשנות בקלות כך שישתמשו במערך האירועים:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

שימו לב שלא כל המאפיינים באירועים המאוחדים מאוכלסים. מכיוון שהאירועים המותאמים לא נשלחים באמת, אלא רק במהלך הנסיעה, הם לא עוברים בדיקה. לחלק מהשדות כמו currentTarget ו-eventPhase יהיו ערכי ברירת מחדל. קריאה לשיטות שקשורות לשגרה, כמו stopPropagation() או preventDefault(), לא תשפיע על אירוע ההורה.