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

Dave Tapuska
Dave Tapuska

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

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

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

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

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

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

ההתערבות

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

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

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

window.addEventListener("touchstart", func);

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

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

עכשיו המערכת תתעלם מהקריאות ל-preventDefault() בתוך המאזין.

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

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

שגיאות והנחיות

ברוב המקרים לא תבחינו בשינוי. אבל כשהבעיה הזו מתרחשת, הסימפטום הנפוץ ביותר הוא גלילה שמתבצעת מבלי שרוצים. במקרים נדירים, מפתחים עשויים גם להבחין באירועי קליק לא צפויים (כשהקוד preventDefault() חסר במאזין 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 בכל הזדמנות אפשרית. כדי למנוע את כל האפשרויות של גלילה ושינוי מרחק התצוגה בדפדפן בתוך רכיב, צריך להחיל עליו את הערך touch-action: none. אם יש לכם קרוסלה אופקית, מומלץ להחיל עליה את הקוד touch-action: pan-y pinch-zoom כדי שהמשתמשים עדיין יוכלו לגלול אנכית ולשנות את הזום כרגיל. כבר צריך להחיל את touch-action בצורה נכונה בדפדפנים כמו Edge למחשב, שתומכים באירועי Pointer ולא באירועי Touch. בדפדפנים ניידים ישנים יותר של Safari ובדפדפנים ניידים שלא תומכים בפעולות מגע, עליכם להמשיך להפעיל את preventDefault גם אם Chrome יתעלם ממנו.

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

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

סיכום

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

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