Grid布局+拖拽

2,738 阅读1分钟

Grid布局+拖拽

最近公司项目组觉得阿里云或者华为云控制台的自定义的功能非常好,让我进行一下调研,而我身为一个菜鸟前端,我只能一点点的研究了。

暂时实现了一个小的可以重排或者替换的demo作为基础,后续在进行更深度的开发。

主要这个是用的grid布局,每一个单元格目前都是grid-template-areas定义好的。所以目前功能不够完善,就是一个基础小demo。

没有组件化,放在一个页里面的完整代码如下:

<template >
  <div>
    <div
      ref="gridContent"
      class="grid_group"
      :style="{ 'grid-template-areas': gridAreas }"
    >
      <div
        v-for="(dragData, index) in dragList"
        :key="index"
        v-drag
        :style="{ 'grid-area': 'area-' + index }"
        class="drag-item"
        onselectstart="return false;"
      >
        <div class="drag-data-div">这是{{ dragData }}数据</div>
      </div>
    </div>
  </div>
</template>
<script>
import _ from "lodash";
export default {
  name: "mapStudy",
  directives: {
    drag: {
      bind: function (el, binding, vnode) {
        const moveEl = el;
        moveEl.onmousedown = (event) => {
          moveEl.style.boxShadow = "#e6e6e6 0 0 10px 10px";
          moveEl.style.zIndex = 100;
          vnode.context.dragStart(event);
          //获取点击的元素的当前位置
          const disX = event.clientX;
          const disY = event.clientY;
          document.onmousemove = (dEvent) => {
            //获取移动的距离
            let x = dEvent.clientX - disX;
            let y = dEvent.clientY - disY;
            //获取当前元素可移动的位置区域
            const { minX, maxX, minY, maxY } = vnode.context.getRangeOfEl(
              moveEl
            );
            console.log(minX, maxX, minY, maxY);
            x = x < minX ? minX : x > maxX ? maxX : x;
            y = y < minY ? minY : y > maxY ? maxY : y;
            moveEl.style.left = x + "px";
            moveEl.style.top = y + "px";
          };
          document.onmouseup = (upEvent) => {
            document.onmousemove = null; // 需要把事件监听取消
            document.onmouseup = null; // 需要把事件监听取消
            moveEl.style.boxShadow = "none";
            vnode.context.changeBlock(moveEl);
            vnode.context.dragEnd(upEvent, vnode.context.dragList);
          };
        };
      },
    },
  },
  data() {
    return {
      column: 3,
      gridAreas: "",
      dragDataList: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
      dragList: [],
      type: "替换replace", //重排resort/替换replace
    };
  },
  mounted: function () {
    //clone一下,不然resort赋值那个步骤有点问题,也可以选择其他方式优化
    this.dragList = _.cloneDeep(this.dragDataList);
    this.joinGridArea();
  },
  methods: {
    // 根据数据来生成grid布局的area区域。
    joinGridArea: function () {
      console.log(this.dragList);
      const len = this.dragList.length;
      let areaStr = "";
      for (let i = 0; i < len; i++) {
        if (i % this.column === 0) {
          areaStr += '"area-' + i + " ";
          if (this.column === 1) {
            areaStr += '"';
          }
        } else if (i % this.column === this.column - 1) {
          areaStr += "area-" + i + '"';
        } else {
          areaStr += "area-" + i + " ";
        }
      }
      if (len % this.column !== 0) {
        const emptyLength = this.column - (len % this.column);
        areaStr += new Array(emptyLength).fill(".").join(" ") + '"';
      }
      this.gridAreas = areaStr;
    },
    dragStart: function (event) {
      this.$message({
        type: "info",
        message: `拖拽开始,通过console可以查看event参数, ${JSON.stringify(
          event
        )}`,
      });
      console.info("拖拽开始", event);
    },
    dragEnd: function (event, dragList) {
      this.$message({
        type: "info",
        message: `拖拽结束,通过console可以查看event参数, ${JSON.stringify(
          event
        )}, ${dragList}`,
      });
      console.info("拖拽结束", event, dragList);
    },
    //拖拽结束时重排数据或者替换数据
    changeBlock: function (moveEl) {
      // 将方块移入到对应的区域中
      const { nowIndex, index } = this.getIndexOfMoveEL(moveEl);
      if (this.type === "replace") {
        const temp = this.dragList[index];
        this.$set(this.dragList, index, this.dragList[nowIndex]);
        this.$set(this.dragList, nowIndex, temp);
      } else {
        this.dragList.splice(index, 1);
        this.dragList.splice(nowIndex, 0, this.dragDataList[index]);
      }
      moveEl.style.left = 0;
      moveEl.style.top = 0;
    },
    // // 计算当前元素可移动的区域
    getRangeOfEl: function (moveEl) {
      const index = parseInt(
        moveEl.style.gridArea.split(" / ")[0].split("-")[1]
      );
      //下面计算鼠标所选中的块可以移动的区域
      const res = {};
      //第几个块%列数 取模可以去到当前块所在列数。
      const currentColummn = index % this.column;
      //左边:-(当前选中的块的宽度+间距 )*当前块处于的列的位置。
      res.minX = -((moveEl.offsetWidth + 5) * currentColummn);
      //右边:(当前选中的块的宽度+间距 )*当前块处于的列的位置。
      res.maxX = (this.column - currentColummn - 1) * (moveEl.offsetWidth + 5);
      //总数/列数,获取总共多少行
      const allRow = Math.ceil(this.dragList.length / this.column);
      // const allRow = Math.ceil(9 / this.column);
      //当前块的位置/列数,判断当前块所在行。
      const currentRow = Math.floor(index / this.column);
      //上边:-(当前选中的块的高度+间距 )*当前块处于的行的位置。
      res.minY = -((moveEl.offsetHeight + 5) * currentRow);
      //下边:(总共行-当前行-1 )*(当前选中的块的高度+间距)。
      res.maxY = (allRow - currentRow - 1) * (moveEl.offsetHeight + 5);
      return res;
    },
    getIndexOfMoveEL: function (moveEl) {
      const x = parseInt(moveEl.style.left.split("px")[0]);
      const y = parseInt(moveEl.style.top.split("px")[0]);
      const index = parseInt(
        moveEl.style.gridArea.split(" / ")[0].split("-")[1]
      );
      let nowIndex = 0;
      if (x < 0) {
        nowIndex = index - Math.round(Math.abs(x) / moveEl.offsetWidth);
      } else {
        nowIndex = index + Math.round(Math.abs(x) / moveEl.offsetWidth);
      }
      if (y < 0) {
        nowIndex =
          nowIndex -
          Math.round(Math.abs(y) / moveEl.offsetHeight) * this.column;
      } else {
        nowIndex =
          nowIndex +
          Math.round(Math.abs(y) / moveEl.offsetHeight) * this.column;
      }
      console.log(nowIndex);
      return { nowIndex, index };
    },
  },
};
</script>
<style lang="less" scoped>
.grid_group {
  --columnWidth: "auto";
  --rowHeight: "auto";
  display: grid;
  gap: 5px 5px;
  justify-content: center;
  align-content: center;
  width: fit-content;
  position: relative;
  .drag-item {
    position: relative;
    width: var(--columnWidth);
    height: var(--rowHeight);
    line-height: var(--rowHeight);
    text-align: center;
    user-select: none;
    .drag-data-div {
      background-color: gray;
      color: #ffffff;
      width: 200px;
      height: 200px;
      line-height: 200px;
    }
  }
}
</style>

参考文章:

vue.js封装多列布局拖拽(grid布局)