欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于 jQuery 实现键盘事件监听控件

程序员文章站 2022-05-16 11:17:50
最近项目里要做一个画板,需要对键盘事件进行监听,来进行诸如撤回、重做、移动、缩放等操作,因此顺手实现了一个键盘事件监听控件,期间略有收获,整理出来,希望对大家有所帮助,更希...

最近项目里要做一个画板,需要对键盘事件进行监听,来进行诸如撤回、重做、移动、缩放等操作,因此顺手实现了一个键盘事件监听控件,期间略有收获,整理出来,希望对大家有所帮助,更希望能获得高手的指点。

1. 自动获取焦点

似乎浏览器的键盘事件只能被那些可以获得焦点的元素设置监听,而通常需要监听事件的 <div>、<canvas> 元素都不能获得焦点,因此需要修改目标元素的某些属性使其可以获得焦点,另外一种可行的方法是将事件委托给诸如 <input> 标签。这里采用的是第一类方法,当然,可以修改的属性也不止一种,例如,对于 <div> 标签可以将其 “editable” 属性设为 true,而这里采用的是给其设一个 tabindex 值。代码如下:

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

另外,焦点事件的触发需要点击元素或者 tab 切换,而这并不符合人类的直觉,因此需要监听鼠标移入事件,使目标元素“自动”地获得焦点:

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

2. 监听键盘事件

由于项目面向的客户所使用的浏览器以chrome为主(实际上是36x浏览器),因此没有针对浏览器做任何适配,仅仅使用了 jquery的事件监听:

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

由于实现是控件化的,所以定义了一个私有方法 _keydownhandler 来响应键盘的动作。

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按钮的各种类型组合,每种组合下又分别按照按键码存储一个自定义事件类型字符串,事件发生之后会从这里返回这个字符串,当然,没有对应自定义事件的时候,就老老实实地返回空字符串。

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接口,只有一句代码,实现如下:

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 是一个私有方法,用来写入自定义键盘事件的方法:

_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;
    }
    return null;
  };

这样,一个键盘事件监听控件就大功告成了,下面是完整实现代码:

/**
 * @constructor 键盘事件监听器
 * */
function keyboardlistener(param){
  this._init(param);
}
!function(){
  /**
   * @private {string} param.ele 事件对象选择器
   * */
  keyboardlistener.prototype._init = function _init(param){
    this.$ele = $(param.ele);
    this._initevents();
    this._initeventtype();
    return null;
  };
  /**
   * @private _emptyeventhandler 空白事件响应
   * */
  keyboardlistener.prototype._emptyeventhandler = function _emptyeventhandler(){
    return null;
  };
  /**
   * @private _initeventtype 初始化所有初始自定义事件类型
   * */
  keyboardlistener.prototype._initeventtype = function _initeventtype(){
    var alltype = ['up', 'down', 'left', 'right', 'undo', 'redo', 'zoomin', 'zoomout', 'delete'];
    var intlen = alltype.length;
    this.alleventtype = alltype;
    this.callback = {};
    this.eventdiscibeset = {};
    for(var intcnt = 0; intcnt < intlen; intcnt++){
      this.callback['on' + alltype[intcnt]] = keyboardlistener.prototype._emptyeventhandler;
    }
    return null;
  };
  /**
   * @private _initevents 绑定 dom 事件
   * */
  keyboardlistener.prototype._initevents = function _initevents(){
    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 + '';
    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;
    }
    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;
  };
}();

总结

以上所述是小编给大家介绍的基于 jquery 实现键盘事件监听控件,希望对大家有所帮助