מגע ועכבר

יחד שוב בפעם הראשונה

מבוא

במשך כמעט שלושים שנה, חוויית המחשוב במחשבים נשענה על מקלדת ועכבר או לוח מגע, כמכשירי הקלט העיקריים של המשתמשים. עם זאת, בעשור האחרון סמארטפונים וטאבלטים הביאו מודל אינטראקציה חדש: מגע. עם ההשקה של מכונות Windows 8 עם תמיכה במגע, ועכשיו עם ההשקה של Chromebook Pixel המדהים עם תמיכה במגע, המגע הופך עכשיו לחלק מחוויית המשתמש הצפויה במחשב. אחד מהאתגרים הגדולים ביותר הוא ליצור חוויות שיפעלו לא רק במכשירי מגע ובמכשירים עם עכבר, אלא גם במכשירים האלה שבהם המשתמש ישתמש בשתי שיטות הקלט – לפעמים בו-זמנית!

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

מצב המגע בפלטפורמת האינטרנט

iPhone הייתה הפלטפורמה הפופולרית הראשונה עם ממשקי API מובנים למגע בדפדפן האינטרנט. מספר ספקים אחרים של דפדפנים יצרו ממשקי API דומים שנוצרו כך שיתואמים להטמעה ב-iOS, שמתוארת עכשיו במפרט 'אירועי מגע גרסה 1'. יש תמיכה באירועי מגע ב-Chrome וב-Firefox במחשב, ב-Safari ב-iOS, ב-Chrome ובדפדפן Android ב-Android, וגם בדפדפנים ניידים אחרים כמו דפדפן Blackberry.

הקולגה שלי, בוריס סמס (Boris Smus), כתב מדריך מעולה בנושא אירועי מגע ב-HTML5Rocks. זהו עדיין מקום טוב להתחיל בו אם עוד לא התעניינתם באירועי מגע. למעשה, אם זו הפעם הראשונה שאתם עובדים עם אירועי מגע, כדאי לקרוא את המאמר הזה עכשיו, לפני שתמשיכו. אני אמתין.

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

החשוב ביותר: יכול להיות שהמשתמש משתמש במגע ובעכבר

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

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

תמיכה בעכבר ובמגע בו-זמנית

#1 – לחיצה והקשה – הסדר 'הטבעי' של הדברים

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

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

  1. touchstart
  2. touchmove
  3. touchend
  4. שימוש בעכבר
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

כמובן, המשמעות היא שאם אתם מעבדים אירועי מגע כמו touchstart, עליכם לוודא שאתם לא מעבדים גם את אירוע ה-mousedown או הקליק התואם. אם אפשר לבטל את אירועי המגע (לקרוא ל-preventDefault()‎ בתוך פונקציית הטיפול באירועים), לא ייוצרו אירועי עכבר למגע. אחד הכללים החשובים ביותר לגבי טיפול באירועי מגע הוא:

עם זאת, הפעולה הזו מונעת גם התנהגות אחרת של הדפדפן שמוגדרת כברירת מחדל (כמו גלילה). עם זאת, בדרך כלל מטפלים באירוע המגע באופן מלא במטפל, ורוצים להשבית את פעולות ברירת המחדל. באופן כללי, מומלץ לטפל ולבטל את כל אירועי המגע, או להימנע מהגדרת טיפול באירוע הזה.

שנית, כשמשתמש מקייש על רכיב בדף אינטרנט במכשיר נייד, בדפים שלא תוכננו לאינטראקציה בנייד יש עיכוב של לפחות 300 אלפיות השנייה בין אירוע touchstart לבין העיבוד של אירועי העכבר (mousedown). אפשר לעשות זאת באמצעות Chrome. אפשר להפעיל את האפשרות "הדמיה של אירועי מגע" בכלים למפתחים של Chrome כדי לבדוק ממשקי מגע במערכת ללא מגע.

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

Chrome עבור Android דפדפן Android Opera Mobile ל-Android) Firefox ל-Android Safari iOS
אזור תצוגה שלא ניתן לשנות את הגודל שלו ללא עיכוב 300 אלפיות שניה 300 אלפיות שניה ללא עיכוב 300 אלפיות שניה
ללא אזור תצוגה 300 אלפיות שניה 300 אלפיות שניה 300 אלפיות שניה 300 אלפיות שניה 300 אלפיות שניה

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

<meta name="viewport" content="width=device-width,user-scalable=no">

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

#2: אירועי Mousemove לא מופעלים על ידי מגע

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

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