js 元素拖动有标线

实现目标效果

前言

        在这之前,推荐大家先看一下上一篇文章 js 元素可随意拖动变形 这篇实现了,具体实现了大部分功能,这个标线就是在那基础上增加的,但是并没有增加标线吸附效果。

1. 实现原理

        在getClientRect方法中可以获取到想要的数据“left,right,top,bottom”,在这个基础上可以进行双层for循环

        第一次for循环循环小盒子,并判断是不是当前元素,是的话跳过循环

        第二次for循环arr数组获取itemInfo中的数据

        第三次for循环arr数组获取this.currentEl中的数据

        在判断中需要增加一个限制,例如:top:100 == left:100这样的标线出现的位置就会错乱出现在left,增加的限制具体判断这两个属性是否在同一轴上,不是同一轴的跳过判断,不创建标线,可以看RestrictionDirection方法

// 判断是否出现标线
isShowLine(){
  const arr = ['bottom','left','top','right'];
  const rect = this.getClientRect(this.currentEl);
  for(let i = 0; i < this.itemList.length; i++){
    const item = this.itemList[i];
    const itemInfo = this.getClientRect(item);
    if(this.currentEl === item) continue;
    for(let j = 0; j < arr.length; j++){
      for(let k = 0; k < arr.length; k++){
        if(this.RestrictionDirection(arr[k],arr[j]) && rect[arr[k]] == itemInfo[arr[j]]){
          this.createShowLine(arr[j],itemInfo[arr[j]]);
        }
      }
    }
  }
}
// 限制方向
RestrictionDirection(type1,type2){
  const arr = [['bottom','top'],['left','right']]; 
  for(let i = 0; i < arr.length; i++){
    if(arr[i].includes(type1) && arr[i].includes(type2)){
      return true;
    }else if(arr[i].includes(type2) && arr[i].includes(type1)){
      return true;
    }
  }    
}
createShowLine(type,num){
  // type 方向 num 位置
  const arr1 = ['top','bottom'];
  const arr2 = ['left','right'];
  const line = document.createElement('div');
  line.id = 'drag-line';
  line.style.cssText = `
      position: absolute;
      width: ${arr1.includes(type) ? '100%' : '1px'};
      height: ${arr2.includes(type) ? '100%' : '1px'};
      background-color: ${this.markingColor};
      ${arr1.includes(type) ? 'top' : 'left'}: ${num}px;
  }`;
  this.el.appendChild(line);
}

3. 标线出现的时间和消失的时间 

        标线出现的时间,我这里设置的是在移动时或顶点拉动元素时,

        标线消失的时间,我这里设置的是小盒子鼠标抬起时和顶点鼠标抬起时

4. 代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="./drag.js"></script>
  <title>js 拖动元素(标线)</title>
  <style>
    *{
      margin: 0;
      padding: 0;
    }
    .container{
      width: 600px;
      height: 600px;
      background-color: aqua;
    }
    .drag-item{
      width: 100px;
      height: 100px;
      cursor: move;
      z-index: 1000;
      user-select: none;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="drag-item" style="background-color: blue;">1</div>
    <div class="drag-item" style="background-color: red;">2</div>
    <div class="drag-item" style="background-color: green;">3</div>
  </div>
  <button onclick="getData()">获取JSON数据</button>
  <script>
class Drag{
  constructor({
    el,item,marking,markingColor,dashed,dashedColor
  }){
    this.el = this.$(el);
    this.item = item;
    this.itemList = this.$All(item);
    this.itemBoxSize = 5;

    this.marking = marking;
    this.markingColor = markingColor;
    this.dashed = dashed;
    this.dashedColor = dashedColor;

    this.InitPointer = {x:0,y:0}; // 鼠标初始位置
    this.currentEl = null;
    this.currentElItem = null;
    this.isActive = false; // 标记是否第一次点击

    this.zIndex = 2000;
    
    this.init();
  }
  init(){
    this.initEl();
    this.initItemEl();
    this.addEventEl();
    this.resetShowItem();
  }
  initEl(){
    this.el.style.cssText = 'position: relative;';
  }
  addEventEl(){
    document.addEventListener('mousedown',this.windowMouseDown);
    for(let i = 0; i < this.itemList.length; i++){
      this.itemList[i].addEventListener('mousedown',this.ELMouseDown);
    }
  }
  // 处理点击空白区域方法
  windowMouseDown = (e) => {
    this.resetShowItem();
    this.removeDashed();
  }
  ELMouseDown = (e) => {
    // 阻止向下冒泡
    e.stopPropagation();
    if(this.currentEl && e.target.className !== 'drag-item-box' && this.currentEl != e.target){
      this.resetShowItem();
      this.removeDashed();
    }
    this.currentEl = e.target;
    // 设置this.currentEL的z-index属性
    this.currentEl.style.zIndex = this.zIndex++;
    let rect = this.getClientRect(this.currentEl);
    this.elInitPointer = {x:e.x,y:e.y,rectX:rect.left,rectY:rect.top,rectW:rect.width,rectH:rect.height};
    this.currentElItem = e.target;
    
    this.showCurrentItem();
    
    // 区分方块
    if(e.target.className == 'drag-item-box'){
      this.isActive = true
      this.currentEl = e.target.offsetParent
      rect = this.getClientRect(this.currentEl);
      this.elInitPointer = {x:e.x,y:e.y,rectX:rect.left,rectY:rect.top,rectW:rect.width,rectH:rect.height,top:rect.top,left:rect.left};
      document.addEventListener('mousemove',this.itemMouseMove);
      document.addEventListener('mouseup',this.itemMouseUp);
      return;
    }
    this.createDashed();
    document.addEventListener('mousemove',this.ELMouseMove);
    document.addEventListener('mouseup',this.ELMouseUp);
  }
  ELMouseMove = (e) => {
    this.removeShowLine();
    const {x:initX,y:initY,rectX,rectY} = this.elInitPointer;
    const {x,y} = e;
    const xMove = rectX + x - initX;
    const yMove = rectY + y - initY;
    this.currentEl.style.top = `${yMove}px`;
    this.currentEl.style.left = `${xMove}px`;
    this.isOutBoundary();
    this.isShowLine();
  }
  ELMouseUp = (e) => {
    document.removeEventListener('mousemove',this.ELMouseMove);
    document.removeEventListener('mouseup',this.ELMouseUp);
    this.removeShowLine();
  }
  itemMouseMove = (e) => {
    this.removeShowLine();
    this.initItemBox(e);
    this.isShowLine()
  }
  itemMouseUp = () => {
    document.removeEventListener('mousemove',this.itemMouseMove);
    document.removeEventListener('mouseup',this.itemMouseUp);
    this.removeShowLine();
  }
  // 当前元素顶点显示
  showCurrentItem(){
    const list = this.currentEl.querySelectorAll('.drag-item-box');
    if(list.length === 0) return;
    for(let i = 0; i < list.length; i++){
      list[i].style.display = 'block';
    }
  }
  // 重置所有顶点显示
  resetShowItem(){
    const list = this.$All('.drag-item-box');
    if(list.length === 0) return;
    for(let i = 0; i < list.length; i++){
      list[i].style.display = 'none';
    }
  }
  createDashed(){
    if(this.dashed && this.currentEl){
      this.currentEl.style.outline = '1px dashed ' + this.dashedColor;
    }
  }
  removeDashed(){
    if(this.currentEl){
      this.currentEl.style.outline = '';
    }
  }
  // 判断是否出现标线
  isShowLine(){
    const arr = ['bottom','left','top','right'];
    const rect = this.getClientRect(this.currentEl);
    for(let i = 0; i < this.itemList.length; i++){
      const item = this.itemList[i];
      const itemInfo = this.getClientRect(item);
      if(this.currentEl === item) continue;
      for(let j = 0; j < arr.length; j++){
        for(let k = 0; k < arr.length; k++){
          if(this.RestrictionDirection(arr[k],arr[j]) && rect[arr[k]] == itemInfo[arr[j]]){
            this.createShowLine(arr[j],itemInfo[arr[j]]);
          }
        }
      }
    }
  }
  // 限制方向
  RestrictionDirection(type1,type2){
    const arr = [['bottom','top'],['left','right']]; 
    for(let i = 0; i < arr.length; i++){
      if(arr[i].includes(type1) && arr[i].includes(type2)){
        return true;
      }else if(arr[i].includes(type2) && arr[i].includes(type1)){
        return true;
      }
    }    
  }
  createShowLine(type,num){
    // type 方向 num 位置
    const arr1 = ['top','bottom'];
    const arr2 = ['left','right'];
    const line = document.createElement('div');
    line.id = 'drag-line';
    line.style.cssText = `
        position: absolute;
        width: ${arr1.includes(type) ? '100%' : '1px'};
        height: ${arr2.includes(type) ? '100%' : '1px'};
        background-color: ${this.markingColor};
        ${arr1.includes(type) ? 'top' : 'left'}: ${num}px;
    }`;
    this.el.appendChild(line);
  }
  removeShowLine(){
    const list = this.$All('#drag-line');
    if(list.length === 0) return;
    list.forEach((item) => {
      item.remove();
    })
  }

  initItemEl(){
    const boxItme = 8;
    for(let i = 0; i < this.itemList.length; i++){
      const item = this.itemList[i];
      item.style.position = 'absolute';
      item.style.left = '50px';
      item.style.top = `${i * 120}px`
      for(let j = 0; j < boxItme; j++){
        this.addItemBoxDom(item,j);
      }
    }
  }
  initItemBox(e){
    this.isOutBoundary();
    const data_index = Number(this.currentElItem.getAttribute('data-index'));
    const id = '.drag-item-box';
    const list = this.currentEl.querySelectorAll(id);
    const arr = [0,2,5,7];
    const arr1 = [1,6];

    const {x:initX,y:initY,rectW,rectH,left,top} = this.elInitPointer;
    let x = 0;
    let y = 0;
    let moveX = 0;
    let moveY = 0;
    if(this.isActive){
      const obj = {
        0:()=>{
          x = left + rectW;
          y = top + rectH;
          moveX = left;
          moveY = top;
        },
        2:() => {
          x = left;
          y = top + rectH;
          moveX = left + rectW;
          moveY = top;
        },
        5:() => {
          x = left + rectW;
          y = top;
          moveX = left;
          moveY = top + rectH;
        },
        7:() => {
          x = left;
          y = top;
          moveX = left + rectW;
          moveY = top + rectH;
        },
        1:() => {
          y = top + rectH;
          moveY = top;
        },
        6:()=>{
          y = top;
          moveY = top + rectH;
        },
        3:()=>{
          x = left + rectW;
          moveX = left;
        },
        4:()=>{
          x = left;
          moveX = left + rectW;
        },
      }
      obj[data_index]();
      this.elInitPointer.x = x;
      this.elInitPointer.y = y;
      this.isActive = false;
    }else{
      x = initX;
      y = initY;
      moveX = e.clientX;
      moveY = e.clientY;
    }
    // 计算移动后对应元素宽高
    const width = Math.abs(x - moveX);
    const height = Math.abs(y - moveY);
    if(arr.includes(data_index)){
      if(moveX > x && moveY > y){ // 4
        this.currentEl.style.left = x + 'px';
        this.currentEl.style.top = y + 'px';
      }else if(y > moveY && x < moveX){ // 1
        this.currentEl.style.top = moveY + 'px';
        this.currentEl.style.left = x + 'px';
      }else if(moveX < x && moveY > y){ // 3
        this.currentEl.style.left = moveX + 'px';
        this.currentEl.style.top = y + 'px';
      }else if(moveY < y && moveX < x){ // 2
        this.currentEl.style.top = moveY + 'px';
        this.currentEl.style.left = moveX + 'px';
      }
      this.currentEl.style.height = height + 'px';
      this.currentEl.style.width = width + 'px';
    }else{
      if(arr1.includes(data_index)){
        if(moveY > y){
          this.currentEl.style.top = y + 'px';
        }else{
          this.currentEl.style.top = moveY + 'px';
        }
        this.currentEl.style.height = height + 'px';
      }else{
        if(moveX > x){
          this.currentEl.style.left = x  + 'px';
        }else{
          this.currentEl.style.left = moveX + 'px';
        }
        this.currentEl.style.width = width + 'px';
      }
    }
    
  }
  addItemBoxDom(dom,index){
    /**
     * 0  1  2
     * 3     4
     * 5  6  7
     */
    const cursors = ['nw-resize','n-resize','sw-resize','e-resize','w-resize','ne-resize','n-resize','nw-resize']
    const div = document.createElement('div');
    div.classList = "drag-item-box";
    div.setAttribute('data-index',index);
    div.style.cssText = `z-index:1000;cursor:${cursors[index]};position: absolute;width:${this.itemBoxSize}px;height:${this.itemBoxSize}px;border:1px solid #000;`;
    const rect = this.getClientRect(dom);
    let x = 0;
    let y = 0;
    // itemBoxSize 一半
    const itemBoxh = this.itemBoxSize / 2;
    const style = {
      0:() => {
        x = `calc(-${itemBoxh}px)`
        y = `calc(-${itemBoxh}px)`;
      },
      1:()=> {
        x = `calc(50% - ${itemBoxh}px)`;
        y = `calc(-${itemBoxh}px)`;
      },
      2:()=> {
        x = `calc(100% - ${itemBoxh}px)`;
        y = `calc(-${itemBoxh}px)`;
      },
      3:()=> {
        x = `calc(-${itemBoxh}px)`;
        y = `calc(50% - ${itemBoxh}px)`;
      },
      4:()=> {
        x = `calc(100% - ${itemBoxh}px)`;
        y = `calc(50% - ${itemBoxh}px)`;
      },
      5:()=> {
        x = `calc(-${itemBoxh}px)`;
        y = `calc(100% - ${itemBoxh}px)`;
      },
      6:()=> {
        x = `calc(50% - ${itemBoxh}px)`;
        y = `calc(100% - ${itemBoxh}px)`;
      },
      7:()=> {
        x = `calc(100% - ${itemBoxh}px)`;
        y = `calc(100% - ${itemBoxh}px)`;
      }
    }
    style[index]();
    div.style.left = x;
    div.style.top = y;
    dom.appendChild(div);
  }
  // 判断方块是否到边界
  isOutBoundary(){
    const container = this.getClientRect(this.el)
    const rect = this.getClientRect(this.currentEl);
    if(rect.left <= 0){this.currentEl.style.left = container.left;}
    if(rect.top <= 0){this.currentEl.style.top = container.top;}
    if(rect.left >= container.width - rect.width){this.currentEl.style.left = container.width - rect.width + 'px';}
    if(rect.top >= container.height - rect.height){this.currentEl.style.top = container.height - rect.height + 'px';}
  }
  // 获取元素属性
  getClientRect(dom){
    // bottom height left right top width x y
    const style = getComputedStyle(dom);
    return Object.assign(dom.getBoundingClientRect(),{zIndex:parseInt(style.zIndex)}) ;
  }
  $(el){
    return document.querySelector(el);
  }
  $All(el){
    const el_list = document.querySelectorAll(el);
    if(el_list.length <= 0){
      return [];
    }
    return el_list;
  }
  // 获取子元素数据
  getJSONData(file = false){
    const data = {};
    for(let i = 0; i < this.itemList.length; i++){
      const item = this.itemList[i];
      const itemInfo = this.getClientRect(item);
      data[i] = {
        left:itemInfo.left,
        top:itemInfo.top,
        width:itemInfo.width,
        height:itemInfo.height,
        z_index:itemInfo.zIndex
      }
    }
    if(file){
      const str = JSON.stringify(data);
      var blob = new Blob([str], { type: 'text/plain' });
      var a = document.createElement('a');
      a.href = window.URL.createObjectURL(blob);
      a.download = 'data.txt';
      a.click();
    }
    return data;
  }
}

    const drag = new Drag({
      el:'.container',      // 盒子
      item:'.drag-item',    // 小盒子
      marking:true,         // 是否显示标线
      markingColor:'#000',  // 标线颜色
      dashed:true,          // 是否显示虚线
      dashedColor:'#fff',
    });
    function getData(){
      const res = drag.getJSONData();
      console.dir(res)
    }
  </script>
</body>
</html>

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值