واجهة برمجة التطبيقات لسحب وإفلات HTML5

تشرح هذه المشاركة أساسيات السحب والإفلات.

في معظم المتصفّحات، تكون مقاطع النص والصور والروابط قابلة للسحب تلقائيًا. على سبيل المثال، إذا سحبت رابطًا على صفحة ويب، سيظهر لك مربّع صغير يحتوي على عنوان وعنوان URL، ويمكنك إفلاتهما على شريط العناوين أو على سطح المكتب لإنشاء اختصار أو الانتقال إلى الرابط. لجعل أنواع أخرى من المحتوى قابلة للسحب، عليك استخدام واجهات برمجة تطبيقات HTML5 Drag and Drop.

لجعل كائن قابلاً للسحب، اضبط draggable=true على هذا العنصر. يمكن سحب أي عنصر تقريبًا، بما في ذلك الصور أو الملفات أو الروابط أو أي علامات ترميز على صفحتك.

ينشئ المثال التالي واجهة لإعادة ترتيب الأعمدة التي تم تنظيمها باستخدام CSS Grid. يبدو الترميز الأساسي للأعمدة كالتالي، مع ضبط السمة draggable لكل عمود على true:

<div class="container">
  <div draggable="true" class="box">A</div>
  <div draggable="true" class="box">B</div>
  <div draggable="true" class="box">C</div>
</div>

في ما يلي CSS لعنصرَي الحاوية والمربّع. إنّ ملف CSS الوحيد المرتبط بميزة السحب هو ملف cursor: move . يتحكم باقي التعليمات البرمجية في تخطيط وتصميم عناصر الحاوية والمربع.

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
}

.box {
  border: 3px solid #666;
  background-color: #ddd;
  border-radius: .5em;
  padding: 10px;
  cursor: move;
}

في هذه المرحلة، يمكنك سحب العناصر، ولكن لا يحدث شيء آخر. لإضافة سلوك، عليك استخدام JavaScript API.

الاستماع إلى أحداث السحب

لمراقبة عملية السحب، يمكنك رصد أي من الأحداث التالية:

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

بدء تسلسل سحب وإنهائه

بعد تحديد سمات draggable="true" في المحتوى الخاص بك، أرفِق معالِج أحداث dragstart لبدء تسلسل السحب لكل عمود.

يضبط هذا الرمز درجة تعتيم العمود على 40٪ عندما يبدأ المستخدم في سحبه، ثم يعيده إلى 100٪ عند انتهاء حدث السحب.

function handleDragStart(e) {
  this.style.opacity = '0.4';
}

function handleDragEnd(e) {
  this.style.opacity = '1';
}

let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
  item.addEventListener('dragstart', handleDragStart);
  item.addEventListener('dragend', handleDragEnd);
});

ويمكن الاطّلاع على النتيجة في العرض التوضيحي التالي لتطبيق Glitch. اسحب عنصرًا، وسيتغيّر مستوى شفافيته. بما أنّ العنصر المصدر يتضمّن الحدث dragstart، فإنّ ضبط this.style.opacity على ‎40% يقدّم للمستخدم ملاحظات مرئية بأنّ هذا العنصر هو العنصر المحدّد حاليًا الذي يتم نقله. عند إسقاط العنصر، يعود العنصر المصدر إلى معدل تعتيم بنسبة 100%، على الرغم من أنك لم تحدد سلوك الانخفاض بعد.

إضافة إشارات مرئية إضافية

لمساعدة المستخدم في فهم كيفية التفاعل مع واجهتك، استخدِم معالِجات الأحداث dragenter وdragover وdragleave. في هذا المثال، تكون الأعمدة هي أهداف طرحية بالإضافة إلى قابلة للسحب. يمكنك مساعدة المستخدم في فهم ذلك من خلال جعل الحدود متقطّعة عندما يُمسك بعنصر يتم سحبه فوق عمود. على سبيل المثال، يمكنك في خدمة مقارنة الأسعار (CSS) إنشاء فئة over للعناصر التي تمثّل أهداف انخفاض:

.box.over {
  border: 3px dotted #666;
}

بعد ذلك، اضبط معالِجات الأحداث في JavaScript وأضِف الفئة over عند سحب العمود واحذفها عند مغادرة العنصر الذي تم سحبه. في معالج dragend، نحرص أيضًا على إزالة الفئات في نهاية عملية السحب.

document.addEventListener('DOMContentLoaded', (event) => {

  function handleDragStart(e) {
    this.style.opacity = '0.4';
  }

  function handleDragEnd(e) {
    this.style.opacity = '1';

    items.forEach(function (item) {
      item.classList.remove('over');
    });
  }

  function handleDragOver(e) {
    e.preventDefault();
    return false;
  }

  function handleDragEnter(e) {
    this.classList.add('over');
  }

  function handleDragLeave(e) {
    this.classList.remove('over');
  }

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });
});

هناك عدة نقاط تستحق تناولها في هذه التعليمة البرمجية:

  • الإجراء التلقائي لحدث dragover هو ضبط السمة dataTransfer.dropEffect على "none". تتم مناقشة سمة dropEffect لاحقًا في هذه الصفحة. في الوقت الحالي، يُرجى العِلم أنّه يمنع بدء الحدث drop. لتجاوز هذا السلوك، اطلب e.preventDefault(). هناك ممارسة جيدة أخرى، وهي عرض false في المعالج نفسه.

  • يتم استخدام معالِج الحدث dragenter لتبديل فئة over بدلاً من dragover. في حال استخدام dragover، يتم تنشيط الحدث بشكل متكرّر بينما يمسك المستخدم العنصر الذي تم سحبه فوق عمود، ما يؤدي إلى تبديل فئة CSS بشكل متكرّر. يؤدي ذلك إلى تحميل المتصفّح الكثير من عمليات المعالجة غير الضرورية، ما قد يؤثر في تجربة المستخدم. ننصحك بشدة بتقليل عمليات إعادة الرسم، وإذا كنت بحاجة إلى استخدام dragover، ننصحك بتقييد أو خفض صوت أداة معالجة الأحداث.

إكمال عملية الطرح

لمعالجة عملية الطرح، أضِف أداة معالجة أحداث لحدث drop. في معالِج drop ، عليك منع السلوك التلقائي للمتصفّح في عمليات إسقاط البيانات، والتي عادةً ما تكون نوعًا من عمليات إعادة التوجيه المزعجة. ولإجراء ذلك، يمكنك الاتصال بالرقم e.stopPropagation().

function handleDrop(e) {
  e.stopPropagation(); // stops the browser from redirecting.
  return false;
}

تأكد من تسجيل المعالج الجديد إلى جانب المعالجات الأخرى:

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });

إذا شغّلت الرمز في هذه المرحلة، لن يتم إسقاط العنصر في الموقع الجديد. لتنفيذ ذلك، استخدِم عنصر DataTransfer .

تحتفظ السمة dataTransfer بالبيانات المُرسَلة في إجراء سحب. يتم ضبط السمة dataTransfer في حدث dragstart وتتم قراءتها أو معالجتها في حدث الإفلات. يتيح لك الإجراء e.dataTransfer.setData(mimeType, dataPayload) ضبط نوع MIME للكائن وحمولة البيانات.

في هذا المثال، سنسمح للمستخدمين بإعادة ترتيب الأعمدة. للقيام بذلك، تحتاج أولاً إلى تخزين HTML للعنصر المصدر عند بدء السحب:

function handleDragStart(e) {
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

في الحدث drop، يمكنك معالجة انخفاض العمود من خلال ضبط رمز HTML للعمود المصدر على رمز HTML للعمود المستهدَف الذي أدرجت البيانات فيه. يتضمن هذا التحقق من أن المستخدم لا يعود إلى نفس العمود الذي سحب منه.

function handleDrop(e) {
  e.stopPropagation();

  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

يمكنك الاطّلاع على النتيجة في العرض الترويجي التالي. لكي يعمل ذلك، ستحتاج إلى متصفح سطح مكتب. لا تتوفّر واجهة برمجة التطبيقات Drag and Drop API على الأجهزة الجوّالة. اسحب عمود A فوق عمود B ثم ارفع إصبعك عن الماوس، وستلاحظ تغيُّر مواضعهما:

المزيد من خصائص السحب

يعرِض عنصر dataTransfer سمات لتقديم ملاحظات مرئية للمستخدِم أثناء عملية السحب والتحكّم في كيفية استجابة كلّ هدف إسقاط لنوع بيانات معيّن.

  • dataTransfer.effectAllowed يحدّد "نوع السحب" الذي يمكن للمستخدم تنفيذه على العنصر. وهي تُستخدَم في نموذج معالجة السحب والإفلات لإعداد dropEffect أثناء حدثَي dragenter وdragover. يمكن أن تتضمّن الخاصية القيم التالية: none وcopy وcopyLink وcopyMove وlink وlinkMove وmove وall وuninitialized.
  • يتحكّم مقياس dataTransfer.dropEffect في الملاحظات التي يتلقّاها المستخدم أثناء حدثَي dragenter وdragover . عندما يمسك المستخدم بالمؤشر فوق عنصر هدف، يشير مؤشر المتصفّح إلى نوع العملية التي ستحدث، مثل النسخ أو النقل. يمكن أن يتخذ التأثير إحدى القيم التالية: none أو copy أو link أو move.
  • e.dataTransfer.setDragImage(imgElement, x, y) يعني ذلك أنّه بدلاً من استخدام الملاحظات التلقائية "للصورة الشبحية" في المتصفّح، يمكنك ضبط رمز سحب.

تحميل ملف

يستخدم هذا المثال البسيط عمودًا باعتباره مصدر السحب وهدف السحب. قد يحدث هذا في واجهة مستخدم تطلب من المستخدم إعادة ترتيب العناصر. في بعض الحالات، قد يكون هدف السحب ومصدره نوعَين مختلفَين من العناصر، كما هو الحال في واجهة يحتاج فيها المستخدم إلى اختيار صورة واحدة كصورة رئيسية لمنتج من خلال سحب الصورة المحدّدة إلى هدف.

يُستخدم السحب والإفلات بشكل متكرر للسماح للمستخدمين بسحب العناصر من سطح المكتب إلى التطبيق. ويكمن الاختلاف الرئيسي في معالِج drop. بدلاً من استخدام dataTransfer.getData() للوصول إلى الملفات، يتم تضمين بياناتها في موقع dataTransfer.files:

function handleDrop(e) {
  e.stopPropagation(); // Stops some browsers from redirecting.
  e.preventDefault();

  var files = e.dataTransfer.files;
  for (var i = 0, f; (f = files[i]); i++) {
    // Read the File objects in this FileList.
  }
}

يمكنك العثور على مزيد من المعلومات حول ذلك في مقالة السحب والإفلات المخصّص.

مزيد من الموارد