Basic introduction 📝

  • If you need to change the position of multiple elements on a Web page, you can do so by dragging the elements. A global attribute has been added to HTML5draggableBy setting the value of this property totrue/falseTo control whether the element can be dragged. Note that:
    • Links and images are draggable by default and can be dragged through thedraggableSet tofalseMake them non-draggable elements.
    • Internet Explorer 8 and earlier Versions are not supporteddraggableProperties.
  • In this paper, the implementation of image drag-and-drop sorting mainly uses elements triggered during the drag-and-drop processondragstart,ondragendondragoverEvent, trigger time and other events for referenceMDNDue to space, this article will not elaborate.

Effect demonstration 🤩

  • First look at the effect:

  • The following will introduce ideas and specific implementation code.

🤔

  • In this paper, the implementation of image drag-and-drop sorting mainly uses elements triggered during the drag-and-drop processondragstart,ondragendondragoverEvents.
  • By listeningondragstartEvent to save the data of the currently dragged element and add special styles to it (set the element’s transparency and enlarge the element).
  • By listeningondragendEvent to remove the previous step in the dragged element’s special style. To reduce memory consumption, we drag the elementondragendEvent delegate to outermost container (event delegate)
  • Implement the most important drag-sort function, mainly for element bindingondragoverEvents. whenondragoverWhen the event is raised, you need to get the current mouse position (event.clientX.event.clientY), calculate the current mouse drag to which element by judging the current drag element and other elements of the position, so as to achieve the element exchange sort.
  • The implementation code is described in the following sections.

Concrete implementation 🧐

Basic layout

  • Before implementing drag and drop, complete the basic layout:
/** index.tsx */
import React, { useMemo, useState } from 'react';

import styles from './index.module.scss';

// How many columns per row
const COLUMN = 4;
// Width of each element
const WIDTH = 120;
// Height of each element
const HEIGHT = 80;
// Image left and right padding
const IMAGE_PADDING = 5;

const showList = [
  {
    id: 2.name: 'osmo pocket'.image:
      'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703865983&di=a35a43a3b9e866f1ee0048563bfd2577&i mgtype=0&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2F5d8f2523322e3f4de91021701e95182c.jpeg'}, {id: 4.name: 'mavic pro'.image:
      'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=787082346, & FM = 15 & gp = 0. 3090178555 JPG'}, {id: 1.name: 'mavic mini2'.image:
      'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703111703&di=59fa621eb1e7f8f4285b95df80e11fd0&i mgtype=0&src=http%3A%2F%2Fp1.itc.cn%2Fimages01%2F20201105%2F600892c32d524b99a118ea56cdf3c211.png'}, {id: 3.name: 'Mecha Master S1'.image:
      'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703133913&di=a415583ce97dd0a34efe17cac24a97ab&i mgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20200325%2F64ebb68f1125450f91e64bb34dc19d55.jpeg'}, {id: 0.name: 'mavic 2'.image:
      'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4132295553, & FM = 26 & gp = 0. 3011440949 JPG',},];const DragAndDropPage: React.FC = () = > {
  const [list, setList] = useState(showList);

  // IMPORTANT: Animations need to be rendered in a certain order
  const sortedList = useMemo(() = > {
    return list.slice().sort((a, b) = > {
      return a.id - b.id;
    });
  }, [list]);

  const listHeight = useMemo(() = > {
    const size = list.length;
    return Math.ceil(size / COLUMN) * HEIGHT;
  }, [list]);

  return (
    <div 
      className={styles.wrapper} 
      style={{ width: COLUMN * (WIDTH + IMAGE_PADDING) + IMAGE_PADDING }}
    >
      <ul className={styles.list} style={{ height: listHeight}} >
        {sortedList.map((item) => {
          const index = list.findIndex((i) => i === item);
          const row = Math.floor(index / COLUMN);
          const col = index % COLUMN;
          return (
            <li
              key={item.id}
              className={styles.item}
              style={{
                height: HEIGHT.left: col * (WIDTH + IMAGE_PADDING),
                top: row * HEIGHT.padding: `0The ${IMAGE_PADDING}px`}}data-id={item.id}
            >
              <img src={item.image} alt={item.name} width={WIDTH} />
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default React.memo(DragAndDropPage);
Copy the code
  • The contents of the style file are as follows:
/** index.module.scss */
.wrapper {
  overflow: hidden;
  margin: 100px auto;
  padding: 10px 0;
  background: #fff;border-radius: 5px; } .list { list-style: none; padding: 0; margin: 0; font-size: 0; position: relative; } .item { position: absolute; display: flex; align-items: center; The transition: all 0.2 s ease - in-out; }Copy the code

The effect is as follows:

  • As can be seen from the above code, though shownliThe element looks like the followinglistThe original order is displayed, but the actual rendering order is made up of the sorted listsortedListA decision.

It’s acting like it’s followinglistThe original sequence is displayed through thesortedList.mapAccording to the currentitemlistIndex of an arrayindexTo figure out its CSSlefttopProperties.

Drag and drop to realize

Drag to add special styles

  • First, the outermost containerwrapperThe bindingrefProperty to be retrieved later in the event methodDOMNodes, and will eachliElements of thedraggableProperty is set totrue/falseMake it a draggable element and bind itondragstartEvents:

  • handleDragStartThe main method is to save the data of the current dragged element and add special styles to it (set the transparency of the element and enlarge the element). The code is as follows:

  • The effect is as follows:

Drag and drop end to remove special styles

  • As you can see, when you start dragging the element, the element has a special style applied, but when you release the mouse the element does not revert to its original style. At this point, you need to remove the special styles you added earlier after dragging.
  • To reduce memory consumption, we drag the elementondragendEvent delegate to the outermost container (event delegate) with the following code:

  • The effect is as follows:

Drag the sorting

  • Next comes the most important drag-sort function, primarily element bindingondragoverEvents.
  • By default, data/elements cannot be placed in other elements. If we want to implement this feature, we need to prevent the default handling of elements. We can do this by callingevent.preventDefault()Method to implement the onDragover event.
  • whenondragoverWhen the event is raised, you need to get the current mouse position (event.clientX.event.clientY), calculate the current mouse drag to which element by judging the current drag element and other elements of the position, to achieve the element exchange sort, the key is to achieveupdateListMethod, the relevant code is as follows:
/** Inserts an element into an array */
export function insertBefore<T> (list: T[], from: T, to? : T) :T[] {
  const copy = [...list];
  const fromIndex = copy.indexOf(from);
  if (from === to) {
    return copy;
  }
  copy.splice(fromIndex, 1);
  const newToIndex = to ? copy.indexOf(to) : -1;
  if (to && newToIndex >= 0) {
    copy.splice(newToIndex, 0.from);
  } else {
    // There is no To or To is not in the sequence, move the element To the end
    copy.push(from);
  }
  return copy;
}

/** Check whether the array is equal */
export function isEqualBy<T> (a: T[], b: T[], key: keyof T) {
  const aList = a.map((item) = > item[key]);
  const bList = b.map((item) = > item[key]);
  
  let flag = true;
  aList.forEach((i, idx) = > {
    if(i ! == bList[idx]) { flag =false}})return flag;
}

const DragAndDropPage: React.FC = () = > {
  const [list, setList] = useState(showList);
  const dragItemRef = useRef<ListItem>();
  const dropAreaRef = useRef<HTMLDivElement>(null); .const updateList = useCallback(
    (clientX: number, clientY: number) = > {
      constdropRect = dropAreaRef.current? .getBoundingClientRect();if (dropRect) {
        const offsetX = clientX - dropRect.left;
        const offsetY = clientY - dropRect.top;
        const dragItem = dragItemRef.current;
        // Outside the drag area
        if (
          !dragItem ||
          offsetX < 0 ||
          offsetX > dropRect.width ||
          offsetY < 0 ||
          offsetY > dropRect.height
        ) {
          return;
        }

        const col = Math.floor(offsetX / WIDTH);
        const row = Math.floor(offsetY / HEIGHT);
        let currentIndex = row * COLUMN + col;
        const fromIndex = list.indexOf(dragItem);
        if (fromIndex < currentIndex) {
          // Move from front to back
          currentIndex++;
        }
        const currentItem = list[currentIndex];

        const ordered = insertBefore(list, dragItem, currentItem);
        if (isEqualBy(ordered, list, 'id')) {
          return;
        }
        setList(ordered);
      }
    },
    [list]
  );

  const handleDragOver = useCallback(
    (e: React.DragEvent<HTMLDivElement>) = > {
      e.preventDefault();
      updateList(e.clientX, e.clientY);
    },
    [updateList]
  );

  return (
    <div
      className={styles.wrapper}
      ref={dropAreaRef}
      style={{ width: COLUMN * (WIDTH + IMAGE_PADDING) + IMAGE_PADDING }}
      onDragEnd={handleDragEnd}
      onDragOver={handleDragOver}
    >
      <ul className={styles.list} style={{ height: listHeight}} >
        {sortedList.map((item) => {
          const index = list.findIndex((i) => i === item);
          const row = Math.floor(index / COLUMN);
          const col = index % COLUMN;
          return (
            <li
              draggable
              key={item.id}
              className={styles.item}
              style={{
                height: HEIGHT.left: col * (WIDTH + IMAGE_PADDING),
                top: row * HEIGHT.padding: `0The ${IMAGE_PADDING}px`,}}data-id={item.id}
              onDragStart={(e)= > handleDragStart(e, item)}
            >
              <img src={item.image} alt={item.name} width={WIDTH} />
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default React.memo(DragAndDropPage);
Copy the code
  • The effect is as follows:

The complete code

  • index.tsxContents of the document:

  • Style file contents:

Conclusion 👀

  • This article introduces how to useReactProject to achieve a simple picture drag sorting. This is done primarily by setting the element to be draggeddraggableProperty, and listen for related events, add or subtract style, drag sort, etc.

If there are any omissions in the above content, please leave a message ✍️ to point out, and progress together 💪💪💪

If you find this article helpful, 🏀🏀 leave your precious 👍

The resources

  1. React hook
  2. HTML drag and drop API
  3. Drag sort with two different poses