Interfejs API przeciągania i upuszczania HTML5

Ten post zawiera podstawowe informacje o przeciąganiu i upuszczaniu.

Tworzenie przeciąganych treści

W większości przeglądarek wybrany tekst, obrazy i linki można domyślnie przeciągać. Jeśli na przykład przeciągniesz link na stronie internetowej, zobaczysz małe pole z tytułem i adresem URL, które można upuścić na pasku adresu lub na pulpicie, aby utworzyć skrót lub przejść do linku. Aby umożliwić przeciąganie innych typów treści, musisz użyć interfejsów API przeciągania i upuszczania w HTML5.

Aby obiekt można było przeciągać, ustaw dla niego parametr draggable=true. Dozwolone jest przeciąganie niemal wszystkiego, w tym obrazów, plików, linków, plików i wszelkich znaczników na stronie.

W tym przykładzie tworzymy interfejs do przestawiania kolumn rozmieszczonych za pomocą CSS Grid. Podstawowe znaczniki kolumn wyglądają tak, a atrybut draggable każdej kolumny ma wartość 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>

Oto kod CSS elementów kontenera i pudełka. Jedynym elementem CSS powiązanym z funkcją przeciągania jest właściwość cursor: move. Pozostała część kodu kontroluje układ i styl elementów kontenera oraz pola.

.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;
}

Na tym etapie możesz przeciągnąć elementy, ale nic więcej się nie wydarzy. Aby dodać zachowanie, musisz użyć interfejsu JavaScript API.

Nasłuchiwanie zdarzeń przeciągania

Aby monitorować proces przeciągania, możesz nasłuchiwać tych zdarzeń:

Aby obsłużyć przeciąganie, potrzebujesz jakiegoś elementu źródłowego (gdzie przeciąganie się zaczyna), danych (element przeciągany) i docelowego miejsca (obszar, w którym można upuścić element). Źródło może być dowolnym elementem. Docelowy obszar zrzutu to obszar zrzutu lub zestaw obszarów zrzutu, który akceptuje dane, które użytkownik próbuje zrzucić. Nie wszystkie elementy mogą być celami. Na przykład nie możesz ustawić jako celu elementu typu obraz.

Rozpoczynanie i kończenie sekwencji przeciągania

Po zdefiniowaniu atrybutów draggable="true" w treści dołącz do niej element obsługi zdarzenia dragstart, aby rozpocząć sekwencję przeciągania w przypadku każdej kolumny.

Ten kod ustawia przezroczystość kolumny na 40%, gdy użytkownik zaczyna ją przeciągać, i przywraca ją na 100% po zakończeniu zdarzenia przeciągania.

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);
});

Efekt można zobaczyć w tym filmie demonstracyjnym Glitch. Przeciągnij element, aby zmienić jego przezroczystość. Źródłowy element ma zdarzenie dragstart, więc ustawienie wartości this.style.opacity na 40% daje użytkownikowi wizualną informację, że ten element jest aktualnie zaznaczany. Gdy upuścisz element, element źródłowy wróci do 100% przezroczystości, nawet jeśli nie zdefiniujesz jeszcze zachowania po upuszczeniu.

Dodaj więcej wskazówek wizualnych

Aby pomóc użytkownikowi zrozumieć, jak korzystać z interfejsu, użyj przełączników zdarzeń dragenter, dragoverdragleave. W tym przykładzie kolumny są nie tylko przeciągane, ale też miejscami docelowymi. Aby ułatwić użytkownikowi zrozumienie tego, ustaw kreskę obramowania, gdy użytkownik przytrzymuje przeciągnięty element nad kolumną. W CSS możesz na przykład utworzyć klasę over dla elementów, które są elementami docelowymi:

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

Następnie w kodzie JavaScript skonfiguruj moduły obsługi zdarzeń, dodaj klasę over po przeciągnięciu kolumny i usuń ją, gdy przeciągnięty element zniknie. W module obsługi dragend usuwamy też klasy na końcu przeciągania.

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);
  });
});

W tym kodzie warto uwzględnić kilka kwestii:

  • Działanie domyślne dla zdarzenia dragover to ustawienie właściwości dataTransfer.dropEffect na "none". Właściwość dropEffect omówimy dalej na tej stronie. Na razie wystarczy wiedzieć, że uniemożliwia ono wywołanie zdarzenia drop. Aby zastąpić to działanie, zadzwoń pod numer e.preventDefault(). Inną sprawdzoną metodą jest zwrócenie polecenia false w tym samym module obsługi.

  • Obsługa zdarzenia dragenter służy do przełączania klasy over zamiast klasy dragover. Jeśli używasz metody dragover, zdarzenie uruchamia się wielokrotnie, gdy użytkownik przytrzymuje przeciągany element nad kolumną, co powoduje cykliczne przełączanie klasy CSS. Oznacza to, że przeglądarka wykonuje wiele niepotrzebnych zadań związanych z renderowaniem, co może negatywnie wpływać na wygodę użytkownika. Zdecydowanie zalecamy zminimalizowanie liczby ponownych wywołań. Jeśli musisz używać funkcji dragover, rozważ ograniczenie jej działania lub stosowanie debouncowania.

Dokończ dodawanie

Aby przetworzyć usunięcie, dodaj detektor zdarzenia drop. W obiekcie drop musisz zapobiec domyślnemu działaniu przeglądarki w przypadku przerwania, które zwykle powoduje irytujące przekierowanie. Aby to zrobić, zadzwoń pod numer e.stopPropagation().

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

Pamiętaj, aby zarejestrować nowy moduł obsługi obok innych modułów obsługi:

  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);
  });

Jeśli na tym etapie uruchomisz kod, element nie zostanie umieszczony w nowej lokalizacji. Aby to zrobić, użyj obiektu DataTransfer.

Właściwość dataTransfer przechowuje dane wysłane w wyniku przeciągania. Funkcja dataTransfer jest ustawiana w zdarzeniu dragstart i odczytywana lub obsługiwana w zdarzeniu utraty. Wywołanie e.dataTransfer.setData(mimeType, dataPayload) pozwala ustawić typ MIME i ładunek danych obiektu.

W tym przykładzie umożliwimy użytkownikom zmianę kolejności kolumn. Aby to zrobić, musisz najpierw zapisać kod HTML elementu źródłowego po rozpoczęciu przeciągania:

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

  dragSrcEl = this;

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

W zdarzeniu drop przetwarzasz spadek kolumny, ustawiając kod HTML kolumny źródłowej na kod HTML kolumny docelowej, w której zostały przekazane dane. Obejmuje to sprawdzenie, czy użytkownik nie przeciąga elementów z powrotem do tej samej kolumny, z której je przeciągał.

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

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

  return false;
}

Wynik sprawdzisz w tej prezentacji. Aby to zrobić, musisz użyć przeglądarki na komputerze. Interfejs API przeciągania i upuszczania nie jest obsługiwany na urządzeniach mobilnych. Przeciągnij kolumnę A i upuść ją na kolumnę B. Zwróć uwagę, jak zmieniają one miejsca:

Więcej właściwości przeciągania

Obiekt dataTransfer udostępnia właściwości, aby zapewnić użytkownikowi wizualną informację zwrotną podczas przeciągania i kontrolować, jak poszczególne elementy docelowe upuszczania reagują na określony typ danych.

  • dataTransfer.effectAllowedogranicza „rodzaj przeciągania”, który użytkownik może wykonać na elemencie. Jest on używany w modelu przetwarzania metodą przeciągania i upuszczania do inicjowania zmiennej dropEffect podczas zdarzeń dragenterdragover. Właściwość może mieć te wartości: none, copy, copyLink, copyMove, link, linkMove, move, all i uninitialized.
  • dataTransfer.dropEffect kontroluje informacje zwrotne otrzymywane przez użytkownika podczas zdarzeń dragenter i dragover. Gdy użytkownik najedzie kursorem na element docelowy, kursor przeglądarki wskaże, jakiego typu operacja zostanie wykonana, np. kopiowanie lub przenoszenie. Efekt może przyjmować jedną z tych wartości: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) oznacza, że zamiast domyślnej „przejrzystej” ikony w przeglądarce możesz ustawić ikonę do przeciągania.

Prześlij plik

W tym prostym przykładzie kolumna pełni funkcję zarówno źródła, jak i elementu docelowego przeciągania. Może się to zdarzyć w interfejsie, który prosi użytkownika o przesunięcie elementów. W niektórych przypadkach źródło i docelowy element mogą być różnych typów, np. w interfejsie, w którym użytkownik musi wybrać jedno zdjęcie jako główne zdjęcie produktu, przeciągając wybrane zdjęcie do celu.

Przeciąganie i upuszczanie jest często używane, aby umożliwić użytkownikom przeciąganie elementów z pulpitu do aplikacji. Główna różnica dotyczy elementu drop. Zamiast dostępu do plików za pomocą parametru dataTransfer.getData() dane te znajdują się we właściwości 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.
  }
}

Więcej informacji na ten temat znajdziesz w sekcji Niestandardowe przeciąganie i upuszczanie.

Więcej zasobów