js 元素可随意拖动变形

1. 实现目标效果

2. 实现原理

        1. 前言

       在这之前可以先进入我的博客js 仿window拉选框 / 选中文件  可以先看一下这篇文章,实现原理大同小异,最后我会贴上代码方便使用。 

        如果遇到缩放问题,可以使用“window.devicePixelRatio”这段代码获取缩放比例,并加入到计算方向的代码块中。

        2. 规则

        可以看到每个方块的”上左,中上,上右,中左,中右,下左,中下,下右“,8个顶点,而”中上,中下,中左,中右“只能向一个轴方向运动,其余的点可以随意拖动

        3.原理

        可以看到最上方gif图,当拖动”上左”的点时,可以找到对角“下右”为二维坐标系的原点,心里可以想象一个二维坐标系,如图2.3.1,拖动点击的点,可以想象成,初始点击的位置在“下右”移动到了“上左”,到这里就和我前言里推荐大家先去看的那篇文章一样了。

        拖动”中上,中下,中左,中右“这四个点时,可以在元素中心想象一个二维坐标系,如图2.3.2,“中上,中下”为Y轴,“中左,中右”为X轴,当在Y轴运动时只需要修改元素的top和height属性,X轴运动时只需要修改left和width属性

        最后就可以生成一个json格式数据传给后端了

图2.3.1                                                         图2.3.2

 

 3. 代码

        方法解释

  1. initEL 给大盒子初始化状态
  2. addEventEl 给小盒子添加对应的事件
  3. ElMouseDown 小盒子鼠标按下方法|顶点鼠标按下事件
  4. ElMouseMove 小盒子鼠标移动方法
  5. ElMouseUp     小盒子鼠标抬起方法
  6. itemMouseMove 顶点移动方法
  7. itemMouseUp     顶点抬起方法
  8. initItemEl            初始化小盒子的状态
  9. initItemBox         处理顶点拖动效果
  10. addItemBoxDom   给小盒子添加顶点
  11. getClientRect        获取元素坐标属性(bottom height left right top width x y)
  12. isOutBoundary     判断拖动小盒子是否到达边界
  13. $       获取单个元素dom
  14. $All   获取多个元素dom

在 addItemBoxDom方法中会为所有顶点添加Attribute属性“data-index”用于区分具体哪个顶点 

可以使用drag类进行二次封装,添加自己想要的方法,不懂得可以私信博主

const drag = new Drag({
  el:'.container',      // 盒子
  item:'.drag-item',    // 小盒子
});
class Drag{
  constructor({
    el,item
  }){
    this.el = this.$(el);
    this.item = item;
    this.itemList = this.$All(item);
    this.itemBoxSize = 5;

    this.InitPointer = {x:0,y:0}; // 鼠标初始位置
    this.currentEl = null;
    this.currentElItem = null;
    this.isActive = false; // 标记是否第一次点击
    this.init();
  }
  init(){
    this.initEl();
    this.initItemEl();
    this.addEventEl();
  }
  initEl(){
    this.el.style.cssText = 'position: relative;';
  }
  addEventEl(){
    for(let i = 0; i < this.itemList.length; i++){
      this.itemList[i].addEventListener('mousedown',this.ELMouseDown);
    }
  }

  ELMouseDown = (e) => {
    this.currentEl = e.target;
    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;
    // 区分方块
    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;
    }
    document.addEventListener('mousemove',this.ELMouseMove);
    document.addEventListener('mouseup',this.ELMouseUp);
  }
  ELMouseMove = (e) => {
    // this.removeLine();
    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.removeLine();
  }
  itemMouseMove = (e) => {
    this.initItemBox(e)
  }
  itemMouseUp = (e) => {
    document.removeEventListener('mousemove',this.itemMouseMove);
    document.removeEventListener('mouseup',this.itemMouseUp);
  }

  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 div = document.createElement('div');
    div.classList = "drag-item-box";
    div.setAttribute('data-index',index);
    div.style.cssText = `z-index:1000;cursor:pointer;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);
  }
  // 获取元素属性
  getClientRect(dom){
    // bottom height left right top width x y
    return dom.getBoundingClientRect();
  }
  // 判断方块是否到边界
  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';}
  }
  $(el){
    return document.querySelector(el);
  }
  $All(el){
    const el_list = document.querySelectorAll(el);
    if(el_list.length <= 0){
      return [];
    }
    return el_list;
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值