גלילה מהירה במגע כברירת מחדל

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

אנחנו יודעים שמהירות הגלילה היא קריטית לאינטראקציה של המשתמש באתר בנייד, אבל מאזינים לאירועי מגע גורמים לעיתים קרובות לבעיות חמורות בביצועי הגלילה. כדי לטפל בבעיה, Chrome מאפשר למאזיני אירועי מגע להיות פסיביים (מעביר את האפשרות {passive: true} ל-addEventListener()) ושולח את האירוע pointer events API. אלו תכונות נהדרות להעלאת תוכן חדש למודלים שלא חוסמים את הגלילה, אבל לפעמים המפתחים מתקשים להבין ולאמץ אותם.

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

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

רקע: אירועים הניתנים לביטול מאטים את הדף שלך

אם תתבצע קריאה ל-preventDefault() באירועים touchstart או touchmove הראשונים, תמנע גלילה. הבעיה היא שברוב המקרים מאזינים לא יתקשרו ל-preventDefault(), אבל דפדפן צריך להמתין לסיום האירוע כדי לוודא זאת. 'פונקציות event listener פסיביות' שהוגדרו על ידי המפתח פותרות את הבעיה הזו. כשמוסיפים אירוע מגע עם אובייקט {passive: true} כפרמטר השלישי ב-handler של האירועים, אומרים לדפדפן שה-touchstart listener לא יקרא ל-preventDefault() והדפדפן יכול לבצע את הגלילה בבטחה בלי לחסום את ה-listener. לדוגמה:

window.addEventListener("touchstart", func, {passive: true} );

ההתערבות

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

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

זה גרם לנו להגדיר את ההתערבות שלנו כך: אם היעד של touchstart או Touchmove הוא window, document או body, ברירת המחדל היא passive.true משמעות הקוד היא:

window.addEventListener("touchstart", func);

הופך להיות שווה ערך ל:

window.addEventListener("touchstart", func, {passive: true} );

מעכשיו המערכת תתעלם משיחות אל preventDefault() בתוך ה-listener.

בתרשים הבא מוצג הזמן שלוקח לאחוז העליון של הגלילות מהרגע שמשתמש נוגע במסך כדי לגלול עד לרגע שבו התצוגה מתעדכנת. הנתונים האלה מיועדים לכל האתרים ב-Chrome ל-Android. לפני שההתערבות הופעלה, 1% מהגלילות נמשך קצת יותר מ-400 אלפיות השנייה. ב-Chrome 56 בטא, הנפח הצטמצם קצת יותר מ-250 אלפיות השנייה, ירידה של כ-38%. בעתיד אנחנו מקווים שנהפוך את האמירה הפסיבית לברירת המחדל של כל המאזינים ב-touchstart וב-touchmove, תוך פחות מ-50 אלפיות השנייה.

תרשים זמני הגלילה של 1% המובילים

פריצה והכוונה

ברוב המקרים לא תבחינו בשיבושים. אבל כשיש תקלות, התסמינים השכיחים ביותר הם שגלילה מתרחשת כשאתם לא רוצים. במקרים נדירים, מפתחים עשויים להבחין גם באירועי קליק לא צפויים (כאשר preventDefault() היה חסר ב-listener של touchend).

ב-Chrome בגרסה 56 ואילך, כלי הפיתוח יתעדו אזהרה כשתתבצע קריאה ל-preventDefault() באירוע שבו ההתערבות פעילה.

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

האפליקציה שלך יכולה לקבוע אם היא עלולה להגיע למצב הזה בטבע על ידי בדיקה אם לקריאה ל-preventDefault הייתה השפעה כלשהי דרך המאפיין defaultPrevented.

גילינו שאת רוב הדפים המושפעים אפשר לתקן בקלות יחסית, על ידי החלת נכס ה-CSS מגע, כשהדבר אפשרי. כדי למנוע את כל הגלילה וההגדלה של הדפדפן ברכיב מסוים, אפשר להחיל עליו touch-action: none. אם יש לכם קרוסלה אופקית, מומלץ להחיל עליה touch-action: pan-y pinch-zoom כדי שהמשתמש עדיין יוכל לגלול אנכית ולשנות את מרחק התצוגה כרגיל. יש צורך להחיל פעולת מגע בצורה נכונה כבר בדפדפנים כמו Desktop Edge שתומכים באירועי מצביע ולא באירועי מגע. ב-Safari לנייד ובדפדפנים ניידים ישנים יותר שלא תומכים בפעולת מגע, מאזיני המגע צריכים להמשיך לקרוא ל-preventDefault גם אם Chrome יתעלם ממנו.

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

  • אם ה-listener של touchstart מבצע קריאה ל-preventDefault(), צריך לוודא ש-preventDefault() מופעל גם ממאזיני קצה מגע משויכים כדי להמשיך לדגום את היצירה של אירועי קליקים ושל התנהגות הקשה אחרת שמוגדרת כברירת מחדל.
  • מעבירים את {passive: false} בפעם האחרונה (והלא מומלצת) כדי addEventListener() כדי לשנות את התנהגות ברירת המחדל. לתשומת ליבכם, תצטרכו לזהות אם סוכן המשתמש תומך ב-EventListenerOptions.

סיכום

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

למרות שחובה לעשות זאת ב-Safari לנייד, אתרים לא אמורים להסתמך על קריאה ל-preventDefault() מתוך touchstart ו-touchmove מאזינים, כי כבר לא מובטח שהפעולה הזו תכובד ב-Chrome. מפתחים צריכים להחיל את מאפיין ה-CSS touch-action ברכיבים שבהם צריך להשבית את הגלילה ואת שינוי המרחק מהתצוגה, כדי להודיע לדפדפן לפני התרחשות אירועי מגע. כדי למנוע את התנהגות ברירת המחדל של הקשה (כמו יצירה של אירוע מסוג קליק), יש להפעיל את preventDefault() בתוך רכיב ה-listener של touchend.