面向对象(三):面向过程的拖拽、面向对象的拖拽、拷贝拖拽以及事件监听的处理机制

163 阅读4分钟
  1. 先看一个面向过程的拖拽实现方式

拖拽的实现思路:(下面参考代码)

  • 鼠标触发mousedown时:记录起始的鼠标点击位置;记录起始的元素位置;事件绑定到该元素上

  • 鼠标触发mousemove时:

    记录鼠标当前的位置;

    当前的鼠标距离 = 当前的鼠标距离 - 起始的鼠标距离;

    现在的位置 = 开始的元素位置 + 鼠标移动的差值;

    事件绑定到document上(防止鼠标移动过快将元素甩掉)

  • 鼠标触发mouseup时:

    取消事件处理函数 move,move 函数需要抽离,不可使用匿名函数。因为removeEventListener不可取消匿名函数;

    设置 once : true 避免up时中间的代码执行多次

{
    let box = document.querySelector("#box");
    let startMouse = {};  // 摁下时鼠标位置
    let startPosition = {};  // 摁下时元素位置
    // 鼠标触发mousemove事件时的事件函数
    let move = (e)=>{
        // 鼠标当前的位置
        let nowMouse={
            x: e.clientX,
            y: e.clientY
        };
        // 当前的鼠标距离:当前的鼠标距离 - 起始的鼠标距离
        let dis = {
            x: nowMouse.x - startMouse.x,
            y: nowMouse.y - startMouse.y
        };
        // 现在的位置:开始的元素位置 + 鼠标移动的差值
        box.style.left = dis.x + startPosition.x + "px";
        box.style.top = dis.y + startPosition.y + "px";
    };
    box.addEventListener("mousedown",(e)=>{
        // 起始的鼠标点击位置
        startMouse={
            x: e.clientX,
            y: e.clientY
        };
        // 起始的元素位置
        startPosition = {
            x: box.offsetLeft,
            y: box.offsetTop
        }
        document.addEventListener("mousemove",move);  
        // 这里move不能用匿名函数,否则removeEventListener的时候无法取消,removeEventListener取消事件监听不可使用匿名函数
        document.addEventListener("mouseup",()=>{
            document.removeEventListener("mousemove",move);
            // 设定once: true 否则每次mouseup都会再触发一次这里面的代码
        },{once:true})
       
    });
} 
<div id="box"></div>
<div id="box2"></div>
  1. 面向对象的拖拽实现方式
  • 什么是对象?

    对象就是一个封装了数据(属性)和方法的集合体。

  • 什么是面向对象程序设计

    (Object Oriented Programming -- OOP)

    一种编程方法,关注某一个对象的属性和方法,关注对象与对象之间的联系

 <script>
    class Drag {
        constructor(el){
            this.el = el;
            this.startMouse = {};
            this.startPosition = {};
            let move = (e)=>{
                this.move(e);
            };
            //当开始拖拽时
            this.ondragstart = null;
            //拖拽中
            this.ondrag = null;
            //拖拽结束
            this.ondragend = null; 
            this.el.addEventListener("mousedown",(e)=>{
                this.start(e);
                document.addEventListener("mousemove",move);
                document.addEventListener("mouseup",(e)=>{
                    document.removeEventListener("mousemove",move);
                    this.end(e);
                },{once:true})
            });
        }
    start(e){
        this.startMouse={
            x: e.clientX,
            y: e.clientY
        };
        this.startPosition = {
            x: this.getStyle("left"),
            y: this.getStyle("top")
        };
        e.preventDefault();
        // 判断用户是否给该实例对象添加 ondragstart 的处理函数
        this.ondragstart&&(this.ondragstart(e));
    }
    move(e){
        let nowMouse = {
            x: e.clientX,
            y: e.clientY
        };
        let disMouse = {
            x: nowMouse.x - this.startMouse.x,
            y: nowMouse.y - this.startMouse.y
        };
        this.setStyle("left",disMouse.x + this.startPosition.x);
        this.setStyle("top",disMouse.y + this.startPosition.y);
        // 判断用户是否给该实例对象添加 ondrag 的处理函数
        this.ondrag&&(this.ondrag(e));
    }
    end(e){
        // 判断用户是否给该实例对象添加 ondragend 的处理函数
        this.ondragend&&(this.ondragend(e));
    }
    //不加=,直接写个方法,会加在类的原型中
    getStyle(attr){
        return parseFloat(getComputedStyle(this.el)[attr]);
    }
    setStyle(attr,val){
        this.el.style[attr] = val + "px";
    }
} 
// 拷贝拖拽
// 在子类中不写 constructor,会直接继承父类的 constructor
class CopyDrag extends Drag {
    copyNode = null; 
    // 相当于 this.copyNode = null;
    // 这里通过 = 赋值的属性会加在实例化对象中
    ondragstart = (e) => {
        this.copyNode = this.el.cloneNode(true);
        this.el.parentNode.appendChild(this.copyNode);
        this.el.style.opacity = 0.5;
    }
    ondragend = (e) => {
        this.el.parentNode.removeChild(this.copyNode);
        this.el.style.opacity = 1;
    }
}
/*
    怎么可以让我们的类使用起来更灵活
        事件绑定:告诉使用者我有哪些事件
*/
{
    let box1 = document.querySelector("#box1");
    let box1Drag = new Drag(box1);
    box1Drag.ondragstart = function(e){
        console.log("dragstart");    
    };
    box1Drag.ondrag = function(e){
        console.log("drag");    
    };
    box1Drag.ondragend = function(e){
       // box1Drag.ondragstart = null;
        console.log("dragend");    
    };

    let box2 = document.querySelector("#box2");
    let box2Drag = new Drag(box2);
}
    </script>
  1. 事件监听的处理机制
    /* el.addEventListener('eventName', fn)
     * 事件处理机制:
     * 1. 事件队列池
          events = {
            "click":[f1,f2,f3],
            "mousedown":[f1,f2],
            "mouseup":[f2,f4]
          };
     * 2. on 绑定事件
     * 3. dispatch 派发事件
     * 4. off 取消事件绑定
     
        当用户点击时:
        查看 events.click 中是否有事件处理函数
            如果有,就循环把这些事件处理函数都执行了
     */
     
    //实现一套处理监听机制    
    class Event {
        //事件池,用来记录每一个事件的处理函数
        events = {};
        //添加事件监听
        on(eventName,fn){ 
            if(!this.events[eventName]){
                this.events[eventName] = [];
            }
            this.events[eventName].push(fn);
        }
        //取消事件监听
        off(eventName,fn){
            if(this.events[eventName]){
                this.events[eventName] = this.events[eventName].filter(item=>(item!=fn));
            }
        }
        //触发器 调用dispatch(eventName) 时,将相应的函数都执行了
        dispatch(eventName){ 
            if(this.events[eventName]){
                this.events[eventName].forEach(item => {
                    item.call(this);
                });
            }
        }
    }
  1. 实现一个复制拷贝的拖拽
<script>
    class Event {
        events = {}
        on (eventName, fn) {
            if (!this.events[eventName]) {
                this.events[eventName] = []
            }
            this.events[eventName].push(fn)
        }
        off (eventName, fn) {
            if (this.events[eventName]) {
                this.events[eventName] = this.events[eventName].filter((item, i) => {
                    return item !== fn
                })
            }
        }
        dispatch (eventName, event) {
            if (this.events[eventName]) {
                this.events[eventName].forEach((item, i) => {
                    item.call(this, event)
                })
            }
        }
    }
    class Drag extends Event{
        constructor(el) {
            //注意:在子类中,写 constructor 一定要加 super
            super()  
            this.el = el
            this.startM = {}
            this.startP = {}
            this.init()
        }
        init() {
            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.startM = {
                x: e.clientX,
                y: e.clientY
            }
            this.startP = {
                x: this.css(this.el, 'left'),
                y: this.css(this.el, 'top')
            }
            e.preventDefault()
            // 触发startDrag相应的事件处理
            this.dispatch('startDrag', e)
        }
        move (e) {
            let nowM = {
                x: e.clientX,
                y: e.clientY
            }
            let dis = {
                x: nowM.x - this.startM.x,
                y: nowM.y - this.startM.y
            }
            this.css (this.el, 'left', this.startP.x + dis.x)
            this.css (this.el, 'top', this.startP.y + dis.y)
            // 触发moveDrag相应的事件处理
            this.dispatch('moveDrag', e)
        }
        end (e) {
            this.dispatch('endDrag', e)
        }
        css (el, attr, val) {
            if(arguments.length == 2) {
                return parseInt(getComputedStyle(el)[attr])
            } else {
                el.style[attr] = val + 'px'
            }
        }
    }

    class copyDrag extends Drag{
        constructor(...arg) {
            console.log(...arg)
            super(...arg)
            this.on('startDrag', () => {
                this.startDrag()
            })
            this.on('endDrag', () => {
                this.endDrag()
            })
        }
        startDrag(){
            this.newNode = this.el.cloneNode(true)
            this.el.style.opacity = 0.5
            this.el.parentNode.appendChild(this.newNode)
        }
        endDrag() {
            this.el.parentNode.removeChild(this.newNode)
            this.el.style.opacity = 1
        }
    }
    {
        let box1 = document.querySelector('#box1')
        let darg1 = new Drag(box1);
        let box2 = document.querySelector('#box2')
        let darg2 = new copyDrag(box2);
    }
</script>