الإشارة إلى الطريق نحو الأمام

Sérgio Gomes

كان من السهل الإشارة إلى عناصر على الويب. كان لديك ماوس، وكنت تحرّكه حولك، وكنت تضغط أحيانًا على الأزرار، وهذا كلّ ما كان عليك فعله. وتم محاكاة كل ما ليس سوى ماوس، وكان المطوّرون يعرفون بالضبط ما يمكنهم الاعتماد عليه.

ومع ذلك، لا يعني ذلك بالضرورة أنّه جيد. بمرور الوقت، أصبح من المُهم بشكلٍ متزايد عدم استخدام الماوس في كل المواقف: يمكنك استخدام أقلام حساسة للضغط والميل، ما يمنحك حرية إبداعية رائعة، ويمكنك استخدام أصابعك، ما يعني أنّ كل ما تحتاجه هو الجهاز ويدك. ولماذا لا تستخدم أكثر من إصبع واحد؟

لقد كانت أحداث اللمس متوفرة لدينا منذ فترة لكي تساعدنا في ذلك، ولكنّها واجهة برمجة تطبيقات منفصلة تمامًا مخصّصة لللمس، ما يجبرك على ترميز نموذجَي أحداث منفصلَين إذا أردت إتاحة استخدام كلّ من الماوس واللمس. يأتي الإصدار 55 من Chrome مزوّدًا بمعيار أحدث يجمع بين النموذجَين: أحداث المؤشر.

نموذج حدث واحد

تعمل أحداث المؤشر على توحيد نموذج إدخال المؤشر للمتصفّح، ما يجمع بين الأجهزة التي تعمل باللمس والأقلام والفئران في مجموعة واحدة من الأحداث. على سبيل المثال:

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 تم إطلاق المؤشر الذي كان يتم تسجيله.

أنواع الإدخال المختلفة

بشكل عام، تتيح لك أحداث المؤشر كتابة رمز بطريقة لا تعتمد على الإدخال، بدون الحاجة إلى تسجيل عناصر معالجة أحداث منفصلة لأجهزة الإدخال المختلفة. بالطبع، سيظل عليك الانتباه إلى الاختلافات بين أنواع الإدخال، مثل ما إذا كان ينطبق مفهوم التمرير. إذا كنت تريد التمييز بين أنواع أجهزة الإدخال المختلفة، ربما لتوفير رمز أو وظيفة منفصلة للإدخالات المختلفة، يمكنك إجراء ذلك من داخل معالجات الأحداث نفسها باستخدام سمة 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.'));

سرعة مدمجة: يسمح هذا النموذج بتحقيق أداء أفضل تلقائيًا مقارنةً بأحداث اللمس، حيث عليك استخدام مستمعي الأحداث السلبيين لتحقيق المستوى نفسه من الاستجابة.

يمكنك منع المتصفّح من التحكّم في العنصر باستخدام سمة CSS touch-action. سيؤدي ضبطها على none في عنصر إلى إيقاف جميع الإجراءات التي يحدّدها المتصفّح والتي تم بدء تنفيذها على هذا العنصر. ولكن هناك عدد من القيم الأخرى للتحكّم بشكل أدق، مثل pan-x للسماح للمتصفّح بالاستجابة للحركة على محور x وليس محور y. يتيح الإصدار 55 من Chrome القيم التالية:

auto الإعداد التلقائي: يمكن للمتصفّح تنفيذ أي إجراء تلقائي.
none لا يُسمح للمتصفّح بتنفيذ أي إجراءات تلقائية.
pan-x لا يُسمح للمتصفّح إلا بتنفيذ الإجراء التلقائي للتمرير الأفقي.
pan-y لا يُسمح للمتصفّح إلا بتنفيذ الإجراء التلقائي للتمرير العمودي.
pan-left لا يُسمح للمتصفّح إلا بتنفيذ الإجراء التلقائي للتمرير الأفقي، ويجب أن يقتصر على تمرير الصفحة لليسار فقط.
pan-right لا يُسمح للمتصفح إلا بتنفيذ الإجراء التلقائي للانتقال الأفقي، ويجب أن يقتصر على تمرير الصفحة لليسار فقط.
pan-up لا يُسمح للمتصفّح إلا بتنفيذ الإجراء التلقائي للانتقال العمودي، ويجب أن يقتصر على تمرير الصفحة للأعلى فقط.
pan-down لا يُسمح للمتصفّح إلا بتنفيذ الإجراء التلقائي للتنقّل العمودي، ويجب أن يقتصر على تمرير الصفحة للأسفل فقط.
manipulation لا يُسمح للمتصفّح إلا بتنفيذ إجراءات الانتقال للأعلى أو للأسفل وتكبير المحتوى أو تصغيره.

التقاط المؤشر

هل سبق لك أن قضّيت ساعة محبطة في تصحيح أخطاء حدث mouseup تعذّر حلّه، إلى أن لاحظت أنّ السبب هو أنّ المستخدم يُفلت الزر خارج نطاق استهداف النقرة؟ لا؟ حسنًا، ربما المشكلة في جهازي فقط.

ومع ذلك، لم تكن هناك طريقة جيدة لحلّ هذه المشكلة حتى الآن. بالتأكيد، يمكنك إعداد معالِج 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.

يمكنك استخدام العناصر القابلة للاستبدال لأحداث المؤشر لملء الفجوات. بدلاً من ذلك، يمكنك التحقّق من توافق المتصفّح أثناء التشغيل بخطوات بسيطة:

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

إنّ أحداث مؤشر الماوس هي خيار رائع للتحسين التدريجي: ما عليك سوى تعديل طُرق الإعداد لإجراء التحقّق أعلاه، وإضافة معالِجات أحداث مؤشر الماوس في المربّع if، ونقل معالِجات أحداث الماوس/اللمس إلى المربّع else.

ننصحك بتجربة هذه الميزات وإعلامنا برأيك.