إنشاء عنصر قابل للسحب

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

استخدام اسم معرِّف مخصّص

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

لا تضِف role="application" إلى الاسم المعرِّف. يؤدي ذلك إلى تغيير تفاعل التكنولوجيا المساعِدة بدون داعٍ.

يجب تطبيق cursor: grab/grabbing وtouch-action: none وuser-select: none و-webkit-user-select: none على المقبض، وليس على العنصر القابل للسحب بأكمله، لكي يظل النص خارجه قابلاً للاختيار وتحتفظ إيماءات اللمس بسلوكها المعتاد:

.drag-handle {
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.drag-handle.dragging {
  cursor: grabbing;
}

الحفاظ على موضع المؤشر وإمكانية الوصول إليه

استخدِم نموذج ضبط الموضع نسبيًا لإطار العرض، مثل position: fixed، عند تثبيت الحركة في إطار العرض.

  • بدء عملية سحب لزر المؤشر الأساسي فقط (event.button === 0)
  • سجِّل إزاحة المؤشر على pointerdown واستخدِمها في كل حركة حتى لا يقفز العنصر عند سحبه بعيدًا عن زاويته.
  • يجب إبقاء السحب نشطًا بعد أن يغادر المؤشر المقبض. يتم ضبط الإعداد التلقائي على setPointerCapture() ومحو حالة السحب في lostpointercapture. إذا كان من الممكن إزالة المقبض أو إعادة عرضه في منتصف عملية السحب، استخدِم مستمعي pointermove وpointerup وpointercancel على مستوى المستند بدلاً من ذلك، لأنّه يتم فقدان الالتقاط في هذه الحالة.
  • يمكنك الانتظار حتى يتم تحريك المؤشر بمقدار 4 بكسل تقريبًا قبل الدخول في حالة السحب، حتى لا يؤدي النقر على المقبض إلى تفعيل نمط السحب.

باستخدام ميزة "التقاط المؤشر"، يمكن أن تظل أدوات معالجة الحركة على المقبض:

handle.addEventListener('pointerdown', (event) => {
  if (event.button !== 0 || event.target.closest('button, input, select, textarea, a')) return;

  const rect = draggable.getBoundingClientRect();
  offsetX = event.clientX - rect.left;
  offsetY = event.clientY - rect.top;
  handle.setPointerCapture(event.pointerId);
});

handle.addEventListener('pointermove', (event) => {
  if (!handle.hasPointerCapture(event.pointerId)) return;
  handle.classList.add('dragging');
  moveTo(event.clientX - offsetX, event.clientY - offsetY);
});

handle.addEventListener('lostpointercapture', () => {
  handle.classList.remove('dragging');
});

function moveTo(left, top) {
  const maxLeft = Math.max(0, window.innerWidth - draggable.offsetWidth);
  const maxTop = Math.max(0, window.innerHeight - draggable.offsetHeight);

  draggable.style.left = `${Math.max(0, Math.min(left, maxLeft))}px`;
  draggable.style.top = `${Math.max(0, Math.min(top, maxTop))}px`;
}

يجب تثبيت كل موضع للمؤشر ولوحة المفاتيح من خلال المساعد نفسه ليبقى العنصر قابلاً للوصول إليه. أعِد تثبيت موضعه الحالي بعد تغيير حجم إطار العرض أو العنصر. بالنسبة إلى العناصر التي تتطلّب تنسيقًا كبيرًا، ننصحك بالترجمة أثناء السحب لتجنُّب التنسيق لكل إطار، ثم ضبط الموضع النهائي على left وtop عند الإفلات.

توفير إمكانية التنقّل باستخدام لوحة المفاتيح

عندما يكون المقبض في وضع التركيز، حرِّك العنصر باستخدام مفاتيح الأسهم واستخدِم preventDefault() لهذه المفاتيح. استخدِم أداة المساعدة نفسها لتحديد الموضع الثابت مثل حركة المؤشر. تُعدّ الزيادة بمقدار 15 بكسل نقطة بداية معقولة، ويمكنك تعديلها حسب حجم المكوّن واحتياجات الدقة.

ملاحظة خاصة بمربّع الحوار

يمكن لـ <dialog> أو [popover] استخدام هذه الآلية مع العنوان الخاص به كمؤشر. اضبط margin: 0 قبل تعيين left وtop، لأنّ الهوامش التلقائية للمتصفّح تتداخل مع تحديد الموضع يدويًا. للاطّلاع على سلوك فتح مربّعات الحوار والنوافذ المنبثقة وإغلاقها والتركيز عليها وإغلاقها، يُرجى مراجعة التحكّم في مربّعات الحوار والنوافذ المنبثقة التعريفية وإغلاق مربّعات الحوار من خلال عناصر التحكّم في المنصة.

استراتيجيات احتياطية

عندما يؤدي السحب إلى تغيير موضع أحد المكوّنات فقط، يمكنك التعامل معه على أنّه تحسين تدريجي: بدون JavaScript، يمكنك إبقاء العنصر ومحتواه وعناصر التحكّم فيه مرئية وقابلة للقراءة وتعمل في موضع تلقائي معقول. لا تعرض مقبض سحب غير فعّال: إذا كان المقبض عبارة عن عنصر تحكّم مستقل في الإمساك، يمكنك إخفاؤه تلقائيًا وإظهاره عند بدء تشغيل JavaScript؛ إذا كان العنوان يتضاعف كمقبض، يمكنك إرفاق إمكانيات السحب الخاصة به عند بدء تشغيل JavaScript.

يجب تضمين -webkit-user-select: none قبل user-select: none لكي يظلّ تحديد النص غير مفعَّل أثناء السحب في Safari.