تتوفر الشاشات التي تعمل باللمس على المزيد والمزيد من الأجهزة، بدءًا من الهواتف وحتى شاشات سطح المكتب. ويجب أن يستجيب تطبيقك للمس من خلال طُرق بسيطة ورائعة.
تتوفر الشاشات التي تعمل باللمس على المزيد والمزيد من الأجهزة، بدءًا من الهواتف وصولاً إلى شاشات الكمبيوتر المكتبي. عندما يختار المستخدمون التفاعل مع واجهة المستخدم، يجب أن يستجيب تطبيقك للمس بطرق بديهية.
الاستجابة لحالات العنصر
هل سبق لك أن لمست أو نقرت على عنصر في صفحة ويب وتساءلت عما إذا كان الموقع قد اكتشفه بالفعل؟
إن مجرد تغيير لون أحد العناصر أثناء لمس المستخدمين لأجزاء من واجهة المستخدم أو التفاعل معها يمنح ضمانًا أساسيًا بأن موقعك يعمل. لا يؤدي هذا إلى التخفيف من الاستياء فحسب، بل يمكن أيضًا أن يمنح شعورًا سريعًا وسريع الاستجابة.
يمكن أن تكتسب عناصر DOM أيًّا من الحالات التالية: الوضع التلقائي والتركيز والتمرير والنشط. لتغيير واجهة المستخدم الخاصة بنا لكل حالة من هذه الحالات، نحتاج إلى تطبيق أنماط على الفئات الزائفة التالية :hover
و:focus
و:active
كما هو موضح أدناه:
.btn {
background-color: #4285f4;
}
.btn:hover {
background-color: #296cdb;
}
.btn:focus {
background-color: #0f52c1;
/* The outline parameter suppresses the border
color / outline when focused */
outline: 0;
}
.btn:active {
background-color: #0039a8;
}
في معظم متصفحات الأجهزة الجوّالة، سيتم تطبيق حالات hover و/أو hover على أحد العناصر بعد النقر عليه.
ضع في اعتبارك بعناية الأنماط التي تقوم بتعيينها وكيف ستبدو للمستخدم بعد الانتهاء من لمسه.
إيقاف أنماط المتصفح التلقائية
بمجرد إضافة أنماط للحالات المختلفة، ستلاحظ أن معظم المتصفحات
تنفذ أنماطها الخاصة استجابةً لمس المستخدم. يعود هذا إلى حد كبير إلى أنّه عند إطلاق الأجهزة الجوّالة للمرة الأولى، لم يتضمّن عدد من المواقع الإلكترونية أنماطًا لحالة :active
. ونتيجة لذلك، أضافت العديد من المتصفحات لون تمييز
أو نمطًا إضافيًا لإعطاء ملاحظات المستخدم.
تستخدم معظم المتصفّحات السمة outline
في CSS لعرض حلقة حول عنصر عند التركيز على عنصر. ويمكنك منعه باستخدام ما يلي:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
يضيف كل من Safari وChrome لون تمييز النقر، وهو ما يمكن منعه باستخدام
سمة -webkit-tap-highlight-color
في CSS:
/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
-webkit-tap-highlight-color: transparent;
}
يتشابه سلوك Internet Explorer على Windows Phone، ولكن يتم إلغاؤه عبر علامة وصفية:
<meta name="msapplication-tap-highlight" content="no">
هناك تأثيران جانبيان يجب التعامل مع فايرفوكس.
الفئة الزائفة -moz-focus-inner
التي تضيف مخططًا على العناصر القابلة للّمس، ويمكنك إزالتها من خلال ضبط السمة border: 0
.
إذا كنت تستخدم عنصر <button>
على Firefox، سيتم تطبيق تدرج، ويمكنك إزالته من خلال ضبط background-image: none
.
/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
background-image: none;
}
.btn::-moz-focus-inner {
border: 0;
}
إيقاف اختيار المستخدم
عند إنشاء واجهة المستخدم الخاصة بك، قد تكون هناك سيناريوهات تريد فيها تفاعل المستخدمين مع عناصرك ولكنك تريد إيقاف السلوك الافتراضي لاختيار النص عند الضغط لفترة طويلة أو سحب الماوس فوق واجهة المستخدم الخاصة بك.
يمكنك استخدام السمة user-select
في CSS، ولكن عليك الانتباه إلى أنّ تنفيذ هذا الإجراء على المحتوى قد يكون مزعجًا extremely للمستخدمين إذا أرادوا تحديد النص في العنصر.
لذلك يُرجى التأكد من استخدامه بحذر وبشكل باعتدال.
/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
user-select: none;
}
تنفيذ الإيماءات المخصّصة
إذا كانت لديك فكرة للتفاعلات والإيماءات المخصّصة لموقعك الإلكتروني، هناك موضوعان يجب أخذهما في الاعتبار:
- كيفية دعم جميع المتصفحات.
- كيفية الحفاظ على ارتفاع عدد اللقطات في الثانية
في هذه المقالة، سنلقي نظرة على هذه المواضيع بالضبط والتي تغطي واجهات برمجة التطبيقات التي نحتاج إلى دعمها للوصول إلى جميع المتصفحات ثم سنتناول كيفية استخدامنا لهذه الأحداث بكفاءة.
بناءً على ما تريد أن تفعله الإيماءة، ستحتاج على الأرجح إلى أن يتفاعل المستخدم مع عنصر واحد في كل مرة أو ستحتاج إلى أن يتمكن من التفاعل مع عناصر متعددة في نفس الوقت.
وسنلقي نظرة على مثالين في هذه المقالة، يوضح كلاهما توافق جميع المتصفحات وكيفية الحفاظ على ارتفاع عدد اللقطات في الثانية.
سيسمح المثال الأول للمستخدم بالتفاعل مع عنصر واحد. في هذه الحالة، قد ترغب في منح جميع أحداث اللمس لهذا العنصر الواحد، طالما بدأت الإيماءة مبدئيًا على العنصر نفسه. على سبيل المثال، تحريك إصبع بعيدًا عن العنصر القابل للتمرير السريع لا يزال بإمكانه التحكم في العنصر.
هذا مفيد لأنه يوفر قدرًا كبيرًا من المرونة للمستخدم، ولكنه يفرض قيودًا على كيفية تفاعل المستخدم مع واجهة المستخدم لديك.
ومع ذلك، إذا كنت تتوقع أن يتفاعل المستخدمون مع عناصر متعددة في الوقت نفسه (باستخدام اللمس المتعدد)، يجب أن تقتصر اللمس على العنصر المحدّد.
يُعد هذا أكثر مرونة للمستخدمين، ولكنه يؤدي إلى تعقيد منطق معالجة واجهة المستخدم وأقل مرونة مع خطأ المستخدم.
إضافة أدوات معالجة الأحداث
في متصفّح Chrome (الإصدار 55 والإصدارات الأحدث) وInternet Explorer وEdge،
PointerEvents
هي الطريقة المُقترَحة لتنفيذ الإيماءات المخصّصة.
في المتصفّحات الأخرى، يشكّل TouchEvents
وMouseEvents
الأسلوب الصحيح.
إنّ الميزة الرائعة في PointerEvents
هي أنها تدمج أنواعًا متعددة من الإدخال، بما في ذلك أحداث الماوس واللمس والقلم، في مجموعة واحدة من طلبات معاودة الاتصال. الأحداث المطلوب الاستماع إليها هي pointerdown
وpointermove
وpointerup
وpointercancel
.
مماثلة في المتصفّحات الأخرى هي touchstart
وtouchmove
وtouchend
وtouchcancel
لأحداث اللمس، وإذا أردت تنفيذ
الإيماءة نفسها لإدخال الماوس، ستحتاج إلى تنفيذ mousedown
وmousemove
وmouseup
.
إذا كانت لديك أسئلة حول الأحداث التي يجب استخدامها، يمكنك الاطّلاع على جدول أحداث اللمس والماوس والمؤشر.
ويتطلّب استخدام هذه الأحداث استدعاء الإجراء addEventListener()
على عنصر DOM،
مع اسم الحدث ودالة معاودة الاتصال وقيمة منطقية.
تحدد القيمة المنطقية ما إذا كان يجب مشاهدة الحدث قبل أو بعد أن تتاح للعناصر الأخرى الفرصة لاكتشاف الأحداث وتفسيرها. (true
يعني أنك تريد أن يقع الحدث قبل العناصر الأخرى).
إليك مثال على الاستماع لبدء تفاعل.
// Check if pointer events are supported.
if (window.PointerEvent) {
// Add Pointer Event Listener
swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
// Add Touch Listener
swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);
// Add Mouse Listener
swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}
التعامل مع التفاعل من عنصر واحد
في المقتطف القصير أعلاه من الرمز، أضفنا فقط أداة معالجة الحدث الرئيسي لأحداث الماوس. والسبب في ذلك هو أنه لن يتم تشغيل أحداث الماوس إلا عند تمرير المؤشر فوق العنصر الذي تتم إضافة أداة معالجة الحدث إليه.
سيتتبّع TouchEvents
الإيماءة بعد بدئها بغض النظر عن مكان حدوث اللمس، كما سيتتبّع PointerEvents
الأحداث بغض النظر عن مكان حدوث اللمس بعد طلب setPointerCapture
على عنصر DOM.
بالنسبة إلى أحداث تحريك الماوس وأحداث الانتهاء، نضيف أدوات معالجة الأحداث في طريقة بدء الإيماءة ونضيف أدوات معالجة الحدث إلى المستند، ما يعني أنّه يمكن تتبُّع المؤشر حتى تكتمل الإيماءة.
الخطوات التي تمّ اتّخاذها لتنفيذ ذلك:
- يمكنك إضافة جميع مستمعي TouchEvent وPointerEvent. بالنسبة إلى أحداث Mouseالأحداث، أضِف حدث البدء فقط.
- داخل معاودة الاتصال بإيماءة البدء، اربط حركة الماوس وإنهاء الأحداث
بالمستند. وبهذه الطريقة يتم استلام جميع أحداث الماوس بغض النظر عما إذا كان الحدث قد وقع على العنصر الأصلي أم لا. بالنسبة إلى Pointerevents، يجب استخدام الرمز
setPointerCapture()
على العنصر الأصلي لتلقّي جميع الأحداث الإضافية. بعد ذلك، تحكَّم في بداية الإيماءة. - التعامل مع أحداث النقل:
- في الحدث النهائي، أزِل حركة الماوس وانتهِ من المستمعين من المستند وانهي الإيماءة.
في ما يلي مقتطف من طريقة handleGestureStart()
التي تضيف أحداث النقل
والإنهاء إلى المستند:
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if(evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
معاودة الاتصال الأخيرة التي أضفناها هي handleGestureEnd()
، وتزيل أدوات معالجة الحدث وعملية النقل من المستند وتصدر التقاط المؤشر عند انتهاء الإيماءة على النحو التالي:
// Handle end gestures
this.handleGestureEnd = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 0) {
return;
}
rafPending = false;
// Remove Event Listeners
if (window.PointerEvent) {
evt.target.releasePointerCapture(evt.pointerId);
} else {
// Remove Mouse Listeners
document.removeEventListener('mousemove', this.handleGestureMove, true);
document.removeEventListener('mouseup', this.handleGestureEnd, true);
}
updateSwipeRestPosition();
initialTouchPos = null;
}.bind(this);
باتّباع هذا النمط المتمثل في إضافة حدث النقل إلى المستند، إذا بدأ المستخدم في التفاعل مع عنصر وحرك إيماءته خارج العنصر، سنستمر في تسجيل حركات الماوس بغض النظر عن مكانها على الصفحة، وذلك لأنّ الأحداث يتم تلقّيها من المستند.
يوضح هذا المخطّط البياني ما تفعله أحداث اللمس أثناء إضافة حدثَي النقل والإنهاء إلى المستند بمجرد بدء الإيماءة.
الاستجابة باللمس بكفاءة
الآن بعد أن تم الاهتمام بأحداث البداية والنهاية، يمكننا الاستجابة لأحداث اللمس.
بالنسبة إلى أي من أحداث البدء ونقل، يمكنك بسهولة استخراج x
وy
من حدث.
يتحقّق المثال التالي مما إذا كان الحدث من TouchEvent
، وذلك من خلال التأكّد من توفُّر targetTouches
. وفي حال توافقه، يتم استخراج
clientX
وclientY
من اللمسة الأولى.
إذا كان الحدث PointerEvent
أو MouseEvent
، سيتم استخراج clientX
وclientY
مباشرةً من الحدث نفسه.
function getGesturePointFromEvent(evt) {
var point = {};
if (evt.targetTouches) {
// Prefer Touch Events
point.x = evt.targetTouches[0].clientX;
point.y = evt.targetTouches[0].clientY;
} else {
// Either Mouse event or Pointer Event
point.x = evt.clientX;
point.y = evt.clientY;
}
return point;
}
تتضمّن TouchEvent
ثلاث قوائم تحتوي على بيانات اللمس:
touches
: قائمة بكل اللمسات الحالية على الشاشة، بغض النظر عن عنصر DOM الذي تستخدمهtargetTouches
: قائمة اللمسات الحالية على عنصر DOM المرتبط بالحدث.changedTouches
: قائمة اللمسات التي تغيّرت وأدّت إلى تنشيط الحدث.
في معظم الحالات، يمنحك "targetTouches
" كل ما تحتاجه. (لمزيد من المعلومات عن هذه القوائم، راجع قوائم اللمس).
استخدام requestAnimationFrame
بما أنّ استدعاءات الأحداث يتم تنشيطها في سلسلة التعليمات الرئيسية، نريد تشغيل أكبر قدر ممكن من الرموز في عمليات معاودة الاتصال لأحداثنا، ما يحافظ على ارتفاع معدل عرض الإطارات ويمنع التعطُّل.
باستخدام requestAnimationFrame()
، لدينا فرصة لتحديث واجهة المستخدم قبل أن ينوي المتصفّح رسم إطار وسيساعدنا على الخروج من بعض
العمليات الناتجة عن معاودة الاتصال بالحدث.
إذا لم تكن معتادًا على استخدام "requestAnimationFrame()
"، يمكنك
معرفة المزيد من المعلومات هنا.
من إجراءات التنفيذ المعتادة حفظ إحداثيات x
وy
من حدثَي البدء والنقل وطلب إطار صور متحركة داخل معاودة الاتصال بحدث النقل.
في العرض التوضيحي، نخزّن موضع اللمس الأولي في handleGestureStart()
(ابحث عن initialTouchPos
):
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
تخزن طريقة handleGestureMove()
موضع الحدث الخاص بها قبل طلب إطار صورة متحركة إذا احتجنا إلى ذلك، ويتم تمرير دالة onAnimFrame()
كمعاودة الاتصال:
this.handleGestureMove = function (evt) {
evt.preventDefault();
if (!initialTouchPos) {
return;
}
lastTouchPos = getGesturePointFromEvent(evt);
if (rafPending) {
return;
}
rafPending = true;
window.requestAnimFrame(onAnimFrame);
}.bind(this);
قيمة onAnimFrame
هي دالة عند استدعائها، تغير واجهة المستخدم لتحريكها. من خلال تمرير هذه الدالة إلى requestAnimationFrame()
،
نطلب من المتصفح الاتصال بها قبل قُرب تعديل الصفحة مباشرةً
(أي عرض أي تغييرات على الصفحة).
في معاودة الاتصال handleGestureMove()
، نتحقّق مبدئيًا مما إذا كان rafPending
غير صحيح،
مما يشير إلى ما إذا تم طلب onAnimFrame()
من قِبل requestAnimationFrame()
منذ آخر حدث نقل. هذا يعني أنّه ليس لدينا سوى requestAnimationFrame()
واحد
في انتظار تشغيله في أي وقت.
عند تنفيذ معاودة الاتصال onAnimFrame()
، نضبط عملية التحويل على أي
عناصر نريد نقلها قبل تحديث rafPending
إلى false
، ما يسمح لحدث اللمس التالي بطلب إطار جديد للصور المتحركة.
function onAnimFrame() {
if (!rafPending) {
return;
}
var differenceInX = initialTouchPos.x - lastTouchPos.x;
var newXTransform = (currentXPosition - differenceInX)+'px';
var transformStyle = 'translateX('+newXTransform+')';
swipeFrontElement.style.webkitTransform = transformStyle;
swipeFrontElement.style.MozTransform = transformStyle;
swipeFrontElement.style.msTransform = transformStyle;
swipeFrontElement.style.transform = transformStyle;
rafPending = false;
}
التحكم في الإيماءات باستخدام إجراءات اللمس
تتيح لك خاصية CSS touch-action
التحكم في سلوك اللمس التلقائي لأحد العناصر. في الأمثلة التي نقدّمها، نستخدم touch-action: none
لمنع المتصفّح من تنفيذ أي إجراء باللمس من المستخدم، ما يتيح لنا اعتراض جميع أحداث اللمس.
/* Pass all touches to javascript: */
button.custom-touch-logic {
touch-action: none;
}
إنّ استخدام touch-action: none
هو خيار نووي إلى حدّ ما لأنّه يمنع كل
السلوكيات التلقائية للمتصفّح. في كثير من الحالات، يكون أحد
الخيارات أدناه هو الحل الأفضل.
يتيح لك touch-action
إيقاف الإيماءات التي ينفّذها المتصفّح.
على سبيل المثال، يتوافق الإصدار IE10+ مع إيماءة النقر مرّتين للتكبير أو التصغير. ويؤدي ضبط قيمة touch-action
للسمة manipulation
إلى منع السلوك التلقائي للنقر المزدوج.
ويسمح لك هذا الإجراء بتنفيذ إيماءة النقر مرّتين بنفسك.
في ما يلي قائمة بقيم touch-action
الشائعة الاستخدام:
التوافق مع الإصدارات القديمة من IE
إذا كنت تريد التوافق مع IE10، عليك التعامل مع إصدارات PointerEvents
التي تبدأ بالمورّد.
للتأكّد من توفّر PointerEvents
، عادةً ما تبحث عن
window.PointerEvent
، أمّا في IE10، فتبحث عن
window.navigator.msPointerEnabled
.
أسماء الأحداث مع بادئات المورّدين هي: 'MSPointerDown'
و'MSPointerUp'
و'MSPointerMove'
.
يوضح المثال أدناه كيفية التحقق من الدعم وتبديل أسماء الأحداث.
var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';
if (window.navigator.msPointerEnabled) {
pointerDownName = 'MSPointerDown';
pointerUpName = 'MSPointerUp';
pointerMoveName = 'MSPointerMove';
}
// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
window.PointerEventsSupport = true;
}
لمزيد من المعلومات، يُرجى الاطّلاع على مقالة التحديثات هذه من Microsoft.
مَراجع
فئات زائفة لحالات اللمس
يمكنك الاطّلاع على المرجع النهائي لأحداث اللمس على الرابط: W3C Touch Events.
أحداث اللمس والماوس والمؤشر
هذه الأحداث هي الوحدات الأساسية لإضافة إيماءات جديدة إلى تطبيقك:
لمس القوائم
يتضمّن كل حدث لمس ثلاث سمات قائمة:
تفعيل دعم الحالة النشطة على iOS
لا يطبّق Safari على نظام التشغيل iOS الحالة active تلقائيًا. ولتشغيله، عليك إضافة أداة معالجة حدث touchstart
إلى نص المستند أو إلى كل عنصر.
يجب إجراء ذلك قبل إجراء اختبار وكيل المستخدم، بحيث يتم تشغيله على أجهزة iOS فقط.
إن إضافة بداية لمس إلى الجسم لها ميزة تطبيقها على جميع العناصر في DOM، ولكن هذا قد يؤدي إلى مشكلات في الأداء عند تمرير الصفحة.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
document.body.addEventListener('touchstart', function() {}, false);
}
};
وبدلاً من ذلك، يمكنك إضافة أدوات الاستجابة السريعة إلى جميع العناصر القابلة للتفاعل في الصفحة، ما يخفّف من بعض المخاوف المتعلّقة بالأداء.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
var elements = document.querySelectorAll('button');
var emptyFunction = function() {};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('touchstart', emptyFunction, false);
}
}
};