js自定义拖拽组件

添加元素(主要参考了layout插件)
List组件内容
<div class="body">
  <div
    draggable
    class="component-item"
    v-for="item in dragIconList"
    :key="item.type"
    @drag="handleDragStart($event, item)"
    @dragend="handleDragEnd($event, item)"
    >
      <div class="component-img">
        <img :src="item.url" />
      </div>
      <div class="component-name">
        {{item.title}}
      </div>
  </div>
</div>

// dragIconList要添加项的数据列表 参数自行添加 如{title:"名称", type:"类型", url:"iconUrl地址"}
document.addEventListener("dragover", function (e) {
      mouseXY.x = e.clientX;
      mouseXY.y = e.clientY;
    }, false);

添加drag事件

@drag="handleDragStart($event, item)"
@dragend="handleDragEnd($event, item)"
let mouseXY = { "x": null, "y": null };    // 当前鼠标所在的位置
// x为组件所在的x轴下标0-n,y位组件所在的y轴下标0-n,w为组件所占的宽度1-n,h为组件的高度1-n,i为组件的id,也可以为组件添加data参数配置所需数据
let DragPos = { "x": null, "y": null, "w": 1, "h": 1, "i": null };  // 临时拖拽项的参数

// dragStrat事件
handleDragStart (e, item) {
   let type = item.type;  // 多种不同的组件数据可能不同  如下所示
   this.gridInFocus = true;
   // 拖拽过程中计算临时的item位置
   // 当前拖拽项的所占格子数量  不同组件配置不同的参数  按序配置
   if (type == "MyType") {
     DragPos.w = 3;
     DragPos.h = 2;
   }
   // 获取总网格数据
   let parentRect = document.getElementById('content').getBoundingClientRect();
   // 判断是否在容器内
   let mouseInGrid = false;
   if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
     mouseInGrid = true;
   }
   // 将临时项加入layout中  drop项会在后续移除掉,此时判断主要是防止多次添加
   if (mouseInGrid && (this.realValue.findIndex(item => item.i === 'drop')) === -1) {
     let { x, y } = this.getCurrentIndex(parentRect)
     let canChange = checkCanOccupy(this.realValue, 'drop', x, y, DragPos.w, DragPos.h);
     // 判断当前位置是否可以插入
     if (canChange) {
       DragPos.x = x;
       DragPos.y = y;
       this.realValue.push({
         x: x,
         y: y,
         w: DragPos.w,
         h: DragPos.h,
         i: 'drop',
         data: {
           x: x,
           y: y,
           w: DragPos.w,
           h: DragPos.h,
         }
       });
     }
   }
   // 已经加入一个临时数据  realValue是数组 用于存储layout数据
   let index = this.realValue.findIndex(item => item.i === 'drop');
   if (index != -1) {
     // 实时移动位置
     let { x, y } = this.getCurrentIndex(parentRect)
     // 判读当前位置是否有模板
     let canChange = checkCanOccupy(this.realValue, 'drop', x, y, DragPos.w, DragPos.h);
     if (canChange) {
       this.realValue.forEach((item) => {
         if (item.i == 'drop') {
           item.x = x;
           item.y = y;
         }
       })
     }
   }
 },
// 拖拽结束,开始项layout中添加真实数据
handleDragEnd (e, item) {
      this.gridInFocus = false;
      let type = item.type;
      // 根据组件类型进行一次判断,只能存储一次的组件不能二次添加
      if (item.type == "myType") {
        let isHasNews = this.realValue.findIndex((item) => {
          return item.data.type == 'myType';
        })
        if (isHasNews != -1) {
          this.realValue = this.realValue.filter(obj => obj.i != 'drop');
          this.$forceUpdate();
          return this.$modal.msgWarning("当前组件已存在")
        }
      }
      let parentRect = document.getElementById('content').getBoundingClientRect();
      let mouseInGrid = false;
      if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
        mouseInGrid = true;
      }
      let dropItem = this.realValue.find((item) => {
        return item.i == 'drop'
      })
      // 鼠标在网格中并且已有drop项
      if (mouseInGrid && dropItem) {
        let x = dropItem.x, y = dropItem.y
        let flag = this.checkCanInsert(x, y, type);
        if (flag) {
          let obj = {
            x: x,
            y: y,
            w: DragPos.w,
            h: DragPos.h,
            i: uuidv4(),  //  const { v4: uuidv4 } = require('uuid'); uuid插件自行引入
            data: {
              id: uuidv4(),
              type: type,
              showType: 1
            }
          }
          // 这里可以根据类型的不同配置不同的数据参数
          this.realValue.push(obj);
          this.$emit("addSuccess")
        } else {
          this.$modal.msgWarning("当前组件不能放在该位置");
        }
      }
      this.gridInFocus = false;
      this.realValue = this.realValue.filter(obj => obj.i != 'drop');
      this.$forceUpdate();
    },
其他方法
// 根据鼠标所在的x,y获取组件的下标 131/117为组件默认的宽高
getCurrentIndex (rect) {
      let x_num = Math.floor((mouseXY.x - rect.left) / 131);
      let y_num = Math.floor((mouseXY.y - rect.top) / 117);
      let x = Math.floor((mouseXY.x - rect.left - (18 * x_num)) / 131);
      let y = Math.floor((mouseXY.y - rect.top - (18 * y_num)) / 117);
      if (x < 0) {
        x = 0;
      }
      if (y < 0) {
        y = 0;
      }
      if (DragPos.w + x > 6) {
        x = 6 - DragPos.w;
      }
      if (DragPos.h + y > 3) {
        y = 3 - DragPos.h;
      }
      return { x, y };
    },
// 判断当前组件是否可以插入
checkCanOccupy (layout, myId, x, y, w, h) {
  let A = {
    x, y, w, h
  }
  let flag = true;
  layout.forEach(B => {
    if (B.i != myId) {
      let bw = B.w - 1;
      let bh = B.h - 1;
      let aw = A.w - 1;
      let ah = A.h - 1;
      if (aw >= bw && ah >= bh) {
        console.log(B.x + bw >= A.x, B.y + bh >= A.y, B.x <= A.x + aw, B.y <= A.y + ah)
        if (B.x + bw >= A.x && B.y + bh >= A.y && B.x <= A.x + aw && B.y <= A.y + ah) {
          flag = false
        }
      } else if (aw >= bw && ah < bh) {
        // 插入项宽度大于待检测宽度,但高度小于待检测高度
        if (A.x + aw >= B.x && A.x <= B.x) {
          // 只能放在上面或者下面
          if (A.y > B.y) {
            if (A.y <= B.y + bh) {
              flag = false;
            }
          } else if (A.y == B.y) {
            flag = false;
          } else {
            if (A.y + ah >= B.y) {
              flag = false;
            }
          }
        }
      } else if (aw < bw && ah >= bh) {
        // 插入项高度大于待检测高度,但宽度小于待检测宽度
        if (A.y >= B.y && A.y <= B.y + bh) {
          if (A.x < B.x) {
            // 在左边
            if (A.x + aw >= B.x) {
              flag = false;
            }
          } else if (A.x == B.x) {
            flag = false;
          } else {
            if (A.x <= B.x + bw) {
              // 在右边
              flag = false;
            }
          }
        }else {
          if (A.y + ah >= B.y) {
            if (A.y + ah == B.y) {
              if (A.x + aw >= B.x) {
                flag = false
              }
            } else {
              flag = false
            }
          }
        }
      } else {
        if ((B.x <= A.x + aw && B.y <= A.y + ah && B.x + bw >= A.x + aw && B.y + bh >= A.y + ah)) {
          flag = false
        }
      }
    }
  });
  return flag;
}
Home组件主要内容
data
// 网格数据
layout: [

],
// 当前选中项
currentSelectData: {},
gridFocus: false,
// 当前拖动的box
curBox:null,
// 当前选择项的下标
currentSelectIndex: -1,
<div
   :class="['grid', gridFocus?'focus':'']"
   id="content"
   @dragend="handleChangeEnd($event, 'content')"
   >
     <div
        :class="['grid-item', (item.i=='drop' || item.isSelected)?'drop':'']"
        v-for="(item, index) in layout"
        :key="item.i"
        :style="{
          top:getTop(item) + 'px',
          left:getLeft(item) + 'px',
          width:getWidth(item) + 'px',
          height:getHeight(item) + 'px'
          }"
         :date-i="item.i"
         draggable
         @drag="handleChangeStart($event, item)"
         @dragenter="handleChangeEndter($event, item)"
         @dragend="handleChangeEnd($event, item)"
         @click="clickGrid(item, index)"
         >
             <div
             v-if="currentSelectIndex==index"
             class="delete"
             @click="deleteLayout(item)"
             ></div>
             <div
                class="text"
                v-if="item.data.type == '子组件类型'"
              >
                // 根据需要展示不同的子组件内容
             </div>
     </div>
</div>
获取所在位置
getLeft (item) {
   return item.x * 组件默认的宽度 + item.x * (marginLeft值 根据需求设置)
},
getTop (item) {
   return item.y * 组件默认高度 + item.y * (marginTop值 根据需求设置)
},
getWidth (item) {
   return item.w * 组件默认的宽度 + (item.w - 1) * (marginLeft值 根据需求设置)
},
getHeight (item) {
   return item.h * 组件默认高度 + (item.h - 1) * (marginTop值 根据需求设置)
},
拖拽开始
handleChangeStart (e, item) {
      this.curBox = item;
      this.gridFocus = true;
      let { w, h } = item;
      let mouseXY = {
        x: e.clientX,
        y: e.clientY
      }
      let parentRect = document.getElementById('content').getBoundingClientRect();
      let mouseInGrid = false;
      if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
        mouseInGrid = true;
      }
      // 将临时项加入layout中
      if (mouseInGrid) {
        let { x, y } = this.getCurrentIndex(parentRect, mouseXY, this.curBox)
        let canChange = checkCanOccupy(this.layout, item.i, x, y, item.w, item.h);
        // 判断当前位置是否可以插入
        if (canChange) {
          item.x = x;
          item.y = y;
          this.layout.forEach((lay) => {
            if (lay.i == item.i) {
              lay.isSelected = true;
            } else {
              lay.isSelected = false;
            }
          })
        }
      }
      // 已经加入一个临时数据
      let index = this.layout.findIndex(i => item.i === i.i);
      if (index != -1) {
        // 实时移动位置
        let { x, y } = this.getCurrentIndex(parentRect, mouseXY, this.curBox)
        // 判读当前位置是否有模板
        let canChange = checkCanOccupy(this.layout, item.i, x, y, w, h);
        if (canChange) {
          this.layout.forEach((lay) => {
            if (lay.i == item.i) {
              lay.isSelected = true;
            } else {
              lay.isSelected = false;
            }
          })
        }
      }
    },
已经添加过的组件在网格中进行拖拽更改位置,并判断两个组件是否可以互换位置
//    @dragend="handleChangeEnd($event, item)"
handleChangeEnd (e, item) {
      this.gridFocus = false;
      if (item == 'content' || this.targetBox == null || this.targetBox == this.curBox.i) {
        // 移动到新的位置
        let parentRect = document.getElementById('content').getBoundingClientRect();
        let mouseInGrid = false;
        let mouseXY = {
          x: e.clientX,
          y: e.clientY
        }
        if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
          mouseInGrid = true;
        }
        if (mouseInGrid) {
          let { x, y } = this.getCurrentIndex(parentRect, mouseXY, this.curBox);
          // 判读当前位置是否有模板
          let canOccupy = checkCanOccupy(this.layout, this.curBox.i, x, y, this.curBox.w, this.curBox.h);
          if (canOccupy) {
            this.layout.forEach((ele) => {
              if (ele.i == this.curBox.i) {
                ele.x = x;
                ele.y = y;
                this.currentSelectData = this.deepClone(ele);
              }
            })
          }
        }
      } else {
        // 交换盒子位置
        let targetItem = this.deepClone(this.layout.find((ele) => {
          return ele.i == this.targetBox
        }))
        // 检查两个盒子是否可以交换
        let canChangeIndex = this.checkCanChangeIndex(this.curBox, targetItem);
        if (canChangeIndex) {
          let curItemX = this.curBox.x;
          let curItemY = this.curBox.y;
          this.layout.forEach((ele) => {
            if (ele.i == this.curBox.i) {
              ele.x = targetItem.x;
              ele.y = targetItem.y
            } else if (ele.i == this.targetBox) {
              ele.x = curItemX;
              ele.y = curItemY;
            }
          })
        }
        this.targetBox = null
      }
    },

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值