Vue Grid Layout -️ A grid layout system for Vue.js (nanny-level tutorial)

Table of contents

1. Introduction to Vue Grid Layout 

2. Installation and use of vue-grid-layout

 3. Properties

3.1 Mandatory attributes of gridItem

3.2 Calculation method of actual width and height of frame elements

3.3 Summary of element size calculation methods

3.4 properties of gridLayout

4. Events

Five, the practical application of vue-grid-layout

5.1 Move to any position

 5.2 Move events and resize

5.3 Realize the exchange of two elements

5.4 Specify elements that allow dragging

 5.5 Dynamically add/remove elements

5.6 Externally added elements

 5.7 Dynamic Drag and Drop Preview

Six, the use of Vue3

 Summarize


Brothers, is there any good screen recording gif tool to recommend?

Official website: Vue Grid Layout -️ A grid layout system for Vue.js

Gitee:https://gitee.com/wfeng0/vue2-grid-layout 

1. Introduction to Vue Grid Layout 

From the description on the official website, we can see that the grid layout has the following characteristics:

 In a system with drag and drop to form pages, dynamic resizing of components, and edge collision detection , it is undoubtedly the most appropriate to use this layout. Of course, there are also many ready-made articles and blogs. Today, I mainly explain the basic use of the grid system according to the learning ideas on the official website.

The grid system currently supports vue2 the best, and vue3 needs to be supported by plug-ins, which will be explained in detail in the subsection.

2. Installation and use of vue-grid-layout

// 安装:
npm install vue-grid-layout --save

// 引用:
import { GridLayout, GridItem } from 'vue-grid-layout'

// 注册:
components:{ GridLayout, GridItem }

After completing the appeal steps, copy the code in the "Usage" section of the official website to the project:

data() {
        return {
            // 定义栅格系统数据源
            layout: [
                { "x": 0, "y": 0, "w": 2, "h": 2, "i": "0" },
                { "x": 2, "y": 0, "w": 2, "h": 4, "i": "1" },
                { "x": 4, "y": 0, "w": 2, "h": 5, "i": "2" },
                { "x": 6, "y": 0, "w": 2, "h": 3, "i": "3" },
                { "x": 8, "y": 0, "w": 2, "h": 3, "i": "4" },
                { "x": 10, "y": 0, "w": 2, "h": 3, "i": "5" },
                { "x": 0, "y": 5, "w": 2, "h": 5, "i": "6" },
                { "x": 2, "y": 5, "w": 2, "h": 5, "i": "7" },
                { "x": 4, "y": 5, "w": 2, "h": 5, "i": "8" },
                { "x": 6, "y": 3, "w": 2, "h": 4, "i": "9" },
                { "x": 8, "y": 4, "w": 2, "h": 4, "i": "10" },
                { "x": 10, "y": 4, "w": 2, "h": 4, "i": "11" },
                { "x": 0, "y": 10, "w": 2, "h": 5, "i": "12" },
                { "x": 2, "y": 10, "w": 2, "h": 5, "i": "13" },
                { "x": 4, "y": 8, "w": 2, "h": 4, "i": "14" },
                { "x": 6, "y": 8, "w": 2, "h": 4, "i": "15" },
                { "x": 8, "y": 10, "w": 2, "h": 5, "i": "16" },
                { "x": 10, "y": 4, "w": 2, "h": 2, "i": "17" },
                { "x": 0, "y": 9, "w": 2, "h": 3, "i": "18" },
                { "x": 2, "y": 6, "w": 2, "h": 2, "i": "19" }
            ],

        }
    },
 <grid-layout
            :layout.sync="layout"
            :col-num="12"
            :row-height="30"
            :is-draggable="true"
            :is-resizable="true"
            :is-mirrored="false"
            :vertical-compact="true"
            :margin="[10, 10]"
            :use-css-transforms="true"
    >

        <grid-item v-for="item in layout"
                   :x="item.x"
                   :y="item.y"
                   :w="item.w"
                   :h="item.h"
                   :i="item.i"
                   :key="item.i">
            {
   
   {item.i}}
        </grid-item>
    </grid-layout>

Get the following figure, which means that the grid system can be used normally. The following will slowly introduce its properties and event methods.

Add style   to gridItem ( is item ):

 <grid-item v-for="item in layout" :x="item.x" :y="item.y" 
     :w="item.w" :h="item.h" :i="item.i" :key="item.i" class="gridItem">
            {
   
   { item.i }}
        </grid-item>

// 对应的less
<style lang="less" scoped>
.gridItem {
    border: solid black 1px;
    background-color: #CCCCCC;
}
</style>

In this way, is it very similar to the example on the official website? 

 3. Properties

Let’s briefly talk about the attributes that are often used. You can go to the official website to view the description of all attributes. Let’s first understand the necessary attributes of gridItem. It is very important for our explanation.

3.1 Mandatory attributes of gridItem

This is the data item of gridItem: { "x": 0, "y": 0, "w": 2, "h": 2, "i": "0" }, including x, y, w, h, i.

    * 1. i: ID of the element in the grid

    * 2. x: identifies which column the grid element is in

    * 3. y: Identify the row where the grid element is located

    * 4. w: identifies the initial width of the grid element (the value is a multiple of colWidth)

    * 5. h: identifies the initial height of the grid element (the value is a multiple of rowHeight)

 After understanding the basic concepts, let’s talk about the meaning of the parameters in detail (only take the first data item: { "x": 0, "y": 0, "w": 2, "h": 2, "i": "0 "}): 

Set X to 5:

 Set Y to 3: [Currently it cannot be set to 3, because the item will always be in the first row, and we will deal with it later, because it cannot be in a random position yet]

 W and H are relatively simple, controlling the width and height of data items: 

    * 6. minW: The minimum width of the grid element (the value is a multiple of colWidth)

    * 7. minH: The minimum height of the grid element (the value is a multiple of rowHeight)

    * 8. maxW: The maximum width of the grid element (the value is a multiple of colWidth)

    * 9. maxH: the maximum height of the grid element (the value is a multiple of rowHeight)

After understanding the basic properties, we can look at the properties of GridLayout, because other properties of the item will inherit the parent properties. Before looking at its elements, we need to know the element calculation method of the framework.

3.2 Calculation method of actual width and height of frame elements

Illustration: the grid number of vue-grid-layout

It is known that colNum is the number of defined columns, and rowHeight refers to the height of each row (the unit of this is px); as shown in the above figure, the parent box is split into three rows and five columns, and I put a grid element, the actual proportion is as shown in the figure below :

 The actual height and width of an element is equal to the size of a grid. Q: What about the size of an element that occupies 2×2 grids?

Verify it with code:

Just need to understand the existence of margin. Therefore it can be concluded

// 元素的尺寸计算方法
宽度 width = ( box / colNum ) * w + ( w - 1 ) * margin + border
高度 height= rowHeight * h +( h - 1 ) * margin + border

Let's verify this grid number with code:

 

Multiple grid-items can be placed under the grid-layout. We use one for the background frame and one for dragging. The background frame is not draggable or zoomable: is-draggable="false" :is-resizable="false", note Order, the first one will be at the bottom, the effect is as follows.

 In this way, it is clearer to see that after zooming, the size of the element does contain the margin value.

There is a relationship between the width and height of the element and the actual w and h values ​​plus the margin value of (n-1). It took a  lot of space to explain the calculation method of the width and height of this framework. Be sure to remember that it is (n-1) ) relationship, when calculating the element size on the page, don't forget the existence of margin!

3.3 Summary of element size calculation methods

[New chapter added on March 17: Calculation method of element size]

I read many people's comments, and I still have doubts about the size calculation of this element. Now I will explain this difficulty again:

        1. Our drag area width: 1500px; height: 650px; divide its width into 10 parts, how wide is each part? (how many px)

        We know that when configuring, we set margin: [10,10], therefore, each element should have margins of up, down, left, and right. Therefore, the following graph is obtained:

 2. :row-height="50" This attribute indicates that the height of each row is 50px, so how many rows can be divided into 650px height?

 There is also an interval of n+1 here, and by solving the inequality, the maximum is 10.

Let's summarize the calculation formula:

Width: If it can be divided into n columns, then, width - (n+1)*margin / n = the actual width of each element

Height: Suppose it can be divided into m rows, then, row-height * m + (m + 1) * margin <= height

Therefore, the width and height of the element can be dynamically calculated according to the actual width and height of the drag zone

I don't know how to explain it, can you understand? If you are still a little fuzzy, you can read my next project practice, or leave me a message.

3.4 properties of gridLayout

    * 1. layout: The data source of the grid layout, the data source is an Array, and the data item is an object, which must contain i, x, y, w and h attributes.

     * 2. colNum: defines the number of columns of the grid system

     * 3. rowHeight: the height of each row, in pixels

     * 4. maxRows: Define the maximum number of rows

     * 5. isDraggable: identifies whether the elements in the grid are draggable

     * 6. isResizable: identifies whether the elements in the grid can be resized

     * 7. preventCollision: prevent collision property, when the value is set to true, the grid can only be dragged to the blank space

(We set the width and height of the data item to 1 for easy viewing)

colNum : is the number of defined columns

 rowHeight: refers to the height of each row (the unit of this is px)

 margin: defines the element margin in the grid (the first element in the array represents the horizontal margin, and the second represents the vertical margin, in pixels)

Other attributes are relatively simple, so I won’t introduce them in a large space, and we will elaborate on them later. The main thing is that everyone must understand the calculation rules. This framework is very easy to grasp. When encountering a BUG, ​​it is basically a margin problem.

4. Events

Official website description: Event | Vue Grid Layout -️ A grid layout system for Vue.js

The official website of this chapter has already described it very well, and there are also demos and samples, so I won’t go into details.

Five, the practical application of vue-grid-layout

5.1 Move to any position

There is still a problem left in the appeal, that is, the element cannot be moved to any position

gridLayout  verticalCompact attribute: Indicates whether the layout is compressed vertically. When it is false, the vertical direction is not compressed, and elements can be placed.

 5.2 Move events and resize

Events are divided into two groups, the event is being executed and the event has been executed.

The move event has three parameters: i, newX, newY, i identifies the unique mobile element ID and the new position

         @move="moveEvent"

        @moved="movedEvent"

The resize event has 5 parameters: i, newH, newW and the px (pixel) unit of h and w

        @resize="resizeEvent"

        @resized="resizedEvent"

The commonly used events are these two groups, and we will explain and apply them in detail.

5.3 Realize the exchange of two elements

The exchange of elements is the simplest application. When an element is overlaid on an element, the positions of the two are exchanged. Similar to exchanging two numbers, a third variable must be required. We use deep copy to implement data caching.

mounted() {
    this.map = JSON.parse(JSON.stringify(this.layout));
  },

Implementation idea: During the movement, judge whether there is an element at the position, and if so, just exchange the data of the two.

moveEvent(i, x, y) {
      console.log("移动中--move", i, x, y);
      //   1 根据移动位置,循环数据源当前位置是否有元素,不包含自己的位置
      const finditem = this.map.find(
        (item) => item.x === x && item.y === y && item.i != i
      );
      if (!finditem) return;
      console.log("找到了", finditem);
      this.changePosition(
        this.layout.find((item) => item.i === i),
        finditem
      );
    },

 After the logic was clarified, the exchange began:

// 交换位置:
    changePosition(item1, item2) {
      console.log(item1, item2);
      // 定义中间变量交换
      const temp = this.deepClone(item1);
      //   注意:修改的是影响显示的实际数组 layout
      this.layout.forEach((item) => {
        // 找到 item1,将 item1 的信息换成item2
        if (item.i === item1.i) {
          item.x = item2.x;
          item.y = item2.y;
        }
        if (item.i === item2.i) {
          item.x = temp.x;
          item.y = temp.y;
        }
      });
      // 实现交换后,及时同步 数据映射
      this.map = this.deepClone(this.layout);
    },

But there is still a small problem in this way. I hope that the two original positions are exchanged, not the latest position during the movement. This requires obtaining the original data in the data map:

Realize the effect: 

It is enough to simply realize this. Of course, there are other problems, such as the same size can be exchanged, more exchanges are less, and non-immediate exchange after matching, etc., still affect the user experience. We will leave these issues to be dealt with later.

5.4 Specify elements that allow dragging

This scenario is relatively common. In actual projects, it is impossible for the overall structure to allow dragging, because it involves the event response of the structure, etc. Therefore, it is more important to design elements that allow dragging.

[GridItem attribute]

# dragIgnoreFrom (Identifies which sub-elements in the grid element cannot trigger the drag event, the value is css-likea selector)

# dragAllowFrom (Identifies which sub-elements in the grid element can trigger the drag event, the value is css-likea selector)

<grid-item
        v-for="item in layout"
        :x="item.x"
        :y="item.y"
        :w="item.w"
        :h="item.h"
        :i="item.i"
        :key="item.i"
        @move="moveEvent"
        @moved="movedEvent"
        @resize="resizeEvent"
        @resized="resizedEvent"
        class="gridItem"
        :style="{ backgroundColor: item.i === '0' ? '' : 'pink' }"
        drag-ignore-from=".gridBox"
        drag-allow-from=".draggable"
      >
        <div class="gridBox">不允许拖动</div>
        <div class="draggable"></div>
      </grid-item>
.gridItem {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border: solid #fff 1px;
    background-color: #cccccc;

    .draggable {
      position: absolute;
      left: 0;
      top: 0;
      width: 10px;
      height: 10px;
      background-color: #000;
      border-radius: 10px;
      cursor: pointer;
    }
  }

Only specified elements can be dragged, and other unspecified elements are not allowed to be dragged:

 5.5 Dynamically add/remove elements

Adding elements dynamically is to push new item data items into the array object:

 add() {
      this.layout.push({
        x: 1,
        y: 2, // puts it at the bottom
        w: 1,
        h: 1,
        i: this.layout.length + 1,
      });
    },

 To delete an element is to get the i of the element and execute this.layout.splice(index, 1) operation.

dele(i) {
      this.layout.splice(
        this.layout.findIndex((item) => item.i === i),
        1
      );
    },

5.6 Externally added elements

The principle is to use the event source to drag external elements into the layout, dynamically calculate their positions, and dynamically add elements.

For the convenience of calculation, we place the layout system at the position of top:0, left:0

Here we need to calculate the number of grids, grid width and height.

5.6.1 Drag and drop into the layout, you will get real-time coordinates

 In the event source, there are x: 334 y: 161, two attributes.

5.6.2 After the dynamic calculation is released, where should it be

We use the dragged event to listen, and the event is triggered too many times during the movement (the event source is the same, both have x and y attributes).

The current coordinates are 336 299, how do we determine which grid we are in (the grid size is needed for calculation)?

According to our calculation method:

// 元素的尺寸计算方法
宽度 width = ( box / colNum ) * w + ( w - 1 ) * margin + border
高度 height= rowHeight * h +( h - 1 ) * margin + border

Grid size: ( box / colNum )* rowHeight, I don't count, get it directly:

Therefore, the size of each grid is 270*102, the current coordinates are 336 299, and the position is calculated [position refers to x, y in the grid system]:

We need to deal with the margin, which we classify as the size of the grid, so when calculating, the grid is 180*112

336/280=1.2

299/112=2.6

We round up: it should be in the third row, second column.

 After the coordinates can be obtained, the dynamic push element:

 // 动态添加元素
      this.layout.push({
        x: gx - 1,
        y: gy - 1,
        w: 1,
        h: 1,
        i: `item(x:${ex},y:${ey})`,
      });

 5.7 Dynamic Drag and Drop Preview

The above implementation is too blunt, we drag it in, we don’t know its specific location, we make a preview function. (new grid-item)

When dragging to a certain grid, it would be better to display the preview position in real time. Create a new grid-item, use the previewData:[] empty array as the data source, dynamically add the preview data during the execution of the drag event, and update the preview data in real time. This kind of interaction is the best:

Six, the use of Vue3

[The vue3 chapter explanation is too simple, let’s add it today]

// 下载 vue-grid-layout
npm install vue-grid-layout --save

// 下载 vue3 插件支持
npm install [email protected] --save

// main.js中注册
import gridLayout from 'vue-grid-layout'

createApp(App).use(gridLayout).mount('#app')

// 在main.js注册后,不需要在 import 引入,直接使用(App.vue)

<template>
  <grid-layout
    :layout="layout"
    :col-num="12"
    :row-height="30"
    :is-draggable="true"
    :is-resizable="true"
    :is-mirrored="false"
    :vertical-compact="true"
    :margin="[10, 10]"
    :use-css-transforms="true"
  >
    <grid-item
      v-for="item in layout"
      :x="item.x"
      :y="item.y"
      :w="item.w"
      :h="item.h"
      :i="item.i"
      :key="item.i"
    >
      {
   
   { item.i }}
    </grid-item>
  </grid-layout>
</template>

<script setup>
import { reactive } from "vue";

// 使用 vue3 setup 语法糖
const layout = reactive([
  { x: 0, y: 0, w: 2, h: 2, i: "0" },
  { x: 2, y: 0, w: 2, h: 4, i: "1" },
  { x: 4, y: 0, w: 2, h: 5, i: "2" },
  { x: 6, y: 0, w: 2, h: 3, i: "3" },
  { x: 8, y: 0, w: 2, h: 3, i: "4" },
  { x: 10, y: 0, w: 2, h: 3, i: "5" },
  { x: 0, y: 5, w: 2, h: 5, i: "6" },
  { x: 2, y: 5, w: 2, h: 5, i: "7" },
  { x: 4, y: 5, w: 2, h: 5, i: "8" },
  { x: 6, y: 3, w: 2, h: 4, i: "9" },
  { x: 8, y: 4, w: 2, h: 4, i: "10" },
  { x: 10, y: 4, w: 2, h: 4, i: "11" },
  { x: 0, y: 10, w: 2, h: 5, i: "12" },
  { x: 2, y: 10, w: 2, h: 5, i: "13" },
  { x: 4, y: 8, w: 2, h: 4, i: "14" },
  { x: 6, y: 8, w: 2, h: 4, i: "15" },
  { x: 8, y: 10, w: 2, h: 5, i: "16" },
  { x: 10, y: 4, w: 2, h: 2, i: "17" },
  { x: 0, y: 9, w: 2, h: 3, i: "18" },
  { x: 2, y: 6, w: 2, h: 2, i: "19" },
]);
</script>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
</style>

Add styles:

.grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  border: solid #ccc 1px;
  background-color: #ccc;
}

get as follows:

 Then the rest is the event or something, which is the same as vue2. The main reason is that this picture can appear in vue3, which means that there is no problem.

 

 Summarize

The grid layout is not difficult, but you must understand the principles inside and apply it to your own project development, and you must also combine the actual situation of your own project. The logic involved is mainly to calculate width and height, drag event processing and other multi-angle issues. To achieve richer functions, the main thing is to be able to use multiple item systems to achieve stacking effects. The optimization part is left for everyone to think about. If you have any questions, please communicate and discuss. Don’t forget to recommend some gif tools~

Guess you like

Origin blog.csdn.net/weixin_47746452/article/details/127922567