隔離 鼠標點擊 雙擊 移動事件

隔離 鼠標點擊 雙擊 移動事件

應用場景

在編寫一個3d卡片切換的時候,需要執行
1. **單擊** 觸發模態框 
2. **雙擊** 卡片定位
3. **拖動** 卡片跟隨

問題

默認dom事件 
鼠標**雙擊**的時候會觸發**單擊**
鼠標按下**拖動**的開始會觸發**單擊**

因此需要分離這三種事件 雙擊無法觸發單擊 拖動時無法觸發單擊

解決方案

思路

2.實現點擊事件
 分析:需要的點擊事件   單擊  雙擊  拖拽(移動)
        通過mousedown , mouseup , mousemove ,mouseleave控制
   2.0 當鼠標事件
          按下時 mousedown
            註冊mousemove , mouseup 和 mouseleave 
          擡起時 mouseup
            註銷mousemove , mouseup 和 mouseleave, 判斷事件類型<單擊,雙擊,已觸發的移動> 並且 清空所有按鍵信息
          離開時 mouseleavel
            註銷mousemove , mouseup 和 mouseleave  並且 清空所有按鍵信息
   2.1 單擊 mouseup事件下
           當鼠標不是在移動(mouseCtrl.ismoving爲false)且
           當鼠標按下的和擡起的間隔小於一個值默認150毫秒 mouseDiffTime < 150 即爲一次有效點擊 
           開啓一個異步延遲的單擊事件  延遲事件 默認爲300毫秒 將事件id緩存下來
   2.2 雙擊 mouseup事件下
           觸發同樣的單擊事件判斷
           如果存在事件id則代表上一次單擊還未執行, (那即這是150毫秒+300毫秒 之間的第二此點擊  即爲雙擊)
           取消事件id的執行(取消單擊事件) 
           觸發雙擊事件
   2.3 移動 mousemove 事件下
           判斷 鼠標按下的初始位置和當前位置的x距離如果(mouse.moveX) 大於 20 
           觸發移動事件

實現代碼

第一部分爲事件系統提供一個 註冊 銷燬 觸發 的函數

/**一個事件註冊函數 
* @constructor
*/
function RegisterEvents() {
    /** 從數組中移除某個元素
     * @param {[]} arr 
     * @param {*} target 
     */
    var removeItemFromArr = function (arr , target) {
        for (var i = 0; i < arr.length; i++) {
            var element = arr[i];
            if(element === target) return arr.splice(i,1)[0];
        }
        return null;
    };
    
    var eventsStore = {
        "click":[],
        "dblclick":[],
        "movingEvent":[]
    };

    /** 註冊事件函數
     * @param {'click'| 'dblclick' | 'movingEvent'} type
     * @param {Function} fnc params <{}>
     */
    this.on = function (type,fnc) {
        if(eventsStore[type] === undefined)  throw new Error("綁定的事件類型不存在");
        if(typeof fnc !== 'function') throw new Error("綁定的事件不是可執行函數");
        eventsStore[type].push(fnc);
    };

    /** 移除事件函數
     * @param {'click'| 'dblclick' | 'movingEvent'} type
     * @param {Function} fnc 
     */
    this.off = function (type,fnc) {
        if(type && eventsStore[type] === undefined)  throw new Error("綁定的事件類型不存在");
        if(type === undefined){
            eventsStore = [];
        }else if(typeof fnc !== 'function'){
            eventsStore[type].length = 0;
        }else{
            removeItemFromArr(eventsStore[type],fnc);
        }
    };
    
    /** 移除事件函數
     * @param {'click'| 'dblclick' | 'movingEvent'} type
     * @param {{event:EventTarget}} info ?
     */
    this.emit = function (type,info) {
        if(eventsStore[type] === undefined)  throw new Error("綁定的事件類型不存在");
        eventsStore[type].forEach(e=>e(info));
    };
}

第二部分 主體實現分離

/**提供事件系統 增加 單擊 雙擊 移動 事件
 * @constructor
 * @param {JQuery<HTMLElement> } dom 
 * @extends RegisterEvents
 */
function EventSystem(dom) {

    /**保存當前實例 */
    var instance = this;

    if(!dom.jquery) dom = $(dom);
    var mouseCtrl = generateMouseCtrl();
    
    /**鼠標按下時 註冊鼠標移動函數 */
    dom.on('mousedown',  function (e) {
        mouseCtrl.isdown = true;
        mouseCtrl.event = e;
        registerDownAfterListen();

        mouseCtrl.mouseDownTime = new Date();
        mouseCtrl.moveStartX = e.screenX;
        console.log('mousedown');
    });

    /**在點擊之後需要開啓的所有監聽 */
    function registerDownAfterListen() {
        dom.on('mousemove',mousemove);
        dom.on('mouseup',mouseup);
        dom.on('mouseleave',mouseleave);

    }

    /**註銷所有因爲Donw而註冊的監聽 */
    function cancelDownAfterListen() {
        dom.off('mousemove',mousemove);
        dom.off('mouseup',mouseup);
        dom.off('mouseleave',mouseleave);
      
    }

    function mousemove(e) {
        /**因爲只有在mousedown的時候纔會註冊mousemove事件 所以無須 檢測是否按下 */
        // if(mouseCtrl.isdown===false) return ;
        /**當前位置和上一次位置的差值 */
        var constantlyOffset = e.screenX - (mouseCtrl.moveEndX || mouseCtrl.moveStartX);

        mouseCtrl.moveEndX = e.screenX; 
        if(Math.abs(mouseCtrl.moveX)>20 && !mouseCtrl.ismoving){
            mouseCtrl.ismoving = true;
        }
        if(mouseCtrl.ismoving){
            instance.emit('movingEvent',{
                event:mouseCtrl.event,
                moveX:mouseCtrl.moveX,
                constantlyOffset:constantlyOffset
            });
        }
        console.log('mousemove');
    }
    /**@TODO:
     * 1.將點擊事件也集成到mouseup上 
     *      1.1 當mouseCtrl.ismoving爲false且
     *      1.2 當mouseDiffTime < 150 即爲一次有效點擊   
     *      1.3 如果不存在 clickId 開啓一個延時點擊函數 clickId  = excuteClick():async;
     *      1.4 如果存在 clickId 取消 clickId:async  執行 excuteDbclick();
     *  */
    function mouseup() {
        mouseCtrl.mouseUpTime = new Date();

        if(!mouseCtrl.ismoving && mouseCtrl.mouseDiffTime < mouseCtrl.mouseDiffTimeMax){
            mouseCtrl.clickId ?   dblclick():singleClick();
        }else{
            /**這裏如果觸發了單擊或者雙擊  因爲 單擊時有延時的 所以不應該直接釋放mouseCtrl */
            mouseCtrl.isreleased = true;
        }
        cancelDownAfterListen();
        console.log('mouseup');
    }

    function singleClick() {
        mouseCtrl.clickId = setTimeout(function () {
            instance.emit('click',{event:mouseCtrl.event});
            mouseCtrl.isreleased = true;
            console.log('singleClick');
        }, mouseCtrl.mouseDoubleClickTimeMax);
    }

    function dblclick() {
        if(mouseCtrl.clickId) clearTimeout(mouseCtrl.clickId);
        instance.emit('dblclick',{event:mouseCtrl.event});
        mouseCtrl.isreleased = true;
        console.log('dblclick');
    }

    function mouseleave() {
        cancelDownAfterListen();
        mouseCtrl.isreleased = true;
    }

     this.destroy = function () {
        this.off();
        try {
            cancelDownAfterListen();
        } catch(e){console.log(e);}
        dom.off('mousedown');
        dom = null;
        mouseCtrl = null;
    };
    /**生成鼠標屬性管理
     * @returns {{isdown:false,ismoving:false,isreleased:true,moveX:null,clickId:null,moveStartX,moveEndX,mouseDownTime,mouseUpTime,mouseDiffTime,mouseDiffTimeMax:150,mouseDoubleClickTimeMax:300}}   當鼠標釋放的時候會重置所有 isdown:false ismoving:false moveX:null clickId:null 
     * @summary isdown 基本無用 
     */
    function generateMouseCtrl() {
        /**是否按下,是否移動,是否釋放,鼠標移動的橫向距離起始點,鼠標橫向移動的重點 鼠標按下的初始時間  鼠標擡起的時間 */
        var isdown , ismoving , isreleased,moveStartX,moveEndX ,mouseDownTime , mouseUpTime  ; 
        var mouseCtrl = {
            /**觸發事件的id 單擊 */
            clickId:null,
            /**鼠標點擊觸發的window.event */
            event:null,
            /**鼠標橫向移動的距離 */
            moveX:null,
            /**鼠標點擊擡起的間隔時間 */
            mouseDiffTime:null,
            /**固定值 鼠標按下擡起是否時單擊的時間最大值 */
            mouseDiffTimeMax:150,
            /**固定值 兩次鼠標擡起的最大時間間隔, 在其之內 則爲雙擊 在其之位則爲兩次單擊 */
            mouseDoubleClickTimeMax:300
        };
        function init() {
            isdown = false;
            ismoving = false;
            mouseCtrl.clickId = null;
            mouseCtrl.moveX = null;
            moveStartX = null;
            moveEndX = null;
            mouseDownTime = null;
            mouseUpTime = null;
            mouseCtrl.mouseDiffTime = null;
            mouseCtrl.event = null;
        }
        Object.defineProperty(mouseCtrl,'isdown',{
            get:function () {
                return isdown;
            },
            /** @param {Boolean} bol */
            set:function (bol) {
                isdown = bol;
                isreleased = !bol;
            }
        });
        Object.defineProperty(mouseCtrl,'isreleased',{
            get:function () {
                return isreleased;
            },
            /** @param {Boolean} bol */
            set:function (bol) {
                // if(bol === isreleased) return ;
                isreleased = bol;
                if(bol) {
                    init();
                }
            }
        });
        Object.defineProperty(mouseCtrl,'ismoving',{
            get:function () {
                return ismoving;
            },
            /** @param {Boolean} bol */
            set:function (bol) {
                if(isdown) {
                    ismoving = bol;
                }else {
                    ismoving = false;
                }
            }
        });
        Object.defineProperty(mouseCtrl,'moveStartX',{
            get:function () {
                return moveStartX;
            },
            /** @param {number} startX */
            set:function (startX) {
                moveStartX = startX;
                moveEndX = 0;
            }
        });
        Object.defineProperty(mouseCtrl,'moveEndX',{
            get:function () {
                return moveEndX;
            },
            /** @param {number} endX */
            set:function (endX) {
                if(typeof moveStartX !=="number") throw new Error("開始位置moveStartX必須爲數字類型");
                moveEndX = endX;
                mouseCtrl.moveX = moveEndX - moveStartX;
            }
        });
        Object.defineProperty(mouseCtrl,'mouseDownTime',{
            get:function () {
                return mouseDownTime;
            },
            /** @param {number} mouseDownTime */
            set:function (downTime) {
                if((typeof downTime ==="number") || !( downTime instanceof Date)) throw new Error("downTime必須爲數字類型或者時間類型");
                mouseDownTime = downTime;
            }
        });
        Object.defineProperty(mouseCtrl,'mouseUpTime',{
            get:function () {
                return mouseUpTime;
            },
            /** @param {number} endX */
            set:function (upTime) {
                if((typeof upTime ==="number") || !( upTime instanceof Date)) throw new Error("upTime必須爲數字類型或者時間類型");
                mouseUpTime = upTime;
                mouseCtrl.mouseDiffTime = mouseUpTime - mouseDownTime;
            }
        });
        mouseCtrl.isreleased = true;
        return mouseCtrl;
    }
}

EventSystem.prototype = new RegisterEvents();

EventSystem 提供
1. EventSystem.on(type,callback)註冊函數 ‘click’| ‘dblclick’ | ‘movingEvent’ 可選值
2. EventSystem.off(type,callback)註銷函數
3. EventSystem.emit (type,value) 觸發函數
4. EventSystem.destroy() 銷燬函數

總結

啊啊啊啊,可以去掉jquery。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章