تتوفّر الشاشات التي تعمل باللمس على المزيد والمزيد من الأجهزة، بدءًا من الهواتف وحتى شاشات أجهزة الكمبيوتر المكتبي. يجب أن يستجيب تطبيقك للمس المستخدمين بطرق بديهية وجميلة.
تتوفر الشاشات التي تعمل باللمس على المزيد والمزيد من الأجهزة، بدءًا من الهواتف وحتى شاشات سطح المكتب. عندما يختار المستخدمون التفاعل مع واجهة المستخدم الخاصة بك، يجب أن يستجيب تطبيقك إلى لمسهم بطرق بديهية.
الاستجابة لحالات العناصر
هل سبق لك أن لمست أو نقرت على أحد العناصر في صفحة ويب وتساءلت عما إذا كان الموقع قد اكتشفه بالفعل؟
إنّ تغيير لون عنصر ما عندما يلمس المستخدمون أجزاء من واجهة المستخدم أو يتفاعلون معها يمنحهم شعورًا أساسيًا بالطمأنينة بأنّ موقعك الإلكتروني يعمل على ما يرام. لا يخفف هذا من الإحباط فقط، بل يمكن أن يمنح أيضًا شعورًا سريعًا وسريع الاستجابة.
يمكن أن تكتسب عناصر 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;
}
في معظم متصفّحات الأجهزة الجوّالة، سيتم تطبيق حالات المرّر و/أو التركيز على عنصر بعد النقر عليه.
فكِّر جيدًا في الأنماط التي تحدّدها وكيف ستبدو للمستخدم بعد انتهاءه من لمسها.
إيقاف أنماط المتصفّح التلقائية
بعد إضافة أنماط للحالات المختلفة، ستلاحظ أنّ معظم المتصفّحات
تطبّق أنماطها الخاصة استجابةً للمسة المستخدم. ويعود السبب في ذلك إلى أنّه عند إطلاق الأجهزة الجوّالة لأول مرة، لم يكن لدى عدد من المواقع الإلكترونية تنسيقًا لحالة :active
. نتيجةً لذلك، أضافت العديد من المتصفّحات
لونًا أو نمطًا إضافيًا للتمييز من أجل تقديم ملاحظات للمستخدم.
تستخدِم معظم المتصفّحات سمة CSS outline
لعرض حلقة حول
عنصر عند التركيز عليه. ويمكنك منعه باستخدام:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
يضيف Safari وChrome لونًا لتمييز النقرات يمكن منعه باستخدام سمة CSS التالية:
-webkit-tap-highlight-color
/* 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">
هناك تأثيران جانبيان في Firefox يجب التعامل معهما.
الفئة الصورية -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;
}
إيقاف ميزة "اختيار المستخدم"
عند إنشاء واجهة المستخدم الخاصة بك، قد تكون هناك سيناريوهات تريد فيها أن يتفاعل المستخدمون مع العناصر الخاصة بك ولكنك تريد إلغاء السلوك الافتراضي المتمثل في تحديد النص عند الضغط مع الاستمرار أو سحب الماوس فوق واجهة المستخدم.
يمكنك إجراء ذلك باستخدام سمة CSS user-select
، ولكن عليك الحذر من أنّه
قد يكون إجراء ذلك على المحتوى مزعِجًا للغاية
للمستخدمين إذا كانوا يريدون اختيار النص في العنصر.
لذا تأكد من استخدامه بحذر وبشكل محدود.
/* 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. بالنسبة إلى أحداث MouseEvents، أضِف فقط حدث البدء.
- داخل استدعاء إيماءة البدء، اربط أحداث حركة الماوس وإنهاءها بالوثيقة. بهذه الطريقة، يتم تلقّي جميع أحداث الماوس بغض النظر عمّا إذا كان الحدث يحدث على العنصر الأصلي أم لا. بالنسبة إلى 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
من حدثَي x
وy
وطلب إطار للحركة داخل x
.
في العرض التقديمي، نخزّن موضع اللمس الأوّلي في 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
غير صحيحة،
ما يشير إلى ما إذا كان requestAnimationFrame()
قد استدعى onAnimFrame()
منذ حدث النقل الأخير. وهذا يعني أنّ لدينا 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
الشائعة الاستخدام:
التوافق مع الإصدارات القديمة من Internet Explorer
إذا كنت تريد إتاحة استخدام 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.
أحداث اللمس والماوس والمؤشر
هذه الأحداث هي اللبنات الأساسية لإضافة إيماءات جديدة إلى تطبيقك:
قوائم اللمس
يتضمّن كل حدث لمس ثلاث سمات قائمة:
تفعيل دعم الحالة النشطة على 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);
}
}
};