מכוונים לאן שרוצים

Sérgio Gomes

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

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

כבר תקופה ארוכה שאנחנו מקיימים אירועי מגע כדי לעזור בעניין הזה, אבל מדובר ב-API נפרד לגמרי שמיועד למגע, שמאלץ לתכנת שני מודלים נפרדים של אירועים אם אתם רוצים לתמוך גם בעכבר וגם במגע. Chrome 55 מגיע עם תקן חדש יותר שמאחד את שני המודלים: אירועי מצביע.

מודל אירוע יחיד

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

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

הנה רשימה של כל האירועים הזמינים, שאמורים להיראות די מוכרים אם אתם מכירים את אירועי העכבר:

pointerover הסמן נכנס לתיבה התוחמת של הרכיב. הפעולה הזו מתבצעת באופן מיידי במכשירים שתומכים בהעברת עכבר, או לפני אירוע pointerdown במכשירים שלא תומכים באפשרות הזו.
pointerenter דומה ל-pointerover, אבל לא מופיע בבועות ומטפל בצאצאים בצורה שונה. פרטים על המפרט.
pointerdown הסמן נכנס למצב לחצן פעיל, עם לחיצה על לחצן או יצירת איש קשר, בהתאם לסמנטיקה של מכשיר הקלט.
pointermove מיקום הסמן השתנה.
pointerup הסמן יצא ממצב פעיל של לחצן.
pointercancel קרה משהו, כלומר לא סביר שהסמן יפלט אירועים נוספים. כלומר, יש לבטל פעולות מתבצעות ולחזור למצב קלט ניטרלי.
pointerout הסמן יצא מהתיבה התוחמת של הרכיב או המסך. גם אחרי pointerup, אם המכשיר לא תומך בהעברת עכבר.
pointerleave דומה ל-pointerout, אבל לא מופיע בבועות ומטפל בצאצאים בצורה שונה. פרטים על המפרט.
gotpointercapture הרכיב קיבל לכידת מצביע.
lostpointercapture הסמן שצולם שוחרר.

סוגי קלט שונים

באופן כללי, אירועי Pointer מאפשרים לכתוב קוד באופן שלא מסתמך על קלט, בלי לרשום רכיבי handler נפרדים של אירועים למכשירים שונים לקליטת נתונים. כמובן, עדיין צריך לשים לב להבדלים בין סוגי הקלט, למשל אם הקונספט של העברת העכבר רלוונטי. אם בכל זאת רוצים להבדיל בין סוגים שונים של מכשירי קלט – אולי כדי לספק קוד או פונקציונליות נפרדים לערכי קלט שונים – עם זאת, אפשר לעשות זאת מתוך אותם רכיבי handler של אירועים באמצעות המאפיין pointerType של הממשק PointerEvent. לדוגמה, אם אתם מתכנתים חלונית הזזה לניווט צדדית, תוכלו להשתמש בלוגיקה הבאה באירוע pointermove:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

פעולות ברירת מחדל

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

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

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

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

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

auto ברירת מחדל; הדפדפן יכול לבצע כל פעולת ברירת מחדל.
none לדפדפן אין הרשאה לבצע פעולות שמוגדרות כברירת מחדל.
pan-x לדפדפן מותר לבצע רק את פעולת ברירת המחדל של גלילה אופקית.
pan-y הדפדפן מורשה לבצע רק את פעולת ברירת המחדל של גלילה אנכית.
pan-left הדפדפן יכול לבצע רק את פעולת ברירת המחדל לגלילה אופקית, ורק להזיז את הדף ימינה.
pan-right הדפדפן יכול לבצע רק את פעולת ברירת המחדל לגלילה אופקית, ורק להזיז את הדף ימינה.
pan-up הדפדפן יכול לבצע רק את פעולת ברירת המחדל של גלילה אנכית, ורק להזיז את הדף למעלה.
pan-down הדפדפן יכול לבצע רק את פעולת ברירת המחדל של גלילה אנכית, ורק להזיז את הדף למטה.
manipulation הדפדפן מורשה לבצע רק פעולות גלילה ושינוי מרחק התצוגה.

לכידת מצביע

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

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

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

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

תמיכת דפדפן

בזמן הכתיבה, אירועי Pointer נתמכים ב-Internet Explorer 11, ב-Microsoft Edge, ב-Chrome וב-Opera, והם נתמכים באופן חלקי ב-Firefox. תוכלו למצוא רשימה עדכנית בכתובת caniuse.com.

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

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

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

אז קדימה, תן להם הזדמנות וספר לנו מה אתה חושב!