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

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

إنشاء محتوى قابل للسحب

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

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

ينشئ المثال التالي واجهة لإعادة ترتيب الأعمدة التي تم وضعها باستخدام شبكة CSS. يظهر الترميز الأساسي للأعمدة على هذا النحو، مع ضبط السمة 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;
}

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

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

مزيد من المراجع