基于 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插件FusionCharts实现的MSBar3D图效果示例【附demo源码】
Mar 23 jQuery
Jquery-data的三种用法
Apr 18 jQuery
jQuery实现可编辑表格并生成json结果(实例代码)
Jul 19 jQuery
jQuery实现的form转json经典示例
Oct 10 jQuery
jQuery图片查看插件Magnify开发详解
Dec 25 jQuery
jQuery NProgress.js加载进度插件的简单使用方法
Jan 31 jQuery
jQuery实现模糊查询的方法分析
May 10 jQuery
jQuery实现高级检索功能
May 28 jQuery
jquery ui 实现 tab标签功能示例【测试可用】
Jul 25 jQuery
jquery 时间戳转日期过程详解
Oct 12 jQuery
jQuery 判断元素是否存在然后按需加载内容的实现代码
Jan 16 jQuery
jQuery实现鼠标放置名字上显示详细内容气泡提示框效果的方法分析
Apr 04 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
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
php数组函数序列之array_push() 数组尾部添加一个或多个元素(入栈),返回新长度。
2011/11/07 PHP
解析PHP可变函数的经典用法
2013/06/20 PHP
php输出1000以内质数(素数)示例
2014/02/16 PHP
php生成百度sitemap站点地图类函数实例
2014/10/17 PHP
Laravel中Trait的用法实例详解
2016/03/16 PHP
PHP入门教程之数组用法汇总(创建,删除,遍历,排序等)
2016/09/11 PHP
PHP从零开始打造自己的MVC框架之入口文件实现方法详解
2019/06/03 PHP
PHP如何使用JWT做Api接口身份认证的实现
2020/02/03 PHP
javascript教程之不完整的继承(js原型链)
2014/01/13 Javascript
JS实现闪动的title消息提醒效果
2014/06/20 Javascript
JavaScript简单修改窗口大小的方法
2015/08/03 Javascript
jquery+CSS实现的水平布局多级网页菜单效果
2015/08/24 Javascript
jQuery插件实现文字无缝向上滚动效果代码
2016/02/25 Javascript
详解Node.js 命令行程序开发教程
2017/06/07 Javascript
JavaScript数组_动力节点Java学院整理
2017/06/26 Javascript
AngularJs 终极购物车(实例讲解)
2017/11/08 Javascript
值得收藏的vuejs安装教程
2017/11/21 Javascript
Node实战之不同环境下配置文件使用教程
2018/01/02 Javascript
VUE在for循环里面根据内容值动态的加入class值的方法
2018/08/12 Javascript
VUE-cli3使用 svg-sprite-loader
2018/10/20 Javascript
PWA介绍及快速上手搭建一个PWA应用的方法
2019/01/27 Javascript
Layui选项卡制作历史浏览记录的方法
2019/09/28 Javascript
Python实现抓取HTML网页并以PDF文件形式保存的方法
2018/05/08 Python
python-opencv获取二值图像轮廓及中心点坐标的代码
2019/08/27 Python
Jupyter notebook快速入门教程(推荐)
2020/05/18 Python
HTML5页面无缝闪开的问题及解决方案
2020/06/11 HTML / CSS
经典C++面试题一
2016/11/06 面试题
配件采购员岗位职责
2013/12/03 职场文书
大学生入党推荐书范文
2014/05/17 职场文书
培训通知书模板
2015/04/17 职场文书
论文评审意见
2015/06/05 职场文书
贴吧吧主申请感言
2015/08/03 职场文书
大学学生会竞选稿
2015/11/19 职场文书
车辆挂靠协议书
2016/03/23 职场文书
Spring依赖注入多种类型数据的示例代码
2022/03/31 Java/Android