基于 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+pjax简单示例汇总
Apr 21 jQuery
jQuery序列化后的表单值转换成Json
Jun 16 jQuery
jQuery+C#实现参数RSA加密传输功能【附jsencrypt.js下载】
Jun 26 jQuery
jQuery实现返回顶部按钮和scroll滚动功能[带动画效果]
Jul 05 jQuery
jQuery+Ajax请求本地数据加载商品列表页并跳转详情页的实现方法
Jul 12 jQuery
jQuery之动画ajax事件(实例讲解)
Jul 18 jQuery
jQuery实现鼠标移到某个对象时弹出显示层功能
Aug 23 jQuery
jQuery扩展方法实现Form表单与Json互相转换的实例代码
Sep 05 jQuery
jQuery添加新内容的四个常用方法分析【append,prepend,after,before】
Mar 19 jQuery
非常实用的jQuery代码段集锦【检测浏览器、滚动、复制、淡入淡出等】
Aug 08 jQuery
jQuery实现简单QQ聊天框
Aug 27 jQuery
jQuery实现影院选座订座效果
Apr 13 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和MySQL保存和输出图片
2006/10/09 PHP
如何限制访问者的ip(PHPBB的代码)
2006/10/09 PHP
php checkbox 取值详细说明
2010/08/19 PHP
通过PHP修改Linux或Unix口令的方法分享
2012/01/30 PHP
PHP基于文件存储实现缓存的方法
2015/07/20 PHP
Yii中CArrayDataProvider和CActiveDataProvider区别实例分析
2016/03/02 PHP
javascript 函数式编程
2007/08/16 Javascript
jquery 快速回到页首的方法
2013/12/05 Javascript
Jqgrid设置全选(选择)及获取选择行的值示例代码
2013/12/28 Javascript
js中call与apply的用法小结
2013/12/28 Javascript
jQuery简易图片放大特效示例代码
2014/06/09 Javascript
js中取得变量绝对值的方法
2015/01/03 Javascript
jQuery实现感应鼠标动画效果自动伸长的输入框实例
2015/02/24 Javascript
js文本框输入内容智能提示效果
2015/12/02 Javascript
Javascript 字符串模板的简单实现
2016/02/13 Javascript
Servlet实现文件上传,可多文件上传示例
2016/12/05 Javascript
JS实现侧边栏鼠标经过弹出框+缓冲效果
2017/03/29 Javascript
原生js实现仿window10系统日历效果的实例
2017/10/31 Javascript
vue 2.8.2版本配置刚进入时候的默认页面方法
2018/09/21 Javascript
layui监听下拉选框选中值变化的方法(包含监听普通下拉选框)
2019/09/24 Javascript
VUE+node(express)实现前后端分离
2019/10/13 Javascript
Jquery+javascript实现支付网页数字键盘
2020/12/21 jQuery
[01:10]DOTA2亚洲邀请赛 征战号角响彻全场
2015/01/06 DOTA
Python时间戳与时间字符串互相转换实例代码
2013/11/28 Python
python实现目录树生成示例
2014/03/28 Python
Python对象转JSON字符串的方法
2016/04/27 Python
Python实现字符串反转的常用方法分析【4种方法】
2017/09/30 Python
unittest+coverage单元测试代码覆盖操作实例详解
2018/04/04 Python
浅谈Python在pycharm中的调试(debug)
2018/11/29 Python
Python3中的bytes和str类型详解
2019/05/02 Python
python获取整个网页源码的方法
2020/08/03 Python
KIKO MILANO俄罗斯官网:意大利领先的化妆品和护肤品品牌
2021/01/09 全球购物
乡领导班子四风问题对照检查材料
2014/09/25 职场文书
安全生产感想
2015/08/07 职场文书
2016年“节能宣传周”活动总结
2016/04/05 职场文书
python实现双向链表原理
2022/05/25 Python