1 Star 0 Fork 0

KayanChan / drag-model

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

视频目录

面向对象

OOP (Object Oriented Programming): 面向(Oriented)对象(Object)的程序设计(Programming)

class

类: 抽取想象的功能,把他们归在一类

编写Drag类

  • constructor 构造函数: 类的默认方法,创建类的实例化对象时被调用。constructor中的this,指向实例化对象。
  • 一般来讲:实例化对象的属性,定义在构建函数里,对象的方法定义在类里
  • 类的实例化(一切皆对象)

继承的使用

  • extend 和 super
  • 给Drag类添加事件体系
  • 从Drag类中,继承出来 框选 和 拖拽

总结

  • 什么是面向对象编程

    对程序的功能进行抽象,把相似的功能封装成一个类,在使用时,使用的是类的实例化对象。

拖拽

拖拽思路

拖拽过程

  1. 鼠标按下(mousedown): 记录鼠标按下坐标位置、元素初始坐标位置
  2. 鼠标移动(mousemove): 记录新的鼠标坐标位置,计算坐标差值,得到移动距离,设置元素新的坐标
  3. 鼠标抬起(mouseup): 移除移动事件监听

事件监听

  1. 添加事件监听 addEventListener(event, function, useCapture)
    1. event 事件名称
    2. function 回调函数
    3. useCapture
      1. 布尔值:指定事件是否在捕获或冒泡阶段执行
      2. 对象
        1. capture:布尔值,表示监听器是否在捕获阶段执行
        2. once:布尔值,表示监听器是否仅执行一次
        3. passive:布尔值,表示监听器不会调用 preventDefault()
      3. 省略不填,等同false
  2. 移除事件监听 removeEventListener

getComputedStyle

getComputedStyle(element, pseudoElement) 方法用于获取指定元素的 CSS 样式(pseudoElement :伪元素)

getBoundingClientRect

elment.getBoundingClientRect() 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置

获取的样式是元素在浏览器中最终渲染效果的样式

疑问点

  1. 鼠标移动和鼠标抬起事件监听为什么在要鼠标按下里实现? 因为元素位移变化是在鼠标按下时触发,否则鼠标滑过元素就触发位移变化。
  2. 鼠标移动和鼠标抬起的监听对象为什么是document,而不是box? 因为鼠标移动过快时,鼠标不在box上,box则无法监听移动和抬起事件。
  3. 鼠标移动的处理函数move为什么要单独抽取出来? 因为移除移动事件的监听时,需要对鼠标移动的处理函数进行销毁。
  4. 鼠标抬起事件为什么不需要进行手动移除监听? 因为参数设置{once: true}表示监听器只执行一次

框选

框选思路

框选过程

  1. 鼠标按下(mousedown): 记录鼠标按下坐标位置、创建元素,插入dom
  2. 鼠标移动(mousemove): 记录新的鼠标坐标位置,计算坐标差值,得到元素的宽、高、left和top值
  3. 鼠标抬起(mouseup): 移除元素、移除移动事件监听

框选要点

  • 宽度: 按下坐标和移动坐标的横向差值的绝对值
  • 高度: 按下坐标和移动坐标的纵向差值的绝对值
  • left: 按下坐标和移动坐标,更靠近左侧的横坐标
  • top: 按下坐标和移动坐标,更靠近上方的纵坐标

自定义事件

dom提供元素对象进行交互的接口:

  • el.addEventListener('click', fn)
  • el.removeEventListener('click', fn)
  • let ev = new Event('look', {bubbles: true, cancelable: false}) // 生成look事件实例
  • document.dispatchEvent(ev) // 使用dispatchEvent方法触发事件

模拟dom的事件监听机制(订阅者模式)

事件池:存储相应的事件 事件添加机制:添加相应的事件处理函数 实例.on('事件名称', e => {}) 事件删除机制:删除相应的事件处理函数 实例.off('事件名称', e => {}) 事件触发机制:如何触发对应的事件 实例.dispatch('事件名称', e)

class Event {
  constructor() {
    /*
    * this.events = {
      click: [f1,f2,f3],
      mousemove: [f1,f2,f3]
    }
    */
    this.events = {}; // 事件池
  }
  // 添加相应的事件处理
  on(evnet, fn) {
    if (!this.events[evnet]) {
      this.events[evnet] = [];
    }
    this.events[evnet].push(fn);
  }

  // 删除相应的事件处理
  off(evnet, fn) {
    if (this.events[evnet]) {
      this.events[evnet] = this.events[evnet].filter(
        (item) => fn != item
      );
    }
  }

  // 触发相应的事件处理
  dispatch(event, ...arg) {
    if (this.events[event]) {
      this.events[event].forEach((item) => {
        item.call(this, ...arg); // 从window => 指向实例化对象
      });
    }
  }
}

继承

class Parent {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}

// class Child  {
//   constructor(name, age) {
//     this.name = name;
//     this.age = age;
//   }
//   sayName() {
//     console.log(this.name);
//   }
//   sayAge() {
//     console.log(this.age);
//   }
// }

// 继承:把父类的属性和方法都拿过来
class Child extends Parent {
  constructor(name, age) {
    super(name); // 继承父类的构造函数
    this.age = age;
  }
  sayAge() {
    console.log(this.age);
  }
}

let child = new Child("KKB", 18)
child.sayName()
child.sayAge()

Drag类与自定义事件结合

提取拖拽跟框选共同的功能留在Drag类,差异部分功能交给实例的自定义事件处理

class Drag extends Event {
  constructor(el) {
    super();
    this.el = el;
    this.startMouse = {}; // 按下时,鼠标坐标
    let move = (e) => {
      this.move(e);
    };
    this.el.addEventListener("mousedown", (e) => {
      this.start(e);
      document.addEventListener("mousemove", move);
      document.addEventListener(
        "mouseup",
        () => {
          this.end(e);
          document.removeEventListener("mousemove", move);
        },
        { once: true }
      );
    });
  }
  start(e) {
    /*
      * 拖拽:获取按下时元素坐标
      * 框选:创建元素用于画框
      */
    this.startMouse = {
      x: e.clientX,
      y: e.clientY,
    };
    this.dispatch("dragstart", e);
  }
  move(e) {
    /*
      * 拖拽:用按下时元素坐标+差值,计算当前元素的坐标
      * 框选:计算框的宽、高、left、top
      */
    let nowMouse = {
      x: e.clientX,
      y: e.clientY,
    };
    let dis = {
      x: nowMouse.x - this.startMouse.x,
      y: nowMouse.y - this.startMouse.y,
    };
    this.dispatch("dragmove", e, dis, nowMouse);
  }
  end(e) {
    /*
      * 拖拽:不作处理
      * 框选:移除框
      */
    this.dispatch("dragend", e);
  }
}

拖拽

let box = document.querySelector("#box");
let boxDrag = new Drag(box);

let startOffset = {}; // 按下时,元素坐标
boxDrag.on("dragstart", (e) => {
  console.log("开始拖拽时执行", e);
  startOffset = {
    x: parseFloat(getComputedStyle(box)["left"]),
    y: parseFloat(getComputedStyle(box)["top"]),
  };
  e.stopPropagation();
});
boxDrag.on("dragmove", (e, dis, now) => {
  console.log("拖拽中执行", e, dis, now);
  let nowOffset = {
    x: dis.x + startOffset.x,
    y: dis.y + startOffset.y,
  };
  box.style.left = nowOffset.x + "px";
  box.style.top = nowOffset.y + "px";
});

框选

let selectDrag = new Drag(document);
let select = null; // 记录元素
selectDrag.on("dragstart", (e) => {
  start = {
    x: e.clientX,
    y: e.clientY,
  };
  select = document.createElement("div");
  select.className = "selectBox";
  document.body.appendChild(select);
  e.stopPropagation();
});
selectDrag.on("dragmove", (e, dis, now) => {
  // 框的宽度:两个坐标点的横向差值
  select.style.width = Math.abs(dis.x) + "px";
  // 框的高度:两个坐标点的纵向差值
  select.style.height = Math.abs(dis.y) + "px";
  // 框的left:两个坐标点靠近左侧的横坐标(较小的值)
  select.style.left = Math.min(now.x, selectDrag.startMouse.x) + "px";
  // 框的top:两个坐标点靠近上方的纵坐标(较小的值)
  select.style.top = Math.min(now.y, selectDrag.startMouse.y) + "px";
});
selectDrag.on("dragend", (e) => {
  select.remove();
});

注意 在拖拽元素上的mousedown事件会冒泡给到document,从而触发框选的功能 因而需要通过e.stopPropagation();进行阻止冒泡

拖拽子类DragEl

class DragEl extends Drag {
  constructor(...arg) {
    super(...arg);
    this.startOffset = {};
    this.on("dragstart", (e) => {
      this.dragStart(e);
    });
    this.on("dragmove", (e, dis, now) => {
      this.dragMove(e, dis, now);
    });
  }
  dragStart(e) {
    this.startOffset = {
      x: parseFloat(getComputedStyle(this.el)["left"]),
      y: parseFloat(getComputedStyle(this.el)["top"]),
    };
  }
  dragMove(e, dis, now) {
    let nowOffset = {
      x: this.startOffset.x + dis.x,
      y: this.startOffset.y + dis.y,
    };
    this.el.style.left = nowOffset.x + "px";
    this.el.style.top = nowOffset.y + "px";
  }
}

let boxDrag = new DragEl(document.querySelector("#box"));
boxDrag.on("dragstart", (e) => {
  e.stopPropagation(); // 处理冒泡
});

框选子类DragSelect

class DragSelect extends Drag {
  constructor(...arg) {
    super(...arg);
    this.select = null;
    this.startOffset = {};
    this.on("dragstart", (e) => {
      this.dragStart(e);
    });
    this.on("dragmove", (e, dis, now) => {
      this.dragMove(e, dis, now);
    });
    this.on("dragend", (e) => {
      this.dragEnd(e);
    });
  }
  dragStart(e) {
    this.startOffset = {
      x: e.clientX,
      y: e.clientY,
    };
    this.select = document.createElement("div");
    this.select.className = "selectBox";
    document.body.appendChild(this.select);
    e.stopPropagation(); // 处理冒泡
  }
  dragMove(e, dis, now) {
    // 框的宽度:两个坐标点的横向差值
    this.select.style.width = Math.abs(dis.x) + "px";
    // 框的高度:两个坐标点的纵向差值
    this.select.style.height = Math.abs(dis.y) + "px";
    // 框的left:两个坐标点靠近左侧的横坐标(较小的值)
    this.select.style.left = Math.min(now.x, this.startMouse.x) + "px";
    // 框的top:两个坐标点靠近上方的纵坐标(较小的值)
    this.select.style.top = Math.min(now.y, this.startMouse.y) + "px";
  }
  dragEnd(e) {
    this.select.remove();
  }
}

let selectDrag = new DragSelect(document);

碰撞

碰撞检测思路

碰撞检测:哪些条件发生碰撞 => 哪些条件不发生碰撞

  • 红.right < 黄.left (左)
  • 红.left > 黄.right (右)
  • 红.bottom < 黄.top (上)
  • 红.top > 黄.bottom (下)
<style>
  #box {
    position: absolute;
    left: 0;
    top: 0;
    width: 100px;
    height: 100px;
    background: red;
  }
  #box2 {
    position: absolute;
    left: calc(50% - 100px);
    top: calc(50% - 100px);
    width: 100px;
    height: 100px;
    background: yellow;
    z-index: 2;
  }
  .box3 {
    float: left;
    margin-right: 50px;
    width: 100px;
    height: 100px;
    background: blue;
    z-index: 2;
  }
</style>

<div id="box"></div>
<div id="box2"></div>
<div class="box3"></div>
<div class="box3"></div>
<div class="box3"></div>
<div class="box3"></div>
<div class="box3"></div>

<script>
  function boom(el1, el2) {
    let el1Rect = el1.getBoundingClientRect();
    let el2Rect = el2.getBoundingClientRect();
    if (
      el1Rect.right < el2Rect.left ||
      el1Rect.left > el2Rect.right ||
      el1Rect.bottom < el2Rect.top ||
      el1Rect.top > el2Rect.bottom
    ) {
      return false;
    }
    return true;
  }
  {
    let box = document.querySelector("#box");
    let box2 = document.querySelector("#box2");
    let box3 = document.querySelectorAll(".box3");
    let boxDrag = new DragEl(box);
    boxDrag.on("dragmove", () => {
      if (boom(box, box2)) {
        box.style.background = "green";
      } else {
        box.style.background = "red";
      }
      box3.forEach((item) => {
        if (boom(box, item)) {
          // box.style.background = "green"; // 经过循环后,依旧是未碰撞的情况
          item.style.background = "green";
        } else {
          item.style.background = "blue";
        }
      });
    });
  }
</script>

空文件

简介

拖拽、框选、碰撞模型、类实现 展开 收起
JavaScript
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/kayanchan/drag-model.git
git@gitee.com:kayanchan/drag-model.git
kayanchan
drag-model
drag-model
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891