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

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 המצביע שזוהה שוחרר.

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

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

אפשר למנוע מהדפדפן להשתלט על touch-action נכס CSS. הגדרת הערך 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!'));

תמיכה בדפדפנים

נכון למועד הכתיבה, אירועי מצביע נתמכים ב-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.
}

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

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