2638 words
13 minutes
Keyboard Events

١. مقدمة بسيطة 🚪#

تخيل إنك قاعد قدام باب بيتك، وكل ما حد يعمل حاجة على الباب (يخبط، يفتح، يقفل) انت بتعرف وبترد على الحدث ده.

الـ keyboard events شبه كده بالظبط - هي طريقة الكمبيوتر إنه يعرف إيه اللي بيحصل على الكيبورد بتاعك.

٢. الأنواع الأساسية#

keydown (لما تدوس)#

// زي ما انت بتدوس على جرس الباب
input.addEventListener('keydown', function(event) {
    console.log('حد دايس على الجرس دلوقتي');
    // لو دوست على حرف A مثلاً
    if (event.key === 'a') {
        console.log('دوست على A');
    }
});

keyup (لما ترفع صباعك)#

// زي ما انت بتسيب جرس الباب
input.addEventListener('keyup', function(event) {
    console.log('الجرس رجع لمكانه');
    // مثال عملي: لو بتكتب رسالة
    if (event.key === 'Enter') {
        console.log('خلصت كتابة وضغطت إنتر');
    }
});

keypress (deprecated)#

element.addEventListener('keypress', function(event) {
    console.log('تم الضغط والترك');
});

٣. الخصائص المهمة#

const textInput = document.querySelector('#message-input');

textInput.addEventListener('keydown', function(event) {
    // تخيل إنك سواق ميكروباص:
    
    // معرفة أنهي زرار اتضغط (زي ما تعرف مين نده عليك في الشارع)
    console.log('مين نده؟:', event.key);
    
    // لو حد ضغط Shift (زي لما حد يقولك على جنب)
    if (event.shiftKey) {
        console.log('قف على جنب');
    }
    
    // لو حد ضغط Ctrl (زي إشارة المرور)
    if (event.ctrlKey) {
        console.log('إشارة حمرا، استنى');
    }
    
    // لو حد ضغط Alt (زي الزمور)
    if (event.altKey) {
        console.log('بيزمر: بييييب');
    }
});

٤. مثال عملي من حياتنا: لعبة “عم جمال البقال كأنه جاتا ” 🏪#

const gameInput = document.querySelector('#game-input');
let score = 0;

gameInput.addEventListener('keydown', function(event) {
    // لو دوست على السهم اليمين
    if (event.key === 'ArrowRight') {
        console.log('عم جمال مشي يمين علشان يجيب الطماطم');
        score += 5;
    }
    
    // لو دوست على السهم الشمال
    if (event.key === 'ArrowLeft') {
        console.log('عم جمال رجع للمحل تاني');
        score += 3;
    }
    
    // لو دوست على مسافة
    if (event.key === ' ') {
        console.log('عم جمال قفز فوق القطة! 😺');
        score += 10;
    }
    
    // اطبع النتيجة
    console.log(`النتيجة دلوقتي: ${score} نقطة`);
});

٥. مثال للشات/الدردشة 💬#

const chatInput = document.querySelector('#chat-input');

chatInput.addEventListener('keydown', function(event) {
    // لو دوست إنتر
    if (event.key === 'Enter') {
        // منع السطر الجديد
        event.preventDefault();
        
        // لو مفيش رسالة، بلاش نعمل حاجة
        if (chatInput.value.trim() === '') {
            console.log('مينفعش نبعت رسالة فاضية يا كبير');
            return;
        }
        
        // بعت الرسالة
        console.log('جاري إرسال الرسالة: ' + chatInput.value);
        chatInput.value = ''; // نضف مكان الكتابة
    }
    
    // لو عايز تمسح الرسالة كلها (Ctrl + Backspace)
    if (event.ctrlKey && event.key === 'Backspace') {
        console.log('مسحت الرسالة كلها مرة واحدة');
        chatInput.value = '';
    }
});

نصائح مهمة 💡#

  1. دايماً اعمل preventDefault() لما تحتاج تمنع السلوك الديفولت للمتصفح
  2. متنساش تمسح الـ event listeners لما متحتاجهمش
  3. اعمل حساب الـ mobile keyboards - مش كل الناس بتستخدم كيبورد حقيقي
  4. خلي بالك من الـ browser compatibility

إزاي تاخد بالك من كيبورد الموبايلات لما تعمل keyboard events في الجافاسكريبت؟#

المشاكل اللي ممكن تقابلك مع كيبورد الموبايل:#

  • الكيبورد اللمس مش بيعمل نفس الحركات زي الكيبورد لعادي:
    • لو المستخدم اختار كلمة من الاقتراحات (Auto-Complete) ➡️ مبيبعتهاش كـ key events.
    • لو كتب بالـ Swipe/Glide ➡️ مبيحسبش كل حرف على حدة.
    • لو عدل على الكلام بالـ Copy/Paste ➡️ مبيظهرش كـ keydown.
  • الكود اللي مبنى على keyup/keydown مش هيشتغل مع الموبايلات!

إيه البديل؟#

  • input event: ده البطل اللي بيشوف أي تغيير في الحقل بغض النظر عن مصدره:
    • ضغط أزرار
    • اختيار اقتراح
    • سحب أصبع (Swipe)
    • تعديل بالنسخ/لصق
    • حتى لو داس على زر المسح (Backspace)!
// بدل ما تكتب:
document.querySelector('input').addEventListener('keyup', function(e) { ... });

// اكتب:
document.querySelector('input').addEventListener('input', function(e) { 
  // هتشتغل مع اي تغيير في الحقل (حتى لو من الكيبورد اللمس)
});

ليه الحل ده كويس؟#

  • بيتعامل مع القيمة النهائية (e.target.value) مش طريقة الكتابة.
  • بيغطي كل طرق الإدخال الغريبة اللي ممكن تخطر على بالك!

أمثلة إضافية للتطبيق 🎯#

١. عمل shortcut للسيف:#

document.addEventListener('keydown', function(event) {
    // Ctrl + S
    if (event.ctrlKey && event.key === 's') {
        event.preventDefault();
        console.log('حفظت الملف يا معلم!');
    }
});

٢. التحكم في الصوت:#

document.addEventListener('keydown', function(event) {
    switch(event.key) {
        case 'ArrowUp':
            console.log('علي الصوت شوية');
            break;
        case 'ArrowDown':
            console.log('نزل الصوت شوية');
            break;
        case 'm':
            console.log('اقفل الصوت خالص');
            break;
    }
});

٦. نصائح مهمة#

  • استخدم event.preventDefault() لو عايز تمنع السلوك الديفولت للمتصفح
  • اعمل تشيك على event.repeat لو عايز تعرف إذا كان المستخدم ضاغط على الزرار باستمرار
  • متنساش تعمل cleanup للـ event listeners لما متحتاجهمش

إزاي تكتشف إذا كان المستخدم ضاغط على الزرار باستمرار؟ (مع event.repeat) ⌨️#

المشكلة:#

  • لو المستخدم ضاغط على زرار معين وماسكه (زي السهم لأعلى في الألعاب)،
  • الكيبورد العادي (الفيزيكي) بيبعت keydown event كل شوية لحد ما يرفع إصبعه.
  • عايزين نفرق بين:
    • الضغطة الأولى ✅
    • الضغط المستمر (اللي جه بعد كدا) ❌

الحل السحري: event.repeat 🔥#

  • event.repeat هي خاصية في الـ keyboard event بترجع true لو الحدث ده نتيجة ضغط مستمر على الزرار.
document.addEventListener('keydown', function(e) {
  if (e.repeat) {
    console.log('لا تعمل حاجة: الضغط مستمر!');
    return; // وقف التنفيذ لو الضغط مستمر
  }
  
  console.log('ضغطة أولى: إعمل الحاجة اللي عايزها هنا!');
});

٧. مثال على عمل form validation#

const input = document.querySelector('input');

input.addEventListener('keydown', function(event) {
    // منع الأرقام في حقل النص
    if (/^\d$/.test(event.key)) {
        event.preventDefault();
        console.log('مينفعش تكتب أرقام هنا');
    }
});

input.addEventListener('keyup', function(event) {
    // التحقق من طول النص
    if (this.value.length < 3) {
        console.log('لازم تكتب على الأقل 3 حروف');
    }
});

٨. التعامل مع الـ modifier keys#

إيه هي الـ modifier keys؟ وازاي تتعامل معاها في الجافاسكريبت؟ 🎛️#

  • دول مفاتيح خاصة مينفعش تستخدمهم لوحدهم، لكن بيعدلوا سلوك المفاتيح التانية لما تضغط عليهم مع بعض!
  • مثال: لما تضغط Ctrl + C عشان تنسخ، الـ Ctrl هو الـ modifier key.

أشهر الـ modifier keys:#

  1. Shift

    • بيخلي الحروف كابتل (A ➔ a)
    • لو داس عليه مع حركة الماوس: بيعدل في الـ dragging.
  2. Ctrl (Control) 🎮

    • بيستخدم في الاختصارات زي Ctrl + S للحفظ.
  3. Alt (Option في الماك) ⎇

    • بيستخدم في إظهار قوائم أو حاجات خاصة (زي Alt + Tab للتبويبات).
  4. Meta (مفتاح الـ Windows أو Command في الماك) ⊞

    • في الماك: Cmd + C للنسخ.

document.addEventListener('keydown', function(event) {
    let combination = [];
    
    if (event.ctrlKey) combination.push('Ctrl');
    if (event.altKey) combination.push('Alt');
    if (event.shiftKey) combination.push('Shift');
    combination.push(event.key);
    
    console.log('الأزرار المضغوطة:', combination.join(' + '));
});

٩. أخطاء شائعة لازم تتجنبها#

  • متعتمدش على keyCode لأنها deprecated
  • متنساش تعمل تشيك على القيم قبل ما تستخدمها
  • خلي بالك من الـ browser compatibility
  • استخدم event.key بدل event.which أو event.keyCode

١. “متعتمدش على keyCode لأنها deprecated”#

  • اللي كان بيحصل:
    قديمًا كنا نستخدم event.keyCode عشان نعرف رقم الزر المضغوط (مثلًا: 13 لزر Enter).
  • المشكلة:
    الخاصية دي اتقفلت (deprecated) في معايير الويب الحديثة، ومش مضمونة تشتغل في كل المتصفحات!
  • الحل:
    استخدم event.key (يرجع اسم الزر كـ string مثل “Enter”) أو event.code (يرجع الكود الفيزيائي للزر مثل “KeyA”).
// ❌ خطأ:
if (event.keyCode === 13) { ... }

// ✅ صح:
if (event.key === 'Enter') { ... }

٢. “متنساش تعمل تشيك على القيم قبل ما تستخدمها”#

  • اللي كان بيحصل: لو المستخدم دخل حاجة مش متوقعة (مثلًا: حروف في حقل أرقام)، التطبيق ممكن يقع!

  • الحل: دايماً افحص القيم قبل التعامل معها (Validation).

input.addEventListener('input', (e) => {
  const value = e.target.value;
  
  // مثال: تأكد إن القيمة رقم
  if (isNaN(value)) {
    alert('مينفعش تدخل حروف هنا يا معلم!');
    return;
  }
});

٣. “خلي بالك من الـ browser compatibility”#

  • اللي كان بيحصل: بعض الخصائص مش مدعومة في كل المتصفحات بنفس الشكل!
    • مثال: event.code مش مدعوم في IE أبدًا.
    • مثال: event.key بترجع قيم مختلفة في Safari للمفاتيح الخاصة (مثل: ß vs ss).
  • الحل:
    • شوف MDN عشان تتأكد من الدعم.
    • اختبر التطبيق على متصفحات مختلفة (Chrome, Firefox, Safari).

٤. “استخدم event.key بدل event.which أو event.keyCode”#

  • الفرق بينهم:
    • event.key: بيرجع اسم الزر مفهوم للإنسان (مثل “ArrowUp”, “a”).
    • event.which و event.keyCode: بيرجعوا أرقام قديمة ومش دقيقة.
// ❌ مش هتعرف تفرق بين الأحرف الكبيرة والصغيرة:
if (event.keyCode === 65) { ... } // 65 for both 'a' and 'A'

// ✅ هتعرف تفرق بسهولة:
if (event.key === 'a') { ... } // Lowercase
if (event.key === 'A') { ... } // Uppercase (مع Shift)

١٠. مثال متقدم - عمل undo/redo#

let history = [];
let currentIndex = -1;

document.addEventListener('keydown', function(event) {
    // Ctrl + Z (Undo)
    if (event.ctrlKey && event.key === 'z') {
        event.preventDefault();
        if (currentIndex > 0) {
            currentIndex--;
            applyChange(history[currentIndex]);
        }
    }
    
    // Ctrl + Y (Redo)
    if (event.ctrlKey && event.key === 'y') {
        event.preventDefault();
        if (currentIndex < history.length - 1) {
            currentIndex++;
            applyChange(history[currentIndex]);
        }
    }
});

function applyChange(change) {
    console.log('تطبيق التغيير:', change);
}

١١. Event Propagation (Bubbling vs. Capturing) مع keyboard events 🎮#

الفكرة الأساسية:#

لما بتعمل حدث (زي ضغط زر في الكيبورد)، بيتحرك الحدث في 3 مراحل:

  1. Capturing Phase (من الأعلى للأسفل): من الـ window حتى العنصر الهدف.
  2. Target Phase: عند العنصر الهدف نفسه.
  3. Bubbling Phase (من الأسفل للأعلى): من العنصر الهدف عائدة للـ window.

Event Propagation Phases


Event Bubbling vs. Capturing#

1. الـ Bubbling (الانتشار من الداخل للخارج):#

  • كيف يعمل:
    الحدث يبدأ من العنصر الهدف (مثل <input>) ويرتفع للأعلى نحو العناصر الأب (مثل <div> ثم <body>).
  • مثال عملي:
    <div id="parent">
      <input type="text" id="child">
    </div>
    
    <script>
      const parent = document.getElementById('parent');
      const child = document.getElementById('child');
    
      // Bubbling (القيمة الافتراضية)
      child.addEventListener('keydown', () => console.log('Child Bubbling'));
      parent.addEventListener('keydown', () => console.log('Parent Bubbling'));
    </script>
    
  • النتيجة عند الضغط على زر في الـ input:
Child Bubbling → Parent Bubbling

2. الـ Capturing (الانتشار من الخارج للداخل):#

  • كيف يعمل:
    الحدث يبدأ من العنصر الأب (مثل <body>) وينزل للأسفل نحو العنصر الهدف (مثل <div> ثم <input>).
  • مثال عملي:
    <div id="parent">
      <input type="text" id="child">
    </div>
    
    <script>
      const parent = document.getElementById('parent');
      const child = document.getElementById('child');
      // true لتفعيل Capturing
      child.addEventListener('keydown', () => console.log('Child Capturing'), true);
      parent.addEventListener('keydown', () => console.log('Parent Capturing'), true); // true لتفعيل Capturing
    </script>
    
  • النتيجة عند الضغط على زر في الـ input:
Parent Capturing → Child Capturing

ترتيب التنفيذ الكامل للأحداث (Bubbling + Capturing) 🔄#

// إضافة كل الأحداث معًا
parent.addEventListener('keydown', () => console.log('Parent Capturing'), true);
child.addEventListener('keydown', () => console.log('Child Capturing'), true);
child.addEventListener('keydown', () => console.log('Child Bubbling'));
parent.addEventListener('keydown', () => console.log('Parent Bubbling'));
  • النتيجة عند الضغط على زر في الـ input:
Parent Capturing → Child Capturing → Child Bubbling → Parent Bubbling

إيقاف الانتشار بـ stopPropagation() 🛑#

  • يمكنك إيقاف الانتشار باستخدام event.stopPropagation() داخل الـfunc التي تتبع الحدث.
child.addEventListener('keydown', (e) => {
  e.stopPropagation();
  console.log('Event Stopped!');
});
  • النتيجة الجديدة:
Parent Capturing → Child Capturing → Event Stopped!
  • ملاحظة —> تم إيقاف الانتشار بعد مرحلة الـ Target، فلم يصل الحدث لمرحلة الـ Bubbling!

حالات عملية لاستخدام Bubbling و Capturing 🛠️#

1. مثال على Capturing:#

إغلاق dropdown عند الضغط خارجَه.

document.addEventListener('click', (e) => {
  if (!dropdown.contains(e.target)) {
    dropdown.style.display = 'none';
  }
}, true); // Capturing لتأكيد التنفيذ قبل Bubbling
2. مثال على Bubbling:#

تنفيذ حدث على زر داخل جدول.

table.addEventListener('keydown', (e) => {
  if (e.target.tagName === 'BUTTON') {
    console.log('Button pressed!');
  }
}); // Bubbling (القيمة الافتراضية)

١٢. شرح كشف تتابع المفاتيح (Key Sequences)#

الفكرة العامة 🧠#

الكود ده بيسمع لضغطات الكيبورد، وبيتحقق إذا كان المستخدم داس تسلسل معين من المفاتيح (مثلًا: ↑ ↑ ↓ ↓ ← → ← → B A)، وبيستدعي وظيفة معينة لو التتابع صح.
دا مفيد جدًا لعمل:

  • أكواد سرية في الألعاب (مثل شفرات لعبة gata )
  • اختصارات كيبورد معقدة
  • تفعيل ميزات خفية في التطبيق

التنفيذ الفعلي 💻#

1. تعريف الكلاس KeySequenceDetector#

class KeySequenceDetector {
  constructor(sequence, callback) {
    this.sequence = sequence; // التسلسل المطلوب (مثل ['a', 'b', 'c'])
    this.callback = callback; // الوظيفة اللي هتتنفذ لو التتابع اتطابق
    this.currentSequence = []; // التتابع الحالي اللي المستخدم بيدوسه
    this.timeout = null; // مؤقت لمسح التتابع لو المستخدم وقف
    
    document.addEventListener('keydown', this.handleKeyPress.bind(this));
  }
}
  • sequence: التتابع اللي عايزين نتحقق منه (مصفوفة من أسماء المفاتيح).

  • callback: الوظيفة اللي هتشتغل لو التتابع اتطابق.

  • currentSequence: بيتخزن فيه المفاتيح اللي المستخدم داسها بالترتيب.

  • timeout: بيستخدم لإعادة ضبط التتابع لو المستخدم وقف عن الضغط لمدة ثانية.

2. تنفيذ الدالة handleKeyPress#

handleKeyPress(event) {
  clearTimeout(this.timeout); // امسح المؤقت القديم
  
  this.currentSequence.push(event.key); // أضف المفتاح المضغوط للتتابع الحالي
  
  // لو التتابع الحالي أطول من المطلوب، امسح أول عنصر
  if (this.currentSequence.length > this.sequence.length) {
    this.currentSequence.shift();
  }
  
  // لو التتابع الحالي مطابق للمطلوب، شغل الكallback وامسح التتابع
  if (this.isSequenceMatch()) {
    this.callback();
    this.currentSequence = [];
  }
  
  // ضع مؤقت جديد لمسح التتابع بعد ثانية
  this.timeout = setTimeout(() => {
    this.currentSequence = [];
  }, 1000);
}
  • كل ما يتم ضغط زر، بنضيفه لـ currentSequence.

  • لو طول التتابع الحالي زاد عن المطلوب، بنشيل أول زر (عشان نحافظ على الطول المطلوب).

  • لو التتابع الحالي مطابق للمطلوب، بنشغل الـ callback ونمسح التتابع.

  • بنضع مؤقت جديد لمسح التتابع بعد ثانية، لو المستخدم وقف عن الضغط لمدة ثانية.

3. تنفيذ دالة الـ (isSequenceMatch)#

isSequenceMatch() {
  return this.sequence.every((key, index) => 
    key === this.currentSequence[index]
  );
}
  • الدالة دي بتستخدم every عشان تتأكد أن كل عنصر في التتابع المطلوب (sequence) يطابق العنصر المقابل في التتابع الحالي (currentSequence).

4. استخدام الكلاس - مثال عملي: تفعيل كود Konami 🎮#

const konami = new KeySequenceDetector(
  ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'],
  () => console.log('Konami Code Activated! 🚀')
);

١٣. معالجة الأحداث المتزامنة (Concurrent Events)#

الفكرة العامة 🧠#

الكود ده بيسمح لك بتسجيل اختصارات كيبورد مكونة من عدة مفاتيح مضغوطة معًا (مثل Ctrl + Shift + P) وتنفيذ وظيفة معينة عند الضغط عليها.
دا مفيد لـ:

  • اختصارات التطبيقات (مثل حفظ الملف بـ Ctrl + S)
  • تفعيل ميزات خفية بواسطة تركيبات معقدة
  • التحكم في الألعاب باستخدام أكثر من زر في نفس الوقت

1. تعريف الكلاس KeyCombinationHandler#

class KeyCombinationHandler {
  constructor() {
    this.pressedKeys = new Set(); // المفاتيح المضغوطة حالياً
    this.combinations = new Map(); // التركيبات المسجلة مع callbacks
    
    document.addEventListener('keydown', this.handleKeyDown.bind(this));
    document.addEventListener('keyup', this.handleKeyUp.bind(this));
  }
}

2. دالة معالجة الضغط (handleKeyDown)#

handleKeyDown(event) {
  this.pressedKeys.add(event.key); // أضف المفتاح المضغوط
  this.checkCombinations(); // تحقق من وجود تركيبة مطابقة
}

3. دالة معالجة الرفع (handleKeyUp)#

handleKeyUp(event) {
  this.pressedKeys.delete(event.key); // أزل المفتاح المرفوع
}

عند رفع المفتاح، نزيله من pressedKeys.

4. تسجيل تركيبة جديدة (registerCombination)#

registerCombination(keys, callback) {
  const sortedKeys = keys.sort().join('+'); // رتب المفاتيح أبجدياً
  this.combinations.set(sortedKeys, callback);
}
  • مثال: [‘Control’, ‘Shift’, ‘P’] تصبح Control+P+Shift بعد الترتيب.
  • دا بيضمن إن الترتيب اللي تضغط فيه المفاتيح مش مهم!

5. التحقق من التركيبات (checkCombinations)#

checkCombinations() {
  const currentKeys = Array.from(this.pressedKeys).sort().join('+');
  if (this.combinations.has(currentKeys)) {
    this.combinations.get(currentKeys)(); // نفذ الcallback
  }
}
  • بنرتب المفاتيح المضغوطة حالياً أبجدياً.

  • بنشوف إذا كانت موجودة في الـ combinations.

  • لو وجدت match، بنشغل الcallback.

مثال عملي: تفعيل اختصار طباعة 🖨️#

const handler = new KeyCombinationHandler();
handler.registerCombination(['Control', 'Shift', 'p'], () => {
  console.log('تم تفعيل وضع الطباعة!');
  window.print(); // افتح نافذة الطباعة
});
  • لما المستخدم يضغط Ctrl + Shift + P ➔ هتفتح نافذة الطباعة.

١٤. تحسين الأداء (Performance Optimization)#

Debouncing للـ Keyboard Events#

function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

const efficientKeyHandler = debounce((event) => {
    console.log('تم معالجة الحدث:', event.key);
}, 150);

document.addEventListener('keydown', efficientKeyHandler);

١٥. معالجة أحداث IME (Input Method Editor)#

ما هو IME؟ أو Composition#

  • أداة تتيح كتابة أحرف معقدة (كالصينية/اليابانية) بلوحة مفاتيح عربية/لاتينية.
  • مثال: كتابة “nihao” لتحويلها إلى “你好” (مرحبا بالصينية).

كيف تعمل العملية خطوة بخطوة؟ 🔄#

  1. البداية (compositionstart)

    • المستخدم يفتح لوحة IME أو يبدأ كتابة مقطع صوتي (مثل Pinyin للصينية).
    • مثال: الضغط على زر لتحويل لوحة المفاتيح إلى وضع الإدخال الصيني.
  2. التحديثات (compositionupdate)

    • مع كل حرف يُكتب، يتم تحديث النص المؤقت (المرحلة الانتقالية).
    • مثال: كتابة “ni” → “ن” ثم “hao” → “ه” في Pinyin يعطي تحديثات متتالية.
  3. الانتهاء (compositionend)

    • المستخدم يختار الحرف النهائي من القائمة أو يضغط Enter.
    • مثال: اختيار “你好” من القائمة بعد كتابة “nihao”.

لماذا تسمى “Composition”؟ 🧩#

  • لأنها عملية تأليف/تركيب الأحرف من مكونات أبسط:
    • أصوات (Pinyin للصينية)
    • مقاطع (Hiragana/katakana لليابانية)
    • أشكال (Strokes للكورية)

مثال حي من واقع الحياة 🌏#

الإدخالالمرحلةالناتج
ncompositionstart(بدء التركيب)
nicompositionupdate你 (مؤقت)
nihcompositionupdate你ه (مؤقت)
nihacompositionupdate你好 (مؤقت)
nihao + Entercompositionend你好 (نهائي)

الفرق بين الـ Composition والكتابة العادية ⌨️#

الكتابة العاديةComposition
الأحداثinput, keydowncompositionstart/update/end
السرعةفوريةتحتاج تأكيد من المستخدم
الاستخداماللغات اللاتينيةاللغات الآسيوية المعقدة

الأحداث الأساسية:#

1. حدث compositionstart 🟢#

input.addEventListener('compositionstart', () => {
  console.log('بدء تركيب النص');
});
متى يحدث؟#
  • عند بدء إدخال نص عبر IME (مثل فتح لوحة الرموز الصينية).
الاستخدام#
  • تهيئة حالة التطبيق لاستقبال إدخال معقد.

2. حدث compositionupdate 🟡#

input.addEventListener('compositionupdate', (e) => {
  console.log('تحديث:', e.data);
});
متى يحدث؟#
  • مع كل تغيير في النص المؤقت أثناء الكتابة عبر IME.
الاستخدام#
  • كتابة “k” ثم “a” ثم “n” في IME الياباني يعطي تحديثات متتالية.

3. حدث compositionend 🔴#

input.addEventListener('compositionend', (e) => {
  console.log('نص نهائي:', e.data);
});
متى يحدث؟#
  • عند انتهاء الإدخال (بالضغط Enter أو اختيار من القائمة).
الاستخدام#
  • معالجة النص النهائي (مثل حفظه أو إرساله).

١٦. Virtual Keyboard Handler#

الهدف منه 🎯#

هذا الكود بيسمح لك بعمل لوحة مفاتيح افتراضية بتغيير تخطيط الحروف حسب حالة الـ Shift.
مثلاً:

  • في الوضع العادي (default): الضغط على q ينتج ض
  • مع الضغط على Shift (shift): الضغط على q ينتج َ (حركة فتحة)

1. تعريف الكلاس وتهيئة المتغيرات#

class VirtualKeyboardHandler {
  constructor() {
    this.layout = new Map(); // خزانة التخطيطات المختلفة
    this.currentLayout = 'default'; // التخطيط الحالي
    this.initializeLayout(); // تعبئة الخزانة بالتخطيطات
  }
}
  • layout: خريطة (Map) بتخزن كل تخطيطات الكيبورد (عادي - مع Shift - إلخ)
  • currentLayout: التخطيط الحالي المستخدم

2. تهيئة التخطيطات (initializeLayout)#

initializeLayout() {
  // التخطيط العادي (بدون Shift)
  this.layout.set('default', {
    'q': 'ض', 'w': 'ص', 'e': 'ث', 'r': 'ق',
    // ... باقي الحروف
  });
  
  // التخطيط مع Shift (للحركات أو الرموز)
  this.layout.set('shift', {
    'q': 'َ', 'w': 'ً', 'e': 'ُ', 'r': 'ٌ',
    // ... باقي الرموز
  });
}
  • كل تخطيط عبارة عن كائن بيحول المفاتيح (مثل q) إلى قيمها (مثل ض)
  • default: التخطيط الأساسي للكيبورد العربية
  • shift: تخطيط الحركات (الفتحة، الضمة، إلخ)

3. معالجة الضغط على المفاتيح (handleKeyPress)#

handleKeyPress(event) {
  // تحديد التخطيط بناءً على Shift
  const layoutMap = this.layout.get(event.shiftKey ? 'shift' : 'default');
  
  // لو الزر موجود في التخطيط الحالي
  if (layoutMap.hasOwnProperty(event.key)) {
    event.preventDefault(); // امنع الكتابة العادية
    return layoutMap[event.key]; // أرجع الحرف/الرمز المطلوب
  }
  
  return event.key; // لو الزر مش موجود، أرجع قيمته الأصلية
}
  • event.shiftKey: بيتحقق إذا كان زر Shift مضغوط
  • preventDefault(): بيوقف السلوك الافتراضي للزر (مثل كتابة q بالإنجليزية)
  • return layoutMap[event.key]: إرجاع القيمة المطابقة للمفتاح المضغوط

4. تنفيذ الكود#

const keyboard = new VirtualKeyboardHandler();

// عند ضغط زر في الكيبورد
document.addEventListener('keydown', (e) => {
  const output = keyboard.handleKeyPress(e);
  console.log('الحرف الناتج:', output);
});

نصائح إضافية للأداء المتقدم#

  • استخدم requestAnimationFrame مع الـ keyboard events اللي بتحتاج تحديث مرئي
  • اعمل cleanup للـ event listeners باستخدام AbortController
  • استخدم WeakMap للتخزين المؤقت للبيانات المرتبطة بالعناصر
  • اعمل throttling للأحداث المتكررة زي الـ key repeat
  • استخدم Web Workers للعمليات المعقدة

Join our whatsapp group here
My Channel here