מגע ועכבר

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

מבוא

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

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

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

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

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

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

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

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

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

תמיכה בעכבר ובנגיעה ביחד

מס' 1 - לחיצה והקשה - הסדר "הטבעי" של הדברים

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

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

  1. הפעלה במגע
  2. Touchmove
  3. קצה מגע
  4. שימוש בעכבר
  5. mousemove
  6. העברת העכבר כלפי מטה
  7. פינוי עכבר
  8. click

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

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

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

#3: Touchmove ו-MouseMove הם לא אותו דבר

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

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

אפשר כמובן להימנע מהבעיה הזו על ידי הימנעות מהסרת אלמנטים שיש להם (או שיש להם אבות אב) מטפלים במגע בזמן שנגיעה פעילה. לחלופין, ההנחייה הטובה ביותר היא לרשום את ה-handlers סטטיים של קצה ה-Touchmove/Touchmove, המתינו עד לקבלת אירוע Touchstart ואז מוסיפים רכיבי handler של touchmove/touchend/touchcancel ל-target של אירוע touchstart (ולהסיר אותם בקצה/ביטול). כך תמשיכו לקבל אירועים של המגע גם אם רכיב היעד יועבר או יוסר. אפשר לשחק עם הקטע הזה כאן - לגעת בתיבה האדומה תוך כדי לחיצה ארוכה על מקש Escape כדי להסיר את ההקלטה מה-DOM.

#4: מגע ו :העברת עכבר

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

מעניין לציין שבמקרים מסוימים, ה-CSS :hover pseudoclass יכול להיות מופעל על ידי ממשקי מגע. הקשה על רכיב מסוים הופכת אותו למצב :פעיל בזמן שהאצבע למטה, ובנוסף מקבלת את מצב :hover. (בדפדפן Internet Explorer, הצבת הסמן :מתבצעת רק כאשר האצבע של המשתמש מופנית כלפי מטה - דפדפנים אחרים משאירים את הסימן :בפועל עד להקשה הבאה או להזזת העכבר הבאה.) זו גישה טובה להפעלת תפריטים קופצים בממשקי מגע - תופעת לוואי של הפעלת רכיב היא שימוש גם במצב :ריחוף. לדוגמה:

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

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

אחת ההנחיות למניעת הבעיה הזו היא לוודא שאם אתם מטפלים באירועי מגע רק בחלק קטן מממשק המשתמש, צריך לצרף שם רק רכיבי handler של מגע (לא למשל, ב-<body> של הדף). בקיצור, צריך להגביל ככל האפשר את ההיקף של רכיבי ה-handler במגע.

#7: ריבוי נקודות מגע

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

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

ל-W3C Touch APIs שמוטמעים כרגע אין ממשק API שבעזרתו אפשר לקבוע בכמה נקודות מגע התומכות בחומרה, לכן צריך לחשב את ההערכה הטובה ביותר לכמה נקודות מגע שהמשתמשים רוצים. אפשר גם לשים לב לכמות נקודות המגע שרואים בפועל ולפעול בהתאם. לדוגמה, באפליקציה של פסנתר, אם אף פעם לא מופיעות יותר משתי נקודות מגע, ייתכן שתרצו להוסיף ממשק משתמש של "אקורדים". ממשק ה-API של PointerEvents כולל API שמאפשר לקבוע את יכולות המכשיר.

ריטוש

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

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