יחד שוב בפעם הראשונה
מבוא
במשך כמעט שלושים שנה, חוויית המחשוב במחשבים נשענה על מקלדת ועכבר או לוח מגע, כמכשירי הקלט העיקריים של המשתמשים. עם זאת, בעשור האחרון סמארטפונים וטאבלטים הביאו מודל אינטראקציה חדש: מגע. עם ההשקה של מכונות Windows 8 עם תמיכה במגע, ועכשיו עם ההשקה של Chromebook Pixel המדהים עם תמיכה במגע, המגע הופך עכשיו לחלק מחוויית המשתמש הצפויה במחשב. אחד האתגרים הגדולים ביותר הוא ליצור חוויות שעובדות לא רק במכשירי מגע ובמכשירי עכבר, אלא גם במכשירים שבהם המשתמשים משתמשים בשתי שיטות הקלט – לפעמים בו-זמנית!
במאמר הזה נסביר איך יכולות המגע מובנות בדפדפן, איך אפשר לשלב את מנגנון הממשק החדש הזה באפליקציות הקיימות ואיך אפשר לשלב בין מגע לכניסה באמצעות עכבר.
מצב המגע בפלטפורמת האינטרנט
iPhone הייתה הפלטפורמה הפופולרית הראשונה עם ממשקי API מובנים למגע בדפדפן האינטרנט. מספר ספקים אחרים של דפדפנים יצרו ממשקי API דומים שנוצרו כך שיתואמים להטמעה ב-iOS, שמתוארת עכשיו במפרט 'אירועי מגע גרסה 1'. יש תמיכה באירועי מגע ב-Chrome וב-Firefox במחשב, ב-Safari ב-iOS, ב-Chrome ובדפדפן Android ב-Android, וגם בדפדפנים ניידים אחרים כמו דפדפן Blackberry.
הקולגה שלי בוריס סמוס כתב מדריך HTML5Rocks על אירועי Touch, וזו עדיין דרך טובה להתחיל, אם לא בדקת בעבר אירועי Touch. למעשה, אם זו הפעם הראשונה שאתם עובדים עם אירועי מגע, כדאי לקרוא את המאמר הזה עכשיו, לפני שתמשיכו. אני אמתין.
סיימת? עכשיו, אחרי שקיבלתם סקירה כללית על אירועי מגע, אתם יכולים להבין את האתגר בכתיבה של אינטראקציות שתומכות במגע: אינטראקציות המגע יכולות להיות שונות מאוד מאירועי עכבר (ומאירועי משטח מגע ומעקוב כדור שמחקים עכבר). בדרך כלל, ממשקי מגע מנסים לחקות עכברים, אבל ההדמיה הזו לא מושלמת או מלאה. אתם צריכים לעבוד עם שני סגנונות האינטראקציה, ויכול להיות שתצטרכו לתמוך בכל ממשק בנפרד.
החשוב ביותר: יכול להיות שהמשתמש משתמש במסך מגע ובעכבר
מפתחים רבים יצרו אתרים שמזהים באופן סטטי אם סביבה מסוימת תומכת באירועי מגע, ולאחר מכן יוצאים מנקודת הנחה שהם צריכים לתמוך רק באירועי מגע (ולא בעכבר). ההנחה הזו לא נכונה יותר. עצם נוכחותם של אירועי מגע לא מעידה על כך שהמשתמש משתמש בעיקר במכשיר הקלט הזה למגע. מכשירים כמו Chromebook Pixel ומחשבים ניידים מסוימים עם Windows 8 תומכים עכשיו בשתי שיטות הקלט – עכבר ומגע. בקרוב יתווספו עוד מכשירים. במכשירים כאלה די טבעי שהמשתמשים ישתמשו גם בעכבר וגם במסך המגע כדי לקיים אינטראקציה עם אפליקציות, לכן 'תמיכה במגע' לא זהה ל'אין צורך בתמיכה בעכבר'. אי אפשר להתייחס לבעיה כאל 'אני צריך לכתוב שני סגנונות אינטראקציה שונים ולעבור ביניהם'. צריך לחשוב איך שתי האינטראקציות יפעלו יחד וגם בנפרד. ב-Chromebook Pixel אני משתמש לעיתים קרובות במשטח המגע, אבל גם אני פונה למסך ונגיעה בו. באותו אפליקציה או באותו דף, אני עושה מה שמרגיש נכון כרגע. מצד שני, חלק מהמשתמשים במחשבים ניידים עם מסך מגע משתמשים במסך המגע רק לעתים רחוקות, אם בכלל – כך שהקלט ממגע לא אמור להשבית או להפריע לשליטה בעכבר.
לצערנו, קשה לדעת אם סביבת הדפדפן של המשתמש תומכת בהזנת מגע או לא. באופן אידיאלי, דפדפן במחשב שולחני תמיד יציין תמיכה באירועי מגע, כדי שאפשר יהיה לחבר מסך מגע בכל שלב (למשל, אם מסך מגע שמחובר באמצעות KVM יהפוך לזמין). מכל הסיבות האלה, לא כדאי לנסות לעבור בין מגע לעכבר באפליקציות – פשוט תומכים בשניהם.
תמיכה בעכבר ובמגע בו-זמנית
#1 – לחיצה והקשה – הסדר 'הטבעי' של הדברים
הבעיה הראשונה היא שממשקי מגע מנסים בדרך כלל לחקות קליקים בעכבר – כמובן, כי ממשקי מגע צריכים לפעול באפליקציות שבעבר הייתה בהן אינטראקציה רק עם אירועי עכבר. אפשר להשתמש באפשרות הזו כקיצור דרך, כי אירועי 'קליק' ימשיכו להתרחש, בין שהמשתמש לחץ בעכבר ובין שהקיש באצבע על המסך. עם זאת, יש כמה בעיות במקש הקיצור הזה.
קודם כול, חשוב להיזהר כשאתם מעצבים אינטראקציות מתקדמות יותר של מגע: כשהמשתמש משתמש בעכבר, המערכת תגיב באמצעות אירוע לחיצה, אבל כשהמשתמש נוגע במסך, יתרחשו גם אירוע מגע וגם אירוע לחיצה. בלחיצה אחת, סדר האירועים הוא:
- touchstart
- touchmove
- touchend
- שימוש בעכבר
- mousemove
- mousedown
- mouseup
- click
כמובן, המשמעות היא שאם אתם מעבדים אירועי מגע כמו touchstart, עליכם לוודא שאתם לא מעבדים גם את אירוע ה-mousedown או הקליק התואם. אם אתם יכולים לבטל את אירועי המגע (על ידי קריאה ל-preventDefault() בתוך פונקציית הטיפול באירועים), לא ייוצרו אירועי עכבר למגע. אחד מהכללים החשובים ביותר של מטפלי מגע הוא:
עם זאת, הפעולה הזו מונעת גם התנהגות אחרת של הדפדפן שמוגדרת כברירת מחדל (כמו גלילה). עם זאת, בדרך כלל מטפלים באירוע המגע באופן מלא במטפל, ורוצים להשבית את פעולות ברירת המחדל. באופן כללי, כדאי לטפל בכל אירועי המגע ולבטל אותם, או להימנע משימוש ב-handler לאירוע הזה.
שנית, כשמשתמש מקייש על רכיב בדף אינטרנט במכשיר נייד, בדפים שלא תוכננו לאינטראקציה בנייד יש עיכוב של לפחות 300 אלפיות השנייה בין אירוע touchstart לבין העיבוד של אירועי העכבר (mousedown). אפשר לעשות זאת באמצעות Chrome. אפשר להפעיל את האפשרות "אמול אירועי מגע" בכלים למפתחים ב-Chrome כדי לבדוק את ממשקי המגע במערכת ללא מגע.
ההשהיה הזו נועדה לתת לדפדפן זמן לקבוע אם המשתמש מבצע תנועה אחרת – במיוחד, הגדלה בהקשה כפולה. כמובן, זה יכול להיות בעייתי במקרים שבהם רוצים לקבל תגובה מיידית למגע של אצבע. אנחנו ממשיכים לעבוד כדי להגביל את התרחישים שבהם העיכוב הזה מתרחש באופן אוטומטי.
הדרך הראשונה והקלה ביותר למנוע את העיכוב הזה היא "לומר" לדפדפן בנייד שהדף לא יצטרך שינוי מרחק התצוגה. אפשר לעשות זאת באמצעות חלון תצוגה קבוע, למשל על ידי הוספה של הקוד הבא לדף:
<meta name="viewport" content="width=device-width,user-scalable=no">
כמובן, לא תמיד מומלץ לעשות זאת – הפעולה הזו משביתה את התכונה 'הגדלת התצוגה באמצעות צביטה', שעשויה להיות נחוצה מסיבות נגישות. לכן, מומלץ להשתמש בה בזהירות, אם בכלל (אם משביתים את התכונה 'התאמת התצוגה למשתמש', כדאי לספק דרך אחרת לשיפור הקריאוּת של הטקסט באפליקציה). בנוסף, ל-Chrome במכשירים עם מחלקה של מחשבים שתומכים במגע, ובדפדפנים אחרים בפלטפורמות לנייד כאשר הדף כולל אזורי תצוגה שלא ניתנים להתאמה, העיכוב הזה לא רלוונטי.
#2: אירועי Mousemove לא מופעלים על ידי מגע
חשוב לציין בשלב הזה שהאמולציה של אירועי עכבר בממשק מגע לא חלה בדרך כלל על אמולציה של אירועי mousemove - לכן אם אתם בונים פקד יפהפה שמבוסס על עכבר ומשתמש באירועי mousemove, סביר להניח שהוא לא יפעל עם מכשיר מגע אלא אם תוסיפו גם רכיבי handlers של touchmove באופן ספציפי.
בדרך כלל דפדפנים מיישמים באופן אוטומטי את האינטראקציה המתאימה לאינטראקציות מגע בפקדי ה-HTML. לכן, לדוגמה, פקדי טווח של HTML5 פועלים רק כאשר משתמשים באינטראקציות מגע. עם זאת, אם הטמעתם אמצעי בקרה משלכם, סביר להניח שהם לא יפעלו באינטראקציות מסוג 'קליק וגרירה'. למעשה, בספריות נפוצות מסוימות (כמו jQueryUI) עדיין אין תמיכה מובנית באינטראקציות מגע באופן הזה (אבל ל-jQueryUI יש כמה תיקוני 'מונקי-תיקון' לבעיה הזו). זו הייתה אחת מהבעיות הראשונות נתקלתי בהן כששדרגתי את האפליקציה Web Audio Playground כך שתעבוד עם מגע – פס ההזזה היו מבוססים על jQueryUI, ולכן הם לא עבדו עם אינטראקציות של לחיצה וגרירה. עברתי לפקדי טווח של HTML5, והם עבדו. כמובן, אפשר גם פשוט להוסיף מנהלים של תנועות מגע כדי לעדכן את פס ההזזה, אבל יש לכך בעיה אחת…
#3: אין הבדל בין Touchmove ל-MouseMove
ראיתי כמה מפתחים נופלים בפח: הם מגדירים למטפלים באירועי touchmove ו-mousemove לקרוא לאותו נתיב קוד. ההתנהגות של האירועים האלה דומה מאוד, אבל יש הבדלים קלים – במיוחד, אירועי מגע תמיד מטרגטים את הרכיב שבו המגע התחיל, בעוד שאירועי עכבר מטרגטים את הרכיב שנמצא כרגע מתחת לסמן העכבר. לכן יש לנו אירועי mouseover ו-mouseout, אבל אין אירועים תואמים של touchover ו-touchout – רק touchend.
הסיבה הנפוצה ביותר לבעיה הזו היא אם מסירים (או מעבירים) את האלמנט שהמשתמש התחיל לגעת בו. לדוגמה, נניח שיש קרוסלה של תמונות עם טיפול במגע בכל הקרוסלה כדי לתמוך בהתנהגות גלילה מותאמת אישית. ככל שהתמונות הזמינות משתנות, אתם מסירים חלק מהרכיבים של <img>
ומוסיפים רכיבים אחרים. אם המשתמש מתחיל לגעת באחת מהתמונות האלה ואז מסיר אותה, הטיפול (שנמצא באב של רכיב ה-img) פשוט יפסיק לקבל אירועי מגע (כי הם נשלחים ליעד שכבר לא נמצא בעץ) – כך שייראה כאילו המשתמש מחזיק את האצבע במקום אחד, גם אם הוא זז ובסופו של דבר מסיר אותה.
כמובן, אפשר להימנע מהבעיה הזו על ידי הימנעות מהסרת אלמנטים שיש להם (או שיש להם ישות אב שיש לה) גורמים מטפלים במגע בזמן שמגע פעיל. לחלופין, ההנחיה הטובה ביותר היא במקום לרשום גורמים מטפלים סטטיים של touchend/touchmove, להמתין עד שמקבלים אירוע touchstart ואז להוסיף גורמים מטפלים של touchmove/touchend/touchcancel ליעד של אירוע touchstart (ולהסיר אותם בסיום/ביטול). כך תוכלו להמשיך לקבל אירועים לגבי המגע גם אם רכיב היעד יועבר או יוסר. אפשר להתנסות בזה קצת כאן – מקישים על התיבה האדומה תוך כדי לחיצה על מקש Escape כדי להסיר אותה מה-DOM.
#4: לחיצה וריחוף
מטאפורה של סמן העכבר הפרידה בין מיקום הסמן לבין בחירה פעילה, וכך אפשרה למפתחים להשתמש במצבי שיוט כדי להסתיר ולהציג מידע שעשוי להיות רלוונטי למשתמשים. עם זאת, רוב ממשקי המגע לא מזהים כרגע אצבע 'שעוברת מעל' יעד – לכן אסור לספק מידע בעל חשיבות סמנטית (למשל, חלון קופץ עם השאלה 'מהו הלחצן הזה?') על סמך מעבר מעל, אלא אם אתם מספקים גם דרך ידידותית למגע לגשת למידע הזה. חשוב להיזהר בשימוש בהעברת מידע למשתמשים באמצעות מעבר עם העכבר מעל התמונה.
עם זאת, באופן מעניין, בחלק מהמקרים ממשקי מגע יכולים להפעיל את פסאודו-הקלאס :hover ב-CSS – הקשה על אלמנט הופכת אותו ל-:active בזמן שהאצבע נמצאת עליו, והוא גם מקבל את המצב :hover. (ב-Internet Explorer, ה-:hover פועל רק בזמן שהאצבע של המשתמש נמצאת על המסך – בדפדפנים אחרים, ה-:hover פועל עד להקשה הבאה או עד שהעכבר נע). זוהי גישה טובה לגרום לתפריטים קופצים לפעול בממשקי מגע – תופעת לוואי של הפעלת רכיב היא שהמצב :hover חל גם עליו. לדוגמה:
<style>
img ~ .content {
display:none;
}
img:hover ~ .content {
display:block;
}
</style>
<img src="/awesome.png">
<div class="content">This is an awesome picture of me</div>
אחרי שמקישים על רכיב אחר, הרכיב כבר לא פעיל ומצב ההחזקה מעליו נעלם, בדיוק כמו אם המשתמש השתמש בסמן העכבר והזיז אותו מהרכיב. מומלץ לעטוף את התוכן ברכיב <a>
כדי להפוך אותו גם לנקודת העברה (tabstop). כך המשתמש יוכל להציג או להסתיר את המידע הנוסף באמצעות העברת העכבר מעליו או לחיצה עליו, הקשה עליו במקלדת או הקשה עליו במגע, בלי צורך ב-JavaScript. כשהתחלתי לעבוד על Web Audio Playground כדי שיפעל היטב בממשקי מגע, גיליתי שהתפריטים הקופצים שלי כבר עבדו היטב במגע, כי השתמשתי במבנה כזה.
השיטה שלמעלה מתאימה לממשקים שמבוססים על סמן העכבר, וגם לממשקי מגע. בניגוד לשימוש במאפייני 'title' כשמעבירים את העכבר מעליהם, הם לא מוצגים כשהרכיב יופעל:
<img src="/awesome.png" title="this doesn't show up in touch">
סיבה מס' 5: דיוק של מגע לעומת דיוק של עכבר
לעכברים יש ניתוק רעיוני מהמציאות, אבל מתברר שהם מדויקים במיוחד, מכיוון שמערכת ההפעלה הבסיסית בדרך כלל עוקבת אחר דיוק הפיקסלים המדויק של הסמן. מפתחי אפליקציות לנייד, לעומת זאת, למדו שהפעלת אצבע על מסך מגע לא מדויקת באותה מידה, בעיקר בגלל שטח האצבע במגע עם המסך (ובחלקו בגלל שהאצבעות חוסמות את המסך).
אנשים וחברות רבים ערכו מחקרי משתמשים מקיפים בנושא עיצוב אפליקציות ואתרים שמתאימים לאינטראקציה באמצעות אצבע, ונכתבו על כך הרבה ספרים. ההמלצה הבסיסית היא להגדיל את גודל יעדי המגע על ידי הגדלת הריפוד, ולהקטין את הסבירות להקשות שגויות על ידי הגדלת השוליים בין הרכיבים. (השוליים לא נכללים בטיפול בזיהוי היטים של אירועי מגע וקליקים, אבל הריפוד כן נכלל). אחת מהתיקונים העיקריים שביצעתי ב-Web Audio Playground הייתה הגדלת הגודל של נקודות החיבור, כדי שיהיה קל יותר לגעת בהן בצורה מדויקת.
ספקי דפדפנים רבים שמטפלים בממשקים מבוססי-מגע הוסיפו גם לוגיקה לדפדפן כדי לעזור לטרגט את הרכיב הנכון כשמשתמש נוגע במסך, וכדי לצמצם את הסבירות לקליקים שגויים. עם זאת, בדרך כלל המערכת מתקנת רק אירועי קליקים, ולא תנועות (אבל נראה ש-Internet Explorer משנה גם אירועי mousedown/mousemove/mouseup).
מס' 6: עדיף לאחסן את מכשירי המגע, אחרת הם יגרמו לגלילה העכשווית
חשוב גם להגביל את רכיבי הטיפול במגע רק לאלמנטים שבהם אתם צריכים אותם. אלמנטים של מגע יכולים להשתמש ברוחב פס גבוה מאוד, לכן חשוב להימנע משימוש ברכיבי טיפול במגע באלמנטים של גלילה (כי העיבוד עשוי להפריע לביצועי הדפדפן לגלילה מהירה במגע ללא תנודות – דפדפנים מודרניים מנסים לגלול בשרשור של GPU, אבל זה בלתי אפשרי אם הם צריכים לבדוק קודם עם JavaScript אם כל אירוע מגע יטפל על ידי האפליקציה). כאן אפשר לראות דוגמה להתנהגות הזו.
כדי להימנע מהבעיה הזו, מומלץ לוודא שאם אתם מטפלים באירועי מגע רק בחלק קטן מממשק המשתמש, אתם מחברים שם רק גורמים מטפלים במגע (לא, למשל, ב-<body>
של הדף). בקיצור, כדאי להגביל את היקף הגורמים המטפלים במגע ככל האפשר.
#7: מולטי-טאץ'
האתגר האחרון והמעניין הוא שלמרות שאנחנו מתייחסים לממשק המשתמש כ'מגע', התמיכה היא למעשה כמעט תמיד בממשק מגע רב-שכבתי (Multi-touch) – כלומר, ממשקי ה-API מספקים יותר ממקור קלט אחד של מגע בכל פעם. כשמתחילים להוסיף תמיכה במגע לאפליקציות, חשוב לחשוב איך מספר נגיעות עשויות להשפיע על האפליקציה.
אם פיתחתם אפליקציות שמבוססות בעיקר על עכבר, אתם רגילים לפתח עם נקודת סמן אחת לכל היותר – בדרך כלל מערכות לא תומכות בכמה סמני עכבר. באפליקציות רבות, עליכם רק למפות אירועי מגע לממשק של סמן יחיד, אבל רוב החומרה שראינו לקלט מגע במחשב יכולה לטפל לפחות בשני מקורות קלט בו-זמנית, ורוב החומרה החדשה תומכת לפחות ב-5 מקורות קלט בו-זמנית. כשמפתחים פסנתר במסך, כמובן שרוצים לתמוך בכמה קלטות מגע בו-זמנית.
לממשקי ה-API של W3C Touch שמופעלים כרגע אין ממשק API שקובע כמה נקודות מגע החומרה תומכת בהן, כך שתצטרכו להשתמש באומדן הטוב ביותר שלכם לגבי מספר נקודות המגע שהמשתמשים שלכם ירצו – או, כמובן, לשים לב למספר נקודות המגע שאתם רואים בפועל ולהתאים את עצמכם. לדוגמה, באפליקציית פסנתר, אם אף פעם לא מוצגים יותר משני נקודות מגע, כדאי להוסיף ממשק משתמש של 'אקורדים'. ל-PointerEvents API יש ממשק API כדי לקבוע את היכולות של המכשיר.
ריטוש
אנחנו מקווים שהמאמר הזה עזר לכם להתמודד עם האתגרים הנפוצים בהטמעת מגע לצד אינטראקציות עם עכבר. חשוב מכל העצות האחרות, כמובן, הוא שצריך לבדוק את האפליקציה בסביבות של מכשירים ניידים, טאבלטים ומחשבים עם שילוב של עכבר ומגע. אם אין לכם חומרה עם מגע ועכבר, תוכלו להשתמש באפשרות הדמיה של אירועי מגע ב-Chrome כדי לבדוק את התרחישים השונים.
בעזרת ההנחיות האלה, אפשר לא רק ליצור חוויות אינטראקטיביות מרתקות שפועלות היטב עם קלט מגע, קלט עכבר ואפילו עם שני סגנונות האינטראקציה בו-זמנית, אלא גם לעשות זאת בקלות יחסית.