Natives Drag-and-Drop in HTML5-Kampf und -Analyse --- Realisierung von Drag-and-Drop-Komponenten in React

Vorwort: Drag-and-Drop-Komponenten sind ein weit verbreitetes Feature in der Front-End-Entwicklung Ob Sie jetzt React oder Vue verwenden, es gibt viele vorgefertigte Drag-and-Drop-Komponenten, die verwendet werden können. Manchmal müssen Sie es jedoch möglicherweise noch selbst implementieren, daher müssen Sie die Implementierungsprinzipien verstehen.

Hintergrund: Ich habe diese Woche eine Aufgabe übernommen, um die Shuttle-Box von antD ziehbar zu machen (sowohl links als auch rechts können gezogen werden, und Mehrfachauswahl-Drag-and-Drop wird unterstützt). Nachdem ich mir die API von antD angesehen hatte, stellte ich fest, dass es keine Konfiguration gibt, also beschloss ich, eine native Implementierung selbst zu schreiben. Ich borgte mir von der Demo des großen Kerls, sah, wie Drag & Drop implementiert ist, und wendete es dann an das Projekt.

Verstehen Sie Drag & Drop in HTML

Heutzutage verlassen sich die meisten Frontend-Drag-and-Drop-Komponenten auf die Drag-and-Drop-Schnittstelle, die nativ von HTML5 bereitgestellt wird. Bevor Sie also beginnen, Komponenten mit einem bestimmten Framework zu kapseln , müssen Sie diese nativen Schnittstellenfunktionen herausfinden.

Das Drag-Event wird dem DOM-Mouse-Event von HTML 5 hinzugefügt. Für ein Seitenelement mit dem Draggable -Attribut set wird , solange es auf ein Element mit demselben Droppable-Attribut gezogen wird, eine vollständige Drag-and-Drop-Funktion ausgeführt. Während dieses Vorgangs werden jeweils die folgenden Ereignistypen ausgelöst: 

Tipp: Links und Bilder sind standardmäßig verschiebbar, es ist kein verschiebbares Attribut erforderlich.

draggable hat drei Werte wie folgt:

draggable = true (das Element kann gezogen werden)

draggable = false (das Element kann nicht gezogen werden)

draggable = auto (der Browser kann selbstständig entscheiden, ob ein Element gezogen werden kann)

Durch das gezogene Element (Quellelement) ausgelöste Ereignisse:

* ondragstart - löst aus, wenn ein Element gezogen wird (das Dragstart-Ereignis wird ausgelöst, wenn die Maustaste gedrückt wird und die Maus sich zu bewegen beginnt) * ondrag - löst aus, wenn das Element gezogen wird (das
Drag-Ereignis wird weiterhin ausgelöst, während das Element gezogen wird, ähnlich wie mousemove- und touchmove-Ereignisse)
* ondragend – Wird ausgelöst, nachdem der Benutzer das Ziehen des Elements beendet hat, wenn das Ziehen stoppt (unabhängig davon, ob das Element auf einem gültigen Ablageziel oder einem ungültigen Ablageziel platziert wird), das Dragend-Ereignis wird passieren.

Hinweis: Standardmäßig ändert der Browser das Aussehen des gezogenen Elements während des Ziehens nicht. Du kannst es aber selbst modifizieren. Die meisten Browser erstellen jedoch eine durchsichtige Kopie des gezogenen Elements, die immer dem Cursor folgt. Wenn ein Element in ein gültiges Ablageziel gezogen wird, werden die folgenden Ereignisse ausgelöst:

Ereignis, das ausgelöst wird, wenn das gezogene Element losgelassen wird (Zielelement) :

* ondraenter - löst dieses Ereignis aus, wenn das von der Maus gezogene Objekt in seinen Containerbereich eintritt (ähnlich dem Mouseover-Ereignis) *
ondragover - löst aus, wenn sich das von der Maus gezogene Objekt an einem Element vorbeibewegt (das Dragover-Ereignis wird kontinuierlich ausgelöst, alle 100 Wird einmal in Millisekunden ausgelöst, ähnlich
dem mousemove-
Ereignis

Achtung: Unter dragenter-, dragover- (dragend)-Ereignissen müssen wir das Standardverhalten des Browsers verhindern, damit die Elemente, die wir ziehen, zu lösbaren Elementen werden.

Nachdem Sie sich mit diesen grundlegenden Ereignistypen vertraut gemacht haben, besteht die Implementierung darin, die entsprechenden Ereignisverarbeitungsfunktionen an das Quellobjekt bzw. das Zielobjekt zu binden und auf die Verarbeitung zu warten.

Neben diesen Drag-and-Drop-Ereignisschnittstellen müssen wir in der Regel auch die Übertragung von Daten abwickeln. Auch HTML5 bietet eine einfache Schnittstelle: In der entsprechenden Listening-Funktion können wir das Event-Objekt holen, innerhalb dieses Objekts gibt es eine DataTransfer-Schnittstelle, die speziell zum Speichern des Dateninhalts des Events genutzt werden kann. Die DataTransfer entsprechenden Methoden sind:

Datenverarbeitung erfolgt per Drag and Drop

* event.dataTransfer.setData(format, data) Fügen Sie Drag-and-Drop-Daten hinzu, diese Methode erhält zwei Parameter, der erste Parameter ist der Datentyp (kann angepasst werden, füllen Sie nur ähnlichen "text/plain" oder "textml" Text aus, der darstellt der MIME-Typ), der zweite Parameter sind die zu tragenden Daten;

* event.dataTransfer.getData(format) Umgekehrte Operation, Daten abrufen, nur einen Parameter akzeptieren, nämlich Datentyp;

* event.dataTransfer.clearData(format) Daten löschen; löscht die Daten im angegebenen Format aus dem dataTransfer-Objekt, der Parameter ist optional, wenn kein Parameter angegeben wird, werden alle Daten im Objekt gelöscht.

* event.dataTransfer.setDragImage(el, x, y) kann das Bild neben der Maus während des Ziehens und Ablegens anpassen; das Symbol der Drag-and-Drop-Operation festlegen, wobei el das benutzerdefinierte Symbol darstellt und x den Abstand zwischen den Symbol und der Maus in horizontaler Richtung, y repräsentiert den vertikalen Abstand zwischen dem Symbol und der Maus

Das Folgende ist eine einfache Demo~

import React, { useState, useRef } from 'react';

const list = [
  {
    uid: '1',
    text: '序列1',
  },
  {
    uid: '2',
    text: '序列2',
  },
  {
    uid: '3',
    text: '序列3',
  },
  {
    uid: '4',
    text: '序列4',
  },
  {
    uid: '5',
    text: '序列5',
  },
];

const Drag: React.FC<{}> = () => {
  // console.log('list', list)
  const [rightList, setRightList] = useState(list);
  const [leftList, setLeftList] = useState([]);

  //鼠标华划过接受拖拽元素的事件
  const handleDrop = (callBack, e, arrow) => {
    e.preventDefault();  //阻止默认事件:防止打开拖拽元素的url(Firefox)
    console.log('handleDrop', callBack, e, arrow);
    const {
      dataset: { id },
    } = e.target;
    console.log('id', id);

    const curData = JSON.parse(e.dataTransfer.getData('itemData'));
    console.log('curData', curData);
    callBack((prevData) => {
      console.log('prevData', prevData);
      const diffData = prevData.filter((item) => item.uid !== curData.uid);
      // id 不存在是在不同盒子内拖拽  存在则是在本身盒子内拖拽
      // 项目中发现, 只要鼠标经过元素, 且元素被自定义过data-id属性, id都可以都能拿到!
      if (!id) return [...diffData, curData];
      // 找到鼠标划过的目标元素的其盒子内的位置
      const index = diffData.findIndex((item) => item.uid === id);
      //把拖拽元素放置在鼠标划过元素的上方
      diffData.splice(index, 0, curData);
      return diffData;
    });
    //朝左拖拽
    if (arrow === 'left') {
      setRightList((prvData) => prvData.filter((item) => item.uid !== curData.uid));
    }
    // 朝右拖拽
    else {
      setLeftList((prvData) => prvData.filter((item) => item.uid !== curData.uid));
    }
  };
  // 拖拽元素进入目标元素时触发事件-为目标元素添加拖拽元素进入时的样式效果
  const handleDragEnter = (e) => e.target.classList.add('over');

  // 拖拽元素离开目标元素时触发事件-移除目标元素的样式效果
  const handleDragLeave = (e) => e.target.classList.remove('over');

  return (
    <div>
      <div
        style={
   
   { width: '300px', height: '300px' }}
        onDragOver={(e) => {
          e.preventDefault();
        }}
        onDrop={(e) => handleDrop(setLeftList, e, 'left')}
      >
        {leftList.map((item) => (
          <div
            className="item"
            draggable
            key={item.uid}
            data-id={item.uid}
            onDragStart={(e) => {
              e.dataTransfer.setData('itemData', JSON.stringify(item));
            }}
          >
            {item.text}
          </div>
        ))}
      </div>
      <div
        style={
   
   { width: '300px', height: '300px' }}
        onDragOver={(e) => {
          e.preventDefault();
        }}
        onDrop={(e) => handleDrop(setRightList, e, 'right')}
        onDragEnter={handleDragEnter}
        onDragLeave={handleDragLeave}
      >
        {rightList.map((item) => (
          <div
            className="item"
            draggable
            key={item.uid}
            data-id={item.uid}
            onDragStart={(e) => {
              e.dataTransfer.setData('itemData', JSON.stringify(item));
            }}
          >
            {item.text}
          </div>
        ))}
      </div>
    </div>
  );
};

export default Drag;

Zusätzliche Stilverarbeitung (sehen Sie es, wenn eine benutzerdefinierte Stilverarbeitung erforderlich ist)

Fügen Sie Drag-and-Drop-Effekte hinzu

Um den visuellen Effekt von Drag & Drop zu erreichen, müssen die beiden Eigenschaften effectAllowed und dropEffect kombiniert verwendet werden.

dropEffect: Legen Sie das vom Drag-and-Drop-Ziel zugelassene Drag-and-Drop-Verhalten fest. Wenn das hier festgelegte Drag-and-Drop-Verhalten nicht innerhalb des von der Eigenschaft effectAllowed festgelegten Drag-and-Drop-Verhaltens liegt, wird das Drag-and-Drop-Verhalten angezeigt -drop-Operation schlägt fehl. Der Attributwert darf nur "null", "copy", "link" oder "move" sein;

effectAllowed: Legt das vom ziehenden Element erlaubte Ziehverhalten fest. Der Attributwert kann „none“, „copy“, „copyLink“, „copyMove“, „link“, „linkMove“, „move“, „all“ oder „ nicht initialisiert";

// 先设置一些效果常量:(内置属性) 
// 可以通过props传递

export const All = "all";

export const Move = "move";

export const Copy = "copy";

export const Link = "link";

export const CopyOrMove = "copyMove";

export const CopyOrLink = "copyLink";

export const LinkOrMove = "linkMove";

export const None = "none";


// 被拖拽组件
const Drag = (props) => {

    // 如果想自定义拖拽图像(非必须)
    const [isDragging, setIsDragging] = React.useState(false);
    const image = React.useRef(null);
    React.useEffect(() => {
        image.current = null;
        if (props.dragImage) {
            image.current = new Image();
            image.current.src = props.dragImage;
        }

    }, [props.dragImage]);

    // 拖拽开始
    const startDrag = e => {
        setIsDragging(true);
        // 设置数据(必须)
        e.dataTransfer.setData("drag-item", props.dataItem);
        // 设置effectAllowed属性添加效果(非必须)
        e.dataTransfer.effectAllowed = props.dropEffect;
        // 设置图片(非必须)
        if (image.current) {
            e.dataTransfer.setDragImage(image.current, 0, 0);
        }
    };

    // 拖拽结束
    const dragEnd = () => setIsDragging(false);  

    return (
        {props.children}
    );
}


// 目标组件
const DropTarget = (props) => {

    // 滑过时
    const dragOver = e => {
        // 阻止默认行为
        e.preventDefault();
        // 添加效果(非必须)
        e.dataTransfer.dropEffect = props.dropEffect;
    }

    // 进入时(非必须)
    const dragEnter = e => {
        e.dataTransfer.dropEffect = props.dropEffect;
    }

    // 释放时
    const drop = e => {
        // 获取数据(必须)
        const droppedItem = e.dataTransfer.getData("drag-item");
        // 触发回调函数(必须)
        if (droppedItem) {
            props.onItemDropped(droppedItem);
        }
    }

    return (
        {props.children}
    )

}

// 样式(可以根据isDragging来进行判断)
const draggingStyle = {
    opacity: 0.25,
};

【Browserunterstützung】

Derzeit unterstützen nur Internet Explorer 9, Firefox, Opera 12, Chrome und Safari5 Drag-and-Drop, und Safari5.1.2 unterstützt Drag-and-Drop nicht.

Ein Hinweis zu benutzerdefinierten Ablagezielen und Browserkompatibilität

  Wenn Sie ein Element an einigen ungültigen Ablagezielen vorbeiziehen, sehen Sie eine spezielle Mausgeste (ein umgekehrter Schrägstrich in einem Kreis), die anzeigt, dass es nicht abgelegt werden kann. Während alle Elemente Drop-Target-Ereignisse unterstützen, lassen diese Elemente das Drop standardmäßig nicht zu. Wenn das gezogene Element ein Element passiert, das nicht abgelegt werden darf, tritt das Drop-Ereignis nicht auf, egal was der Benutzer tut. Sie können jedoch jedes Element zu einem gültigen Ablageziel machen, indem Sie das Standardverhalten der Dragenter- und Dragover-Ereignisse überschreiben ( event.preventDefault ).

  Nachdem Sie das Standardverhalten überschrieben haben, werden Sie feststellen, dass der Cursor beim Verschieben des gezogenen Elements zum Drop-Ziel zu einem Drop-erlaubten Symbol wird. In Firefox 3.5+ besteht das Standardverhalten für Drop-Ereignisse darin, die URL zu öffnen, die auf dem Drop-Ziel abgelegt wurde. Wenn ein Bild auf das Ablageziel gezogen wird, wird die Seite auf die Bilddatei umgeleitet. Wenn Text per Drag-and-Drop auf ein Drop-Ziel gezogen wird, führt dies zu einem ungültigen URL-Fehler. Damit Firefox normales Drag & Drop unterstützt, ist es also notwendig, das Standardverhalten des Drop-Ereignisses aufzuheben und zu verhindern, dass die URL des gezogenen Elements geöffnet wird.

belohnen:

1. Lernen nativer H5-Drag-and-Drop-Ereignisse und Vertrautheit mit anderen nativen Ereignissen

2. Vertrautheit mit React Hooks+ TS

3. Drag-and-Drop und Datenverarbeitungslogik deaktivieren

unzureichend:

1. Der Stil des Multi-Select-Drag-and-Drop ist noch nicht implementiert

2. Immer noch nicht vertraut genug mit nativen Ereignissen

Referenz: https://blog.csdn.net/weixin_45750771/article/details/125546827

https://blog.csdn.net/weixin_35521120/article/details/113522235

Guess you like

Origin blog.csdn.net/BUG_CONQUEROR_LI/article/details/126758117