واجهة برمجة التطبيقات لسحب وإفلات 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 بشكل متكرّر. يؤدي ذلك إلى تحميل المتصفّح الكثير من عمليات المعالجة غير الضرورية، ما قد يؤثر في تجربة المستخدم. ننصحك بشدة بتقليل عمليات redraws، وإذا كنت بحاجة إلى استخدام 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.
  }
}

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

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