基于 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加密密码到cookie的实现代码
Apr 18 jQuery
用户管理的设计_jquery的ajax实现二级联动效果
Jul 13 jQuery
详解jQuery中关于Ajax的几个常用的函数
Jul 17 jQuery
jQuery简单实现对数组去重及排序操作实例
Oct 31 jQuery
jquery写出PC端轮播图实例
Jan 26 jQuery
jQuery 改变P标签文本值方法
Feb 24 jQuery
jQuery中内容过滤器简单用法示例
Mar 31 jQuery
jQuery滚动条美化插件nicescroll简单用法示例
Apr 18 jQuery
jquery实现垂直无限轮播的方法分析
Jul 16 jQuery
Jquery 动态添加元素并添加点击事件实现过程解析
Oct 12 jQuery
jquery获取input输入框中的值
Nov 13 jQuery
Jquery滑动门/tab切换实现方法完整示例
Jun 05 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
Linux安装配置php环境的方法
2016/01/14 PHP
PHP+MySQL实现的简单投票系统实例
2016/02/24 PHP
Laravel Reponse响应客户端示例详解
2020/09/03 PHP
js计数器代码
2006/11/04 Javascript
js 静态动态成员 and 信息的封装和隐藏
2011/05/29 Javascript
使用javascript获取页面名称
2014/12/23 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
2016/08/04 Javascript
Boostrap基础教程之JavaScript插件篇
2016/09/08 Javascript
使用BootStrapValidator完成前端输入验证
2016/09/28 Javascript
探讨跨域请求资源的几种方式(总结)
2016/12/02 Javascript
Bootstrap源码解读标签、徽章、缩略图和警示框(8)
2016/12/26 Javascript
基于JavaScript实现下拉列表左右移动代码
2017/02/07 Javascript
vue-cli构建项目下使用微信分享功能
2018/05/28 Javascript
微信小程序:数据存储、传值、取值详解
2019/05/07 Javascript
VUE+node(express)实现前后端分离
2019/10/13 Javascript
React中Ref 的使用方法详解
2020/04/28 Javascript
js实现鼠标滑动到某个div禁止滚动
2020/09/17 Javascript
Python实现的数据结构与算法之基本搜索详解
2015/04/22 Python
Python OS模块常用函数说明
2015/05/23 Python
浅谈python中scipy.misc.logsumexp函数的运用场景
2016/06/23 Python
Python操作Excel插入删除行的方法
2018/12/10 Python
Django REST Framework序列化外键获取外键的值方法
2019/07/26 Python
安装2019Pycharm最新版本的教程详解
2019/10/22 Python
Python创建临时文件和文件夹
2020/08/05 Python
Crocs美国官方网站:卡骆驰洞洞鞋
2017/08/04 全球购物
美国综合购物商城:UnbeatableSale.com
2018/11/28 全球购物
高性能装备提升营地:Kammok
2019/02/27 全球购物
印尼购物网站:iLOTTE
2019/10/16 全球购物
党委书记岗位职责
2013/11/24 职场文书
快递员岗位职责
2014/09/12 职场文书
党委书记群众路线对照检查材料思想汇报
2014/10/04 职场文书
小学三年级班主任工作经验交流材料
2015/11/02 职场文书
PHP中strval()函数实例用法
2021/06/07 PHP
Netty分布式客户端处理接入事件handle源码解析
2022/03/25 Java/Android
全网非常详细的pytest配置文件
2022/07/15 Python
windows server2012 R2下安装PaddleOCR服务的的详细步骤
2022/09/23 Servers