基於 jQuery 的鍵盤事件監聽控件

最近項目裏要作一個畫板,須要對鍵盤事件進行監聽,來進行諸如撤回、重作、移動、縮放等快捷鍵操做,所以順手實現了一個鍵盤事件監聽控件,略有收穫,整理出來,但願對你們有所幫助,更但願能得到高手的指點。chrome

1. 自動獲取焦點

彷佛瀏覽器的鍵盤事件只能被那些能夠得到焦點的元素設置監聽,而一般須要監聽事件的 <DIV>、<CANVAS> 元素都不能得到焦點,所以須要修改目標元素的某些屬性使其能夠得到焦點,另一種可行的方法是將事件委託給諸如 <INPUT> 標籤。這裏採用的是第一類方法,固然,能夠修改的屬性也不止一種,例如,對於 <DIV> 標籤能夠將其 「editable」 屬性設爲 true,而這裏採用的是給其設一個 tabindex 值。代碼以下:瀏覽器

$ele.attr('tabindex', 1);

另外,焦點事件的觸發須要點擊元素或者 TAB 切換,而這並不符合人類的直覺,所以須要監聽鼠標移入事件,使目標元素「自動」地得到焦點:函數

$ele.on('mouseenter', $ele.focus);

2. 監聽鍵盤事件

因爲項目面向的客戶所使用的瀏覽器以chrome爲主(其實是36x瀏覽器),所以沒有針對瀏覽器作任何適配,僅僅使用了 jQuery的事件監聽:this

$ele.on('keydown', this._keyDownHandler.bind(this));

因爲實現是控件化的,因此定義了一個私有方法 _keyDownHandler 來響應鍵盤的動做。prototype

3. 按鍵事件甄別

jQuery事件監聽器返回的事件對象信息較多,所以須要進行甄別,爲此定義了一個私有方法 _keyCodeProcess 來處理按鍵設計

function _keyCodeProcess(e){
        var code = e.keyCode + '';
        var altKey = e.altKey;
        var ctrlKey = e.ctrlKey;
        var shiftKey = e.shiftKey;

        var threeKey = altKey && ctrlKey && shiftKey;
        var ctrlAlt = altKey && ctrlKey;
        var altShift = altKey && shiftKey;
        var ctrlShift = shiftKey && ctrlKey;

        var keyTypeSet = this.keyTypeSet;
        var resStr = '';

        if(threeKey){
            resStr = keyTypeSet.threeKey[code];
        } else if(ctrlAlt) {
            resStr = keyTypeSet.ctrlAlt[code];
        } else if(ctrlShift) {
            resStr = keyTypeSet.ctrlShift[code];
        } else if(altShift) {
            resStr = keyTypeSet.altShift[code];
        } else if(altKey) {
            resStr = keyTypeSet.altKey[code];
        } else if(ctrlKey) {
            resStr = keyTypeSet.ctrlKey[code];
        } else if(shiftKey) {
            resStr = keyTypeSet.shiftKey[code];
        } else {
            resStr = keyTypeSet.singleKey[code];
        }

        return resStr
    };

這裏的 keyTypeSet 是一個相似於查找表的對象,裏面存儲了 ctrl、shift、alt按鈕的各類類型組合,每種組合下又分別按照按鍵碼存儲一個自定義事件類型字符串,事件發生以後會從這裏返回這個字符串,固然,沒有對應自定義事件的時候,就老老實實地返回空字符串。code

4. 事件分發

_keyCodeProcess 方法從事件中提取出了事件類型,咱們提早將監聽的回調函數存儲在一個查找表 callback 中,而且「巧妙」地使得其鍵名恰好爲自定義事件字符串前面加個「on」前綴,就能夠方便地調用了,前述 _keyDownHandler 正是爲此而設計的:對象

function _keyDownHandler(e){
        var strCommand = this._keyCodeProcess(e);

        var objEvent = {
            type: '',
            originEvent: e.originEvent
        };

        strCommand && this.callback['on' + strCommand](objEvent);

        return null;
    };

5. 事件訂閱與解除訂閱

前面說了,咱們是把回調函數存儲起來適時調用的,所以須要對外暴露一個「訂閱」接口,讓開發者能夠方便地把本身的回調函數存儲到對象實例中去,爲此,我定義了一個 .bind接口:接口

function bind(type, callback, description){
        var allType = this.allEventType;
        if(allType.indexOf(type) === -1){
            throwError('不支持的事件類型,請先擴展該類型,或採用其餘事件類型');
        }

        if(!(callback instanceof Function)){
            throwError('綁定的事件處理回調必須是函數類型');
        }

        this.callback['on' + type] = callback;

        this.eventDiscibeSet[type] = description || '沒有該事件的描述';

        return this;
    };

因爲是給人用的,因此順帶作了下類型檢查。
根據接口的「對稱性」,有訂閱最好也有解除訂閱,所以定義了 .unbind接口,只有一句代碼,實現以下:three

function unbind(type){
        this.callback['on' + type] = this._emptyEventHandler;

        return this;
    };

6.擴展自定義事件類型

鍵盤事件的組合豐富多彩,若是所有內置在控件中的話,會是很臃腫的,所以除了少數幾個常見的組合鍵以外,開發者能夠經過 .extendEventType 方法,來自定義組合鍵和返回的字符串:

function extendEventType(config){
        var len = 0;
        if(config instanceof Array){
            len = config.length;
            while(len--){
                this._setKeyComposition(config[len]);
            }
        } else {
            this._setKeyComposition(config);
        }
        return this;
    };

其中的 ._setKeyComposition 是一個私有方法,用來寫入自定義鍵盤事件:

function _setKeyComposition(config){
        var altKey = config.alt;
        var ctrlKey = config.ctrl;
        var shiftKey = config.shift;

        var threeKey = altKey && ctrlKey && shiftKey;
        var ctrlAlt = altKey && ctrlKey;
        var altShift = altKey && shiftKey;
        var ctrlShift = shiftKey && ctrlKey;
        var code = config.code + '';

        if(threeKey){
            this.keyTypeSet.threeKey[code] = config.type;
        } else if(ctrlAlt) {
            this.keyTypeSet.ctrlAlt[code] = config.type;
        } else if(ctrlShift) {
            this.keyTypeSet.ctrlShift[code] = config.type;
        } else if(altShift) {
            this.keyTypeSet.altShift[code] = config.type;
        } else if(altKey) {
            this.keyTypeSet.altKey[code] = config.type;
        } else if(ctrlKey) {
            this.keyTypeSet.ctrlKey[code] = config.type;
        } else if(shiftKey) {
            this.keyTypeSet.shiftKey[code] = config.type;
        } else {
            this.keyTypeSet.singleKey[code] = config.type;
        }
        
        this.allEventType.push(type);
        this.callback['on' + type] = this._emptyEventHandler;

        return null;
    };

這樣,一個鍵盤事件監聽控件就大功告成了,下面是完整實現代碼:

/**
 * @constructor 鍵盤事件監聽器
 * */
function KeyboardListener(param){
    this._init(param);
}

!function(){
    'use strict';

    /**
     * @private {String} param.ele 事件對象選擇器
     * */
    KeyboardListener.prototype._init = function _init(param){
        this.$ele = $(param.ele);

        this._initEvents();

        return null;
    };

    /**
     * @private _emptyEventHandler 空白事件響應
     * */
    KeyboardListener.prototype._emptyEventHandler = function _emptyEventHandler(){
        return null;
    };

    /**
     * @private _initEvents 綁定 DOM 事件
     * */
    KeyboardListener.prototype._initEvents = function _initEvents(){
        this.allEventType = [];
        this.callback = {};
        this.eventDiscibeSet = {};

        var $ele = this.$ele;

        $ele.attr('tabindex', 1);

        $ele.on('mouseenter', function(){
            $ele.focus();
        });

        $ele.on('keydown', this._keyDownHandler.bind(this));

        this.keyTypeSet = {
            altKey: {},
            ctrlAlt: {},
            ctrlKey: {},
            threeKey: {},
            altShift: {},
            shiftKey: {},
            ctrlShift: {},
            singleKey: {}
        };

        // 支持一些內建的鍵盤事件類型
        this.extendEventType([
            {
                type: 'redo',
                ctrl: true,
                shift: true,
                code: 90
            },
            {
                type: 'undo',
                ctrl: true,
                code: 90
            },
            {
                type: 'copy',
                ctrl: true,
                code: 67
            },
            {
                type: 'paste',
                ctrl: true,
                code: 86
            },
            {
                type: 'delete',
                code: 46
            },
            {
                type: 'right',
                code: 39
            },
            {
                type: 'down',
                code: 40
            },
            {
                type: 'left',
                code: 37
            },
            {
                type: 'up',
                code: 38
            }
        ]);

        return null;
    };

    /**
     * @private _keyDownHandler 自定義鍵盤事件分發
     * */
    KeyboardListener.prototype._keyDownHandler = function _keyDownHandler(e){
        var strCommand = this._keyCodeProcess(e);

        var objEvent = {
            type: '',
            originEvent: e.originEvent
        };

        strCommand && this.callback['on' + strCommand](objEvent);

        return null;
    };

    /**
     * @private _keyCodeProcess 處理按鍵碼
     * */
    KeyboardListener.prototype._keyCodeProcess = function _keyCodeProcess(e){
        var code = e.keyCode + '';
        var altKey = e.altKey;
        var ctrlKey = e.ctrlKey;
        var shiftKey = e.shiftKey;

        var threeKey = altKey && ctrlKey && shiftKey;
        var ctrlAlt = altKey && ctrlKey;
        var altShift = altKey && shiftKey;
        var ctrlShift = shiftKey && ctrlKey;

        var keyTypeSet = this.keyTypeSet;
        var resStr = '';

        if(threeKey){
            resStr = keyTypeSet.threeKey[code];
        } else if(ctrlAlt) {
            resStr = keyTypeSet.ctrlAlt[code];
        } else if(ctrlShift) {
            resStr = keyTypeSet.ctrlShift[code];
        } else if(altShift) {
            resStr = keyTypeSet.altShift[code];
        } else if(altKey) {
            resStr = keyTypeSet.altKey[code];
        } else if(ctrlKey) {
            resStr = keyTypeSet.ctrlKey[code];
        } else if(shiftKey) {
            resStr = keyTypeSet.shiftKey[code];
        } else {
            resStr = keyTypeSet.singleKey[code];
        }

        return resStr
    };

    /**
     * @private _setKeyComposition 自定義鍵盤事件
     * @param {Object} config 鍵盤事件配置方案
     * @param {String} config.type 自定義事件類型
     * @param {keyCode} config.code 按鍵的碼值
     * @param {Boolean} [config.ctrl] 是否與 Ctrl 造成組合鍵
     * @param {Boolean} [config.alt] 是否與 Alt 造成組合鍵
     * @param {Boolean} [config.shift] 是否與 Shift 造成組合鍵
     * */
    KeyboardListener.prototype._setKeyComposition = function _setKeyComposition(config){
        var altKey = config.alt;
        var ctrlKey = config.ctrl;
        var shiftKey = config.shift;

        var threeKey = altKey && ctrlKey && shiftKey;
        var ctrlAlt = altKey && ctrlKey;
        var altShift = altKey && shiftKey;
        var ctrlShift = shiftKey && ctrlKey;
        var code = config.code + '';
        var type = config.type;

        if(threeKey){
            this.keyTypeSet.threeKey[code] = type;
        } else if(ctrlAlt) {
            this.keyTypeSet.ctrlAlt[code] = type;
        } else if(ctrlShift) {
            this.keyTypeSet.ctrlShift[code] = type;
        } else if(altShift) {
            this.keyTypeSet.altShift[code] = type;
        } else if(altKey) {
            this.keyTypeSet.altKey[code] = type;
        } else if(ctrlKey) {
            this.keyTypeSet.ctrlKey[code] = type;
        } else if(shiftKey) {
            this.keyTypeSet.shiftKey[code] = type;
        } else {
            this.keyTypeSet.singleKey[code] = type;
        }
        this.allEventType.push(type);
        this.callback['on' + type] = this._emptyEventHandler;
        // this.eventDiscibeSet = {};

        return null;
    };

    /**
     * @method extendEventType 擴展鍵盤事件類型
     * @param {Object|Array<object>} config 鍵盤事件配置方案
     * @param {String} config.type 自定義事件類型
     * @param {keyCode} config.code 按鍵的碼值
     * @param {Boolean} [config.ctrl] 是否與 Ctrl 造成組合鍵
     * @param {Boolean} [config.alt] 是否與 Alt 造成組合鍵
     * @param {Boolean} [config.shift] 是否與 Shift 造成組合鍵
     * */
    KeyboardListener.prototype.extendEventType = function extendEventType(config){
        var len = 0;
        if(config instanceof Array){
            len = config.length;
            while(len--){
                this._setKeyComposition(config[len]);
            }
        } else {
            this._setKeyComposition(config);
        }
        return this;
    };

    /**
     * @method bind 綁定自定義的鍵盤事件
     * @param {String} type 事件類型 如:['up', 'down', 'left', 'right', 'undo', 'redo', 'delete', zoomIn, 'zoomOut']
     * @param {Function} callback 回調函數,參數爲一個自定義的仿事件對象
     * @param {String} description 對綁定事件的用途進行說明
     * */
    KeyboardListener.prototype.bind = function bind(type, callback, description){
        var allType = this.allEventType;
        if(allType.indexOf(type) === -1){
            throwError('不支持該事件類型,請先擴展該類型,或採用其餘事件類型');
        }

        if(!(callback instanceof Function)){
            throwError('綁定的事件處理回調必須是函數類型');
        }

        this.callback['on' + type] = callback;

        this.eventDiscibeSet[type] = description || '沒有該事件的描述';

        return this;
    };

    /**
     * @method unbind 解除事件綁定
     * @param {String} type 事件類型
     * */
    KeyboardListener.prototype.unbind = function unbind(type){
        this.callback['on' + type] = this._emptyEventHandler;

        return this;
    };
    
    return null;
}();
相關文章
相關標籤/搜索