اذهب إلى المحتوى

أحداث لوحة المفاتيح: keydown و keyup والتعامل معها في جافاسكربت


محمد أمين بوقرة

قبل أن نأتي إلى لوحة المفاتيح، يُرجى التنبّه إلى أنّ للأجهزة الحديثة سُبلًا أخرى إلى "إدخال شيء ما". فقد يستخدم الناس مثلا تقنيّة التعرّف على الكلام (خاصّة على اﻷجهزة المحمولة) أو النسخ/اللصق بواسطة الفأرة.

ولذلك، إذا أردنا تتبّع جميع المُدخلات في الحقل <input>، فإنّ أحداث لوحة المفاتيح لا تفي بالغرض. يوجد هناك حدث آخر اسمه input لتتبّع التغيّرات التي تحصل في الحقل <input>، مهما كانت الوسيلة. وقد يكون الخيار اﻷفضل لهذه المهمّة. سنتناول هذا الحدث لاحقا في مقال يشرح اﻷحداث change وinput وcut وcopy وpaste.

ينبغي استخدام أحداث لوحة المفاتيح عند التعامل مع اﻷفعال التي تحصل بواسطة لوحة المفاتيح (يدخل في ذلك لوحة المفاتيح الافتراضيّة)، كالاستجابة للضغط على المفاتيح السهميّة Up وDown أو مفاتيح الاختصارات (بما في ذلك الجمع بين المفاتيح).

منصة الاختبار

لفهم أحداث لوحة المفاتيح بشكل أفضل، يمكنك استخدام منصّة الاختبار الموجودة هنا أو المثال الحي الآتي. جرّب تجميعات مختلفة من المفاتيح في المساحة النصيّة.

Keydown وkeyup

يقع حدث keydown عندما يُضغط مفتاح ما، ثمّ يقع keyup عندما يُحرّر.

event.code وevent.key

تسمح الخاصّيّة key لكائن الحدث بالحصول على المحرف، بينما تُمكّنه الخاصّيّة code من الحصول على "كود المفتاح الملموس". على سبيل المثال، يمكن أن يُضغط نفس المفتاح Z مع أو بدون المفتاح Shift. سيعطينا ذلك محرفان مختلفان: z الصغير وZ الكبير.

تعبّر الخاصّيّة event.key عن المحرف، وقد تختلف. لكنّ event.code تبقى نفسها:

Key event.key event.code
Z z (الصغير) KeyZ
Shift+Z Z (الكبير) KeyZ

إذا كان المستخدم يستعمل لغات مختلفة، فإنّ تغييره للغة أخرى سيعطي محرفا مختلفا تماما بدل "Z". وسيصبح ذلك هو قيمة event.key، بينما تبقى event.code هي نفسها دائما: "KeyZ".


ملاحظة: "KeyZ" وغيرها من أكواد المفاتيح

لكلّ مفتاحٍ كود يتعلّق بمكانه على لوحة المفاتيح. أكواد المفاتيح موضّحة في مواصفة أكواد أحداث واجهة المستخدم.

على سبيل المثال:

  • مفاتيح الأحرف لها أكواد من الشكل "Key<letter>‎"‏: "KeyA" و"KeyB" وهكذا.
  • مفاتيح اﻷرقام لها أكواد من الشكل "Digit<number>‎": ‏"Digit0" و"Digit1" وهكذا.
  • المفاتيح الخاصّة مكوّدة بأسمائها "Enter" و"Backspace" و"Tab" وهكذا.

هناك عدّة تخطيطات (layouts) شائعة للوحات المفاتيح، وتعطي المواصفة أكواد مفاتيح لكلّ منها؛ طالع قسم الحروف اﻷبجديّة واﻷرقام في المواصفة لمزيد من اﻷكواد، أو اضغط فقط على المفتاح في منصّة الاختبار الموجودة هنا.



تنبيه: حجم الأحرف مهمّ: "KeyZ" وليس "keyZ"

يبدو ذلك واضحا، لكن لا يزال النّاس يخطئون في ذلك. يُرجى تجنّب الأخطاء عند الكتابة: إنّه KeyZ وليس keyZ. لن ينجح التحقّق event.code=="keyZ" مثلا، إذ يجب أن يكون أوّل حرف من "Key" كبيرا.


لكن ماذا لو لم يكن المفتاح يعطي أيّ محرف؟ مثل Shift أو F1 أو غير ذلك. بالنسبة لتلك المفاتيح، event.key هي في الغالب نفس event.code:

Key event.key event.code
F1 F1 ‏‏F1
Backspace Backspace Backspace
Shift Shift ShiftRight أو ShiftLeft

يُرجى التنبّه أنّ event.code يبيّن المفتاح قد ضُعط بالضبط. فعلى سبيل المثال، تملك أغلب لوحات المفاتيح مفتاحي Shift، على اليمين وعلى اليسار. يخبرنا event.code أيّ واحد قد ضُغط بالضبط، أمّا event.key فهو مسؤول عن "معنى" المفتاح: ما هو (إنّه "تبديل").

لنفترض أنّنا نريد معالجة أحد مفاتيح الاختصارات: Ctrl+Z (أو Cmd+Z في ماك). تربط معظم محرّرات النّصوص فعل "التراجع" بها. يمكننا وضع منصت إلى keydown وتفحّص المفتاح الذي ضُغط.

لكنّ هناك معضلة هنا: في مثل هذه المنصتات، هل يجب علينا تفحّص قيمة event.key أو event.code؟

من جهة، قيمة event.key هي المحرف، وتتغيّر حسب اللغة. فإذا كان للزائر عدّة لغات في نظام التشغيل وينتقل بينها، فإنّ نفس المفتاح يعطي محرفات مختلفة. لذا فمن المنطقيّ تفحّص event.code، لأنّ لها نفس القيمة دائما. هكذا:

document.addEventListener('keydown', function(event) {
  if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
    alert('Undo!')
  }
});

في الجهة المقابلة، هناك مشكلة مع event.code. مع اختلاف تخطيطات لوحة المفاتيح، قد يكون لنفس المفتاح محرفات مختلفة. على سبيل المثال، إليك التخطيط اﻷمريكيّ ("QWERTY") والتخطيط الألمانيّ ("QWERTZ") تحته (من ويكيبيديا):

us-layout.png

german-layout.png

عند نفس المفتاح، نجد في التخطيط اﻷمريكيّ "Z"، بينما في التخطيط اﻷلمانيّ "Y" (اﻷحرف منعكسة).

حرفيًّا، تكون قيمة event.code هي KeyZ بالنسبة للذين عندهم التخطيط اﻷلماني عند الضغط على المفتاح Y.

لو أجرينا التحقّق event.code == 'KeyZ'‎ في شيفرتنا، فإنّ الناس الذين عندهم التخطيط اﻷلمانيّ سينجح معهم هذا التحقّق إذا ضغطوا على المفتاح "Y".

يبدو هذا غريبا حقّا، لكنّه كذلك. تذكر المواصفة هذا السلوك صراحة.

من الممكن إذًا أن يوافق event.code المحرفَ الخطأ في التخطيط غير المتوقّع. فقد تقابل نفس اﻷحرف مفاتيح ملموسة مختلفة باختلاف التخطيطات، مما يؤدّي إلى أكواد مختلفة. لحسن الحظّ، يحدث هذا مع بعض اﻷكواد فقط مثل keyA وkeyQ وkeyZ (كما رأينا)، ولا يحدث مع المفاتيح الخاصّة مثل Shift. يمكن أن تجد القائمة في المواصفة.

لتتبّع المحرفات التي تتعلّق بالتخطيط بشكل موثوق، قد يكون event.key هو الطريقة اﻷفضل.

في المقابل، يمتاز event.code بأنّه يكون هو نفسه على الدوام، ومرتبطا بمكان المفتاح الملموس، حتى لو غيّر الزائر اللغة. وبذلك، ستعمل مفاتيح الاختصارات التي تعتمد عليه حتى في حال تغيير اللغة.

إذا كنّا نودّ معالجة المفاتيح التي تتعلّق بالتخطيط، فإنّ event.key هو الحلّ. وإذا كنّا نريد لمفاتيح الاختصارات أن تعمل ولو عند تغيير اللغة، فقد يكون event.code هو اﻷفضل.

التكرار التلقائي

إذا استغرق الضغط على مفتاح ما مدّة طويلة بما يكفي، فإنّه يبتدئ "التكرار التلقائيّ": يقع الحدث keydown مرار وتكرار، إلى أن يُحرّر المفتاح فنحصل عندها على keyup. لذلك قد يكون من الطبيعيّ أن نحصل على العديد من keydown بالإضافة إلى keyup وحيد.

إذا وقعت الأحداث بواسطة التكرار التلقائيّ، فإنّ قيمة الخاصّية event.repeat لكائن الحدث تكون true.

اﻷفعال الافتراضيّة

تتنوّع الأفعال الافتراضيّة، إذ هناك العديد من اﻷمور التي قد تحصل بواسطة لوحة المفاتيح. على سبيل المثال:

  • يظهر محرف على الشاشة (النتيجة اﻷظهر).
  • يُحذف محرف (مفتاح Delete).
  • تُمرَّر الصفحة (مفتاح PageDown).
  • يفتح المتصفّح حوار "حفظ الصفحة" (Ctrl+S).
  • …وهكذا.

قد يؤدّي منع الفعل الافتراضيّ عند keydown إلى إلغاء معظمها، باستثناء المفاتيح الخاصّة المضمّنة في نظام التشغيل. على سبيل المثال، يعمل Alt+F4 في ويندوز على إغلاق نافذة المتصفّح الحاليّة. ولا سبيل إلى إيقاف ذلك بواسطة منع الفعل الافتراضيّ في جافاسكربت.

مثلا، يتوقّع المُدخل <input> أدناه رقم هاتف، فلا يقبل المفاتيح التي بخلاف اﻷرقام أو + أو () أو -:

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

يُرجى التنبّه أنّ المفاتيح الخاصّة، مثل Backspace وLeft وRight وCtrl+V، لا تعمل في المُدخل. هذا أثر جانبيّ للمرشّح checkPhoneKey الصارم. لنقم بإرخائه بعض الشيء:

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
    key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

تعمل كلّ من مفاتيح اﻷسهم والحذف جيّدا الآن.

لكن لايزال بإمكاننا إدخال أيّ شيء باستخدام الفأرة عن طريق النقر باليمين ثمّ اللصق. فالمرشّح إذًا ليس موثوقا مئة بالمئة. يمكننا تركه كذلك، لأنّه يعمل في غالب الأحيان. أو بدلا من ذلك، يمكننا تتبّع حدث input الذي يقع عند كلّ تغيير، ونتفحّصه عندئذ و نبرزه/نعدّله إذا لم يكن صالحًا.

الأحداث والخاصيات القديمة

في القديم، كان هناك الحدث keypress، بالإضافة إلى خاصّيّات كائن الحدث keyCode وcharCode و which. كان هناك الكثير من حالات عدم توافق المتصفّح عند العمل بها، مما أجبر العاملين على المواصفة على التخلّي عنها جميعا وإنشاء أحداث جديدة (كما بُيّنت في أعلى هذا المقال). لا تزال الشيفرات القديمة تعمل، إذ بقيت المتصفّحات تدعمها، لكن ليس هناك سبب إطلاقا لاستخدامها الآن.

الملخص

يؤدّي الضغط على مفتاح ما إلى توليد حدث من أحداث لوحة المفاتيح، سواءً كان مفتاح رمز أو مفتاحا خاصّا مثل Shift أو Ctrl أو غيرها. الاستثناء الوحيد هو المفتاح Fn الذي يكون أحيانا في الحواسيب المحمولة. ليس هناك حدث لوحة مفاتيح له، لأنّه يكون في الغالب في مستوى أقلّ انخفاضا من مستوى نظام التشغيل.

أحداث لوحة المفاتيح:

  • keydown -- عند الضغط على المفتاح (يتكرّر تلقائيّا إذا ضُغط المفتاح طويلا)،
  • keyup -- عند تحرير المفتاح.

خاصّيات أحداث لوحة المفاتيح اﻷساسيّة:

  • code -- "كود المفتاح" ("KeyA" و"ArrowLeft" وهكذا)، متعلّق بالمكان الملموس للمفتاح على لوحة المفاتيح.
  • key -- المحرف ("A" و"a" وهكذا). لمفاتيح غير المحارف، مثل Esc، تكون له نفس قيمة code عادة.

في السابق، كانت أحداث لوحة المفاتيح تُستعمل أحيانا لتتبّع مُدخلات المستخدم في حقول النماذج. هذا غير موثوق، لأنّ المُدخلات قد تأتي من مصادر متنوّعة. لذلك، لدينا الأحداث input وchange التي تمكّننا من معالجة أيّ مدخل كان (سنتطرّق لها لاحقا في مقال اﻷحداث change وinput وcut وcopy وpaste). تقع تلك اﻷحداث عند إدخال أيّ نوع من المدخلات، بما في ذلك النسخ واللصق والتعرّف على الكلام.

التمارين

مفاتيح الاختصارات الموسعة

الأهميّة: 5

أنشئ الدالّة runOnKeys(func, code1, code2, ... code_n)‎ التي تعمل على بتنفيذ func عند الضغط في نفس الوقت على المفاتيح التي لها اﻷكواد code1, code2, …, code_n.

على سبيل المثال، تظهر الشيفرة أدناه alert عندما تُضغط المفاتيح "Q" و"W" معا (في أيّ لغة، مع أو بدون تفعيل الأحرف الكبيرة).

runOnKeys(
  () => alert("Hello!"),
  "KeyQ",
  "KeyW"
);

يمكن مشاهدة المثال يعمل من هنا.

الحل

لابدّ أن نستخدم اثنين من المعالجات: document.onkeydown وdocument.onkeyup.

لننشئ المجموعة pressed = new Set()‎ لحفظ المفاتيح المضغوطة حاليّا. يعمل المعالج اﻷوّل على الإضافة إليها، بينما يعمل المعالج الثاني على النزع منها. وكلّما وقع الحدث keydown نتحقّق إن كان لدينا ما يكفي من المفاتيح المضغوطة، وننفّذ الدالّة إذا كان الحال كذلك.

افتح الحلّ في البيئة التجريبيّة

ترجمة -وبتصرف- للمقال Keyboard: keydown and keyup من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...