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


Posted in jQuery onApril 04, 2019

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

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 实现键盘事件监听控件,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

jQuery 相关文章推荐
jQuery源码解读之extend()与工具方法、实例方法详解
Mar 30 jQuery
基于jQuery的表单填充实例
Aug 22 jQuery
jQuery实现锚点向下平滑滚动特效示例
Aug 29 jQuery
jquery 键盘事件的使用方法详解
Sep 13 jQuery
基于jQuery的$.getScript方法去加载javaScript文档解析
Nov 08 jQuery
jQuery实现checkbox全选功能完整实例
Jul 12 jQuery
jQuery的Ajax接收java返回数据方法
Aug 11 jQuery
jQuery中常用动画效果函数知识点整理
Aug 19 jQuery
JQuery扩展对象方法操作示例
Aug 21 jQuery
jQuery实现的网站banner图片无缝轮播效果完整实例
Jan 28 jQuery
jQuery实现form表单基于ajax无刷新提交方法实例代码
Nov 04 jQuery
jQuery--遍历操作实例小结【后代、同胞及过滤】
May 22 jQuery
jQuery分组选择器简单用法示例
Apr 04 #jQuery
jQuery实现带3D切割效果的轮播图功能示例【附源码下载】
Apr 04 #jQuery
jquery简单实现纵向的无缝滚动代码实例
Apr 01 #jQuery
jQuery事件blur()方法的使用实例讲解
Mar 30 #jQuery
jQuery实现动态添加和删除input框代码实例
Mar 29 #jQuery
jQuery ajax仿Google自动提示SearchSuggess功能示例
Mar 28 #jQuery
JQueryDOM之样式操作
Mar 27 #jQuery
You might like
PHP 中的批处理的实现
2007/06/14 PHP
PHP获取当前日期所在星期(月份)的开始日期与结束日期(实现代码)
2013/06/18 PHP
PHP中substr()与explode()函数用法分析
2014/11/24 PHP
php读取csv数据保存到数组的方法
2015/01/03 PHP
修改Laravel5.3中的路由文件与路径
2016/08/10 PHP
php与python实现的线程池多线程爬虫功能示例
2016/10/12 PHP
PHP 8新特性简介
2020/08/18 PHP
js有关元素内容操作小结
2011/12/20 Javascript
JQuery为textarea添加maxlength属性并且兼容IE
2013/04/25 Javascript
jQuery点击自身以外地方关闭弹出层的简单实例
2013/12/24 Javascript
JS在IE下缺少标识符的错误
2014/07/23 Javascript
node.js中的fs.rename方法使用说明
2014/12/16 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery操作JSON的CRUD用法实例
2015/02/25 Javascript
jQuery实现首页顶部可伸缩广告特效代码
2015/04/15 Javascript
JavaScript学习小结(7)之JS RegExp
2015/11/29 Javascript
jQuery ajaxForm()的应用
2016/10/14 Javascript
jQuery双向列表选择器select版
2016/11/01 Javascript
vue 移动端适配方案详解
2018/11/15 Javascript
微信小程序实现Session功能及无法获取session问题的解决方法
2019/05/07 Javascript
jQuery表单选择器用法详解
2019/08/22 jQuery
vue实现二级导航栏效果
2019/10/19 Javascript
vue动态循环出的多个select出现过的变为disabled(实例代码)
2019/11/10 Javascript
Vue在H5 项目中使用融云进行实时个人单聊通讯
2020/12/14 Vue.js
[01:03:51]2018DOTA2亚洲邀请赛 4.7 淘汰赛 VP vs LGD 第三场
2018/04/09 DOTA
Python实现的多项式拟合功能示例【基于matplotlib】
2018/05/15 Python
解决python-docx打包之后找不到default.docx的问题
2020/02/13 Python
Python API 操作Hadoop hdfs详解
2020/06/06 Python
15个应该掌握的Jupyter Notebook使用技巧(小结)
2020/09/23 Python
详解canvas绘图时遇到的跨域问题
2018/03/22 HTML / CSS
家长学校教学计划
2015/01/19 职场文书
大学开学典礼新闻稿
2015/07/17 职场文书
2016抗战胜利71周年红领巾广播稿
2015/12/18 职场文书
小学2016年“我们的节日·重阳节”活动总结
2016/04/01 职场文书
导游词范文之颐和园/重庆/云台山
2019/09/10 职场文书
Python利用FlashText算法实现替换字符串
2022/03/31 Python