Drag cow force, easy to achieve a free drag component

preface

Hello, in the first two articles, we walked into the world of front-end low code and revealed the core of low code - the implementation of page designer. When revealing the page designer, we focused on sharing the component drag method of sequential layout. In the comment of that article [2], a little partner asked about the implementation of free layout. In this article, we will share the implementation principle of free layout drag and realize the simplest demo of free drag of designer components.

How to make elements support dragging

The core of realizing the free drag of components is the newly added global attribute draggable attribute in html5, which specifies whether the element can be dragged. The attribute values are as follows:

  • true: Specifies the draggable of the element
  • false: Specifies that the element cannot be dragged
  • auto: uses the browser's default behavior

When we add the draggable attribute to the element tag, the element can be dragged.

<div draggable > Draggable element </div>
Copy code

Drag event

Event classification

When the element can be dragged, we can use the element drag event to control the start end logic of the drag. The drag event is mainly divided into two categories. One category is triggered by dragging the element:

  • dragstart: triggered when the mouse clicks on an element and starts moving
  • drag: triggered continuously during dragging
  • Drag: triggered when the mouse is released after dragging

The other is that when an element is dragged to a target element, the target element will trigger:

  • Drawer: triggered when an element is dragged onto a target
  • dragover: drag the element in the target element and trigger it continuously
  • dragleave: triggered when leaving the target element
  • Drop: triggered when you drop an element into the target element and release the mouse

Drag drop behavior

In the drag event, we will get the dragged event object (e). In the drag object, we can get an important attribute dataTransfer. We can control the placement behavior of the dragged element through the dropEffect attribute of dataTransfer. The description of its value is as follows.

  • none: you cannot drag and drop elements here
  • Move: move to target
  • Copy: copy to target
  • Link: the target opens the drag element (the drag element must be a link and have a URL)

Implementation of page designer

Let's implement the simplest demo of page designer component drag according to the above knowledge points. First, let's define the component list and canvas area

<template>
  <div>
    <!-- List of components on the left -->
    <div class="left">
      <div
        class="left-item"
        v-for="item in list1"
        :key="item.code"
        draggable
      >
        {{ item.name }}
      </div>
    </div>
    <!-- Canvas area -->
    <div class="targetContent" ref="targetContent">
      <div
        class="item"
        v-for="item in list2"
        :key="item.id"
        :ref="item.id"
        :style="{
          top: `${item.top - 16}px`,
          left: `${item.left - 85}px`,
          'z-index': `${item.zIndex}`
        }"
      >
        <template v-if="item.code === 'MyInput'">
          <a-input></a-input>
        </template>
      </div>
    </div>
  </div>
</template>
Copy code

The component list and the pages in the canvas are traversed and rendered through list1 and list respectively.

<script>
import _ from "lodash";
export default {
  data() {
    return {
      list1: [
        {
          code: "MyInput",
          name: "Input box",
          props: {}
        }
      ],
      list2: [],
    };
  }
}
</script>
Copy code

Let's analyze how to drag the components in the component list to the canvas. As mentioned above, a series of events can be set for the dragged elements and target elements, so we can set the dragstart event for each component when rendering the component list. In this event, we need to do the following:

  1. Set the placement behavior of the dragged element to move, that is, move.
  2. When the component passes through the target element, the default behavior must be blocked, otherwise drop cannot be triggered.
  3. Set the drop behavior when the component leaves the target element as no drag and drop, that is, none.
  4. Drag the element to add the element to the canvas when the target element is released, that is, add the component metadata to list2, and the metadata corresponding to the element also records the coordinate position of the component in the screen.

Then listen to the above actions in the Draft event.

Let's implement the above process through code. First, when traversing the component list, add the dragstart and drag events of the component.

@dragstart="e => dragstart(e, item)"
@dragend="dragend"
Copy code

The following is the implementation of these two events.

dragstart(e, item) {
  this.dragItem = item;
  // Set element placement behavior - move
  this.$refs.targetContent.addEventListener("dragenter", this.dragenter);
  // The default behavior must be blocked when the target element passes, otherwise drop cannot be triggered
  this.$refs.targetContent.addEventListener("dragover", this.dragover);
  // Set the placement behavior of the element when leaving the target element -- you can't drag and drop
  this.$refs.targetContent.addEventListener("dragleave", this.dragleave);
  // Drag the element to add the element to the canvas when the target element is released
  this.$refs.targetContent.addEventListener("drop", this.drop);
},
dragend(e) {
  this.$refs.targetContent.removeEventListener("dragenter", this.dragenter);
  this.$refs.targetContent.removeEventListener("dragover", this.dragover);
  this.$refs.targetContent.removeEventListener("dragleave", this.dragleave);
  this.$refs.targetContent.removeEventListener("drop", this.drop);
},
dragenter(e) {
  e.dataTransfer.dropEffect = "move";
},
dragover(e) {
  e.preventDefault();
},
dragleave(e) {
  e.dataTransfer.dropEffect = "none";
},
drop(e) {
  const { code } = this.dragItem;
  this.list2.push({
    top: e.offsetY,
    left: e.offsetX,
    zIndex: 1,
    code: code,
    id: Date.parse(new Date())
  });
  this.dragItem = null;
}
Copy code

In this way, the components in our component list can be dragged to the canvas.

How can the components dragged to the canvas move flexibly by dragging? Similarly, we can add the mousedown event to the components in the canvas. In the event, we add the monitoring of the mousemove event. When the components in the canvas move, we update the metadata coordinates corresponding to the moved element in real time. The following is the implementation of the code.

mousedown(e, item) {
  this.moveItem = item;
  document.addEventListener("mousemove", this.mousemove);
  document.addEventListener("mouseup", this.mouseup);
},
mousemove(e) {
  const _this = this;
  let { clientX, clientY } = e;
  const moveIdx = _.findIndex(this.list2, function(o) {
    return o.id === _this.moveItem.id;
  });
  let newList2 = _.cloneDeep(this.list2);
  newList2[moveIdx].top = clientY;
  newList2[moveIdx].left = clientX;
  this.list2 = newList2;
},
mouseup(e) {
  document.removeEventListener("mousemove", this.mousemove);
  document.removeEventListener("mouseup", this.mouseup);
}
Copy code

In this way, the components in the canvas can be moved.

Postscript

In this article, we implemented the simplest demo of free layout of page designer components, so that we can understand the implementation principle of free drag. As for the processing of some details, you can realize it yourself according to your own needs ~ let's pay attention to the small partners interested in this series of articles. We will continue to share and communicate with you.

Finally, let's look at the complete demo code.

<template>
  <div>
    <!-- List of components on the left -->
    <div class="left">
      <div
        class="left-item"
        v-for="item in list1"
        :key="item.code"
        draggable
        @dragstart="e => dragstart(e, item)"
        @dragend="dragend"
      >
        {{ item.name }}
      </div>
    </div>
    <!-- Canvas area -->
    <div class="targetContent" ref="targetContent">
      <div
        class="item"
        v-for="item in list2"
        :key="item.id"
        :ref="item.id"
        :style="{
          top: `${item.top - 16}px`,
          left: `${item.left - 85}px`,
          'z-index': `${item.zIndex}`
        }"
        @mousedown="e => mousedown(e, item)"
      >
        <template v-if="item.code === 'MyInput'">
          <a-input></a-input>
        </template>
      </div>
    </div>
  </div>
</template>

<script>
import _ from "lodash";
export default {
  data() {
    return {
      list1: [
        {
          code: "MyInput",
          name: "Input box",
          props: {}
        }
      ],
      list2: [],
      dragItem: null,
      moveItem: null
    };
  },
  methods: {
    dragstart(e, item) {
      this.dragItem = item;
      // Set element placement behavior - move
      this.$refs.targetContent.addEventListener("dragenter", this.dragenter);
      // The default behavior must be blocked when the target element passes, otherwise drop cannot be triggered
      this.$refs.targetContent.addEventListener("dragover", this.dragover);
      // Set the placement behavior of the element when leaving the target element -- you can't drag and drop
      this.$refs.targetContent.addEventListener("dragleave", this.dragleave);
      // Drag the element to add the element to the canvas when the target element is released
      this.$refs.targetContent.addEventListener("drop", this.drop);
    },
    dragend(e) {
      this.$refs.targetContent.removeEventListener("dragenter", this.dragenter);
      this.$refs.targetContent.removeEventListener("dragover", this.dragover);
      this.$refs.targetContent.removeEventListener("dragleave", this.dragleave);
      this.$refs.targetContent.removeEventListener("drop", this.drop);
    },
    dragenter(e) {
      e.dataTransfer.dropEffect = "move";
    },
    dragover(e) {
      e.preventDefault();
    },
    dragleave(e) {
      e.dataTransfer.dropEffect = "none";
    },
    drop(e) {
      const { code } = this.dragItem;
      this.list2.push({
        top: e.offsetY,
        left: e.offsetX,
        zIndex: 1,
        code: code,
        id: Date.parse(new Date())
      });
      this.dragItem = null;
    },
    mousedown(e, item) {
      this.moveItem = item;
      document.addEventListener("mousemove", this.mousemove);
      document.addEventListener("mouseup", this.mouseup);
    },
    mousemove(e) {
      const _this = this;
      let { clientX, clientY } = e;
      const moveIdx = _.findIndex(this.list2, function(o) {
        return o.id === _this.moveItem.id;
      });
      let newList2 = _.cloneDeep(this.list2);
      newList2[moveIdx].top = clientY;
      newList2[moveIdx].left = clientX;
      this.list2 = newList2;
    },
    mouseup(e) {
      document.removeEventListener("mousemove", this.mousemove);
      document.removeEventListener("mouseup", this.mouseup);
    }
  }
};
</script>
<style lang="less" scoped>
.left {
  padding: 10px;
  position: absolute;
  width: 270px;
  background: rgb(247, 202, 202);
  top: 0;
  bottom: 0;
  left: 0;
}
.left-item {
  height: 100px;
  line-height: 100px;
  background: #fff;
}
.targetContent {
  background: rgb(173, 244, 247);
  height: 100vh;
  padding: 0 270px;
}
.item {
  position: absolute;
}
</style>

Copy code

Author: fleshy siege lion

https://juejin.cn/post/7019820870537314334

Added by BrettCarr on Thu, 23 Dec 2021 18:56:56 +0200