//------------------键盘处理类 Namespace.register("U.K"); //#region 全局变量区域 //变量来存储_MAP的翻转版从上面 U.K._REVERSE_MAP; //特殊键码的映射到其对应的键 U.K._MAP = { 8: 'backspace', 9: 'tab', 13: 'enter', 16: 'shift', 17: 'ctrl', 18: 'alt', 20: 'capslock', 27: 'esc', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down', 45: 'ins', 46: 'del', 91: 'meta', 93: 'meta', 224: 'meta' }; //特殊字符,使他们能够支持映射 U.K._KEYCODE_MAP = { 106: '*', 107: '+', 109: '-', 110: '.', 111: '/', 186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\', 221: ']', 222: '\'' }; //这是需要一个美国小键盘上的shift键的映射 U.K._SHIFT_MAP = { '~': '`', '!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0', '_': '-', '+': '=', ':': ';', '\"': '\'', '<': ',', '>': '.', '?': '/', '|': '\\' }; //这是你可以用它来绘制特殊字符串列表 U.K._SPECIAL_ALIASES = { 'option': 'alt', 'command': 'meta', 'return': 'enter', 'escape': 'esc', 'plus': '+', 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' }; U.K.start = function () { //遍历F键,F1至F19,并把它们添加到地图 for (var i = 1; i < 20; ++i) { U.K._MAP[111 + i] = 'f' + i; } //数字键盘的数字添加 for (i = 0; i <= 9; ++i) { U.K._MAP[i + 96] = i.toString(); } } //#endregion //#region 方法使用区域 //取事件,并返回的键值 U.K._characterFromEvent = function (e) { //为按键事件,我们应该回到该字符是 if (e.type == 'keypress') { var character = String.fromCharCode(e.which); // 如果没有然后按下Shift键它是安全的假设 // 我们想要的字符是小写。这意味着,如果 // 你不小心有大写锁定,然后键绑定 // 将继续工作 // // 可能是不希望的唯一副作用是,如果你 // 绑定类似'A',因为你要触发 // 事件被按下大写字母A时,大写锁定将不再 // 触发事件。尽管绑定了shift+a。 if (!e.shiftKey) { character = character.toLowerCase(); } return character; } // 非按键事件所需的专用地图 if (U.K._MAP[e.which]) { return U.K._MAP[e.which]; } if (U.K._KEYCODE_MAP[e.which]) { return U.K._KEYCODE_MAP[e.which]; } // 如果它不是在特殊的地图 // 与KEYDOWN和KeyUp事件的性质似乎总 // 进来作为一个大写字符无论您是按住Shift // 或不。我们应该确保它始终是小写的比较 return String.fromCharCode(e.which).toLowerCase(); } /** * 检查,如果两个数组相等, * * @param {Array} modifiers1 * @param {Array} modifiers2 * @returns {boolean} */ U.K._modifiersMatch = function (modifiers1, modifiers2) { return modifiers1.sort().join(',') === modifiers2.sort().join(','); } /** * 需要一个关键事件,并计算出的修饰符是什么 * * @param {Event} e * @returns {Array} */ U.K._eventModifiers = function (e) { var modifiers = []; if (e.shiftKey) { modifiers.push('shift'); } if (e.altKey) { modifiers.push('alt'); } if (e.ctrlKey) { modifiers.push('ctrl'); } if (e.metaKey) { modifiers.push('meta'); } return modifiers; } /** * 确定指定的键码是一个修改键或不 * * @param {string} key * @returns {boolean} */ U.K._isModifier = function (key) { return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; } /** * 颠倒了地图查找,这样我们可以寻找特定键 *看看有什么可以和不能使用的按键 * * @return {Object} */ U.K._getReverseMap = function () { if (!_REVERSE_MAP) { _REVERSE_MAP = {}; for (var key in U.K._MAP) { //从这里拉出数字小键盘按键的事业宜 //能够从字符检测的按键 if (key > 95 && key < 112) { continue; } if (U.K._MAP.hasOwnProperty(key)) { _REVERSE_MAP[U.K._MAP[key]] = key; } } } return _REVERSE_MAP; } /** * 挑选基础上,组合键最佳动作 * * @param {string} key - character for key * @param {Array} modifiers * @param {string=} action passed in */ U.K._pickBestAction = function (key, modifiers, action) { //如果没有动作,拿起我们应该尽量挑选一个 //我们认为将工作最适合这一关键 if (!action) { action = U.K._getReverseMap()[key] ? 'keydown' : 'keypress'; } //与预期的按键组合键不起作用, //切换到KEYDOWN if (action == 'keypress' && modifiers.length) { action = 'keydown'; } return action; } /** * *从一个字符串组合键转换到一个数组 * * @param {string} 的组合,如 "command+shift+l" * @return {Array} */ U.K._keysFromString = function (combination) { if (combination === '+') { return ['+']; } combination = combination.replace(/\+{2}/g, '+plus'); return combination.split('+'); } /** * Gets 获取信息的特定的组合键 * * @param {string} combination key combination ("command+s" or "a" or "*") * @param {string=} action * @returns {Object} */ U.K._getKeyInfo = function (combination, action) { var keys; var key; var i; var modifiers = []; // 从这种模式带钥匙,弄清实际 // 模式是所有 keys = U.K._keysFromString(combination); for (i = 0; i < keys.length; ++i) { key = keys[i]; // normalize key names if (U.K._SPECIAL_ALIASES[key]) { key = U.K._SPECIAL_ALIASES[key]; } //如果这不是一个按键事件那么我们应该 //聪明地使用shift键 //这只会为我们工作不过键盘 if (action && action != 'keypress' && U.K._SHIFT_MAP[key]) { key = U.K._SHIFT_MAP[key]; modifiers.push('shift'); } //如果该键是一个修改,然后将其添加到修改器列表 if (U.K._isModifier(key)) { modifiers.push(key); } } //根据密钥的组合是什么 //我们会尽力挑选最好的事件它 action = U.K._pickBestAction(key, modifiers, action); return { key: key, modifiers: modifiers, action: action }; } //判断监听来源 U.K._belongsTo = function (element, ancestor) { if (element === null || element === document) { return false; } if (element === ancestor) { return true; } return U.K._getReverseMap(element.parentNode, ancestor); } //#endregion //#region 对外接口区域 //对外公布函数 U.K.Mousetrap = function (targetElement) { var self = this; targetElement = targetElement || document; if (!(self instanceof U.K.Mousetrap)) { return new U.K.Mousetrap(targetElement); } /** * 元素附加关键事件 * * @type {Element} */ self.target = targetElement; /** * 通过Mousetrap.bind所有的回调设置列表() * * @type {Object} */ self._callbacks = {}; /** * 字符串组合的直接映射到用于触发回调() * * @type {Object} */ self._directMap = {}; /** * 跟踪什么级别的每个序列是因为多个 *序列可以开始时具有相同的序列 * * @type {Object} */ var _sequenceLevels = {}; /** * 变量来存储方法setTimeout * * @type {null|number} */ var _resetTimer; /** * 临时的状态,我们会忽略下一个KEYUP * * @type {boolean|string} */ var _ignoreNextKeyup = false; /** * 临时的状态,我们会忽略下一个按键 * * @type {boolean} */ var _ignoreNextKeypress = false; /** * 是我们目前的序列里面? * acticon(“KEYUP”或“的keydown”或“keypress”)或flase的类型 * * @type {boolean|string} */ var _nextExpectedAction = false; /** * 重置所有序列柜台,除了传递的那些 * * @param {Object} doNotReset * @returns void */ function _resetSequences(doNotReset) { doNotReset = doNotReset || {}; var activeSequences = false, key; for (key in _sequenceLevels) { if (doNotReset[key]) { activeSequences = true; continue; } _sequenceLevels[key] = 0; } if (!activeSequences) { _nextExpectedAction = false; } } /** * *查找匹配基础上的keyCode,所有的callback,modifiers,action * * @param {string} character * @param {Array} modifiers * @param {Event|Object} e * @param {string=} sequenceName - name of the sequence we are looking for * @param {string=} combination * @param {number=} level * @returns {Array} */ function _getMatches(character, modifiers, e, sequenceName, combination, level) { var i; var callback; var matches = []; var action = e.type; // 如果没有与此相关的键码的事件 if (!self._callbacks[character]) { return []; } // 如果修改键快到了自身,我们应该允许它 if (action == 'keyup' && U.K._isModifier(character)) { modifiers = [character]; } // 通过被按下该键所有的回调循环 //,看看其中是否匹配 for (i = 0; i < self._callbacks[character].length; ++i) { callback = self._callbacks[character][i]; //如果一个序列名未指定,但是这是一个序列的 //错误的水平,那么移动到下一个match if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { continue; } // //如果我们正在寻找的action不符合我们的action //那么我们就应该继续执行 if (action != callback.action) { continue; } //如果这是一个keypress event和meta和control key //没有按下这意味着我们只需要看 //字符,否则检查改性剂以及 // // Chrome浏览器不会触发一个keypress如果meta or control is down // Safari会触发一个 keypress 如果 meta or meta+shift is down // Firefox会触发一个keypress 如果 meta or control is down if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || U.K._modifiersMatch(modifiers, callback.modifiers)) { //当你绑定的组合或序列的第二次它 //应该覆盖的第一个。如果sequenceName或 //组合在这个指定的调用它做到了这一点 // //@todo使删除它自己的方法? var deleteCombo = !sequenceName && callback.combo == combination; var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; if (deleteCombo || deleteSequence) { self._callbacks[character].splice(i, 1); } matches.push(callback); } } return matches; } /** * 实际调用回调函数 * * * *如果您的回调函数返回false,这将使用jQuery的 * convention - 防止违约和对事件停止传播史 * * @param {Function} callback * @param {Event} e * @returns void */ function _fireCallback(callback, e, combo, sequence) { // //如果此事件不应该发生到此为止 if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { return; } if (callback(e, combo) === false) { U.M.StopDefault(e); U.M.StopBubble(e); } } /** * 处理一个字符键事件 * * @param {string} character * @param {Array} modifiers * @param {Event} e * @returns void */ self._handleKey = function (character, modifiers, e) { var callbacks = _getMatches(character, modifiers, e); var i; var doNotReset = {}; var maxLevel = 0; var processedSequenceCallback = false; // 计算maxLevel的序列,所以我们只能执行时间最长的回调序列 for (i = 0; i < callbacks.length; ++i) { if (callbacks[i].seq) { maxLevel = Math.max(maxLevel, callbacks[i].level); } } //通过匹配回调回路这一关键事件 for (i = 0; i < callbacks.length; ++i) { // 触发有序回调 // 这是因为例如,如果您有多个序列 // 结合诸如“G i”和“G t”,他们都需要触发 // 回调匹配摹原因,否则你永远只能 // 匹配到第一个 if (callbacks[i].seq) { //只触发回调的maxLevel防止 // // //例如 “a option b' 应该不会造成'option b' 触发 //即使'option b'为其它序列的一部分 // //这里不匹配任何序列都将被丢弃 //下面的_resetSequences通话 if (callbacks[i].level != maxLevel) { continue; } processedSequenceCallback = true; // //保留其中的序列是匹配为以后列表 doNotReset[callbacks[i].seq] = 1; _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); continue; } // 如果没有序列相匹配,但我们还在这里 // 这意味着这是一个普通的匹配,所以我们应该触发了 if (!processedSequenceCallback) { _fireCallback(callbacks[i].callback, e, callbacks[i].combo); } } // if the key you pressed matches the type of sequence without // being a modifier (ie "keyup" or "keypress") then we should // reset all sequences that were not matched by this event // // this is so, for example, if you have the sequence "h a t" and you // type "h e a r t" it does not match. in this case the "e" will // cause the sequence to reset // // modifier keys are ignored because you can have a sequence // that contains modifiers such as "enter ctrl+space" and in most // cases the modifier key will be pressed before the next key // // also if you have a sequence such as "ctrl+b a" then pressing the // "b" key will trigger a "keypress" and a "keydown" // // the "keydown" is expected when there is a modifier, but the // "keypress" ends up matching the _nextExpectedAction since it occurs // after and that causes the sequence to reset // // we ignore keypresses in a sequence that directly follow a keydown // for the same character //如果你按下键序列无类型相匹配 //是一个修改(即“KEYUP”或“按键”),那么,我们应该 //重新设置那些没有此事件相匹配的所有序列 // //是这样,例如,如果你有序列“哈t”和你 //型“听到T”不匹配。在这种情况下,将“e”的意愿 //导致序列复位 // //修饰键被忽略,因为你可以有一个序列 //包含修饰,如“输入Ctrl +空格”,并在最 //情况下,修改键将在下键之前按下 // //此外,如果你有一个序列,例如“CTRL + B A”,然后按 //“b”的键将触发一个“按键”和一个“的keydown” // //在“的keydown”时,有一个改性剂预期,但 //“按键”结束了,因为它发生_nextExpectedAction匹配 //之后和使该序列重新设置 // //我们忽略了一个顺序按键直接遵循的keydown //对于相同的字符 var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; if (e.type == _nextExpectedAction && !U.K._isModifier(character) && !ignoreThisKeypress) { _resetSequences(doNotReset); } _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; }; /** *处理一个keydown事件 * * @param {Event} e * @returns void */ function _handleKeyEvent(e) { // 为正常化的关键事件e.which if (typeof e.which !== 'number') { e.which = e.keyCode; } var character = U.K._characterFromEvent(e); //没有字符发现后停止 if (!character) { return; } //需要使用===用于字符检查,因为该字符可以是0 if (e.type == 'keyup' && _ignoreNextKeyup === character) { _ignoreNextKeyup = false; return; } self.handleKey(character, U.K._eventModifiers(e), e); } /** * 称为设置1秒超时指定的顺序 * *这是为了让序列中的每个按键后,你长为1秒 *按下一个键,你必须重新开始之前 * * @returns void */ function _resetSequenceTimer() { clearTimeout(_resetTimer); _resetTimer = setTimeout(_resetSequences, 1000); } /** * 绑定一个键序列事件 * * @param {string} combo - combo specified in bind call * @param {Array} keys * @param {Function} callback * @param {string=} action * @returns void */ function _bindSequence(combo, keys, callback, action) { // 通过增加一个序列水平记录这个组合开始 // 和level设定为0 _sequenceLevels[combo] = 0; /** * *回调,以增加该序列的序列水平和复位 * 处于活动状态的所有其他序列 * * @param {string} nextAction * @returns {Function} */ function _increaseSequence(nextAction) { return function () { _nextExpectedAction = nextAction; ++_sequenceLevels[combo]; _resetSequenceTimer(); }; } /** * 包装指定的回调另一个函数内,以便 *尽快此序列完成后重置所有序列计数器 * * @param {Event} e * @returns void */ function _callbackAndReset(e) { _fireCallback(callback, e, combo); //我们应该忽略下一个关键了,如果动作键不放 //或按键。这是如此,如果你完成一个序列 //松开按键的最后一个关键不会触发KEYUP if (action !== 'keyup') { _ignoreNextKeyup = U.K._characterFromEvent(e); } //怪异的竞争条件,如果一个序列与该键结束 //另一序列始于 setTimeout(_resetSequences, 10); } //通过按键一次,并结合适当的回调循环 //功能。任何关键领导到最后,就应 //增加的顺序。决赛之后,应该重置所有序列 // //如果在原来的绑定调用指定的操作那么就会 //在整个使用。否则,我们将通过动作的 //下一个关键应该匹配。这允许顺序 //混搭按键和KEYDOWN事件,这取决于 //的是更好地适合于提供的密钥 for (var i = 0; i < keys.length; ++i) { var isFinal = i + 1 === keys.length; var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || U.K._getKeyInfo(keys[i + 1]).action); _bindSingle(keys[i], wrappedCallback, action, combo, i); } } /** * 绑定一个键盘组合 * * @param {string} combination * @param {Function} callback * @param {string=} action * @param {string=} sequenceName - name of sequence if part of sequence * @param {number=} level - what part of the sequence the command is * @returns void */ function _bindSingle(combination, callback, action, sequenceName, level) { // //存储与Mousetrap.trigger使用直接映射参考 self._directMap[combination + ':' + action] = callback; // 使连续务必多个空格成为一个空格 combination = combination.replace(/\s+/g, ' '); var sequence = combination.split(' '); var info; //如果该模式是键的序列然后通过此方法运行 //重新处理每个模式一键在同一时间 if (sequence.length > 1) { _bindSequence(combination, sequence, callback, action); return; } info = U.K._getKeyInfo(combination, action); //确保初始化数组,如果这是第一次 //回调增加了对这一关键 self._callbacks[info.key] = self._callbacks[info.key] || []; //删除现有的比赛,如果有一个 _getMatches(info.key, info.modifiers, { type: info.action }, sequenceName, combination, level); //后面添加此调用数组 //如果它是一个序列把它在开始时 //如果不把它在最后 // //这是重要的,因为这些方式处理预期 //序列的人是第一位的 self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({ callback: callback, modifiers: info.modifiers, action: info.action, seq: sequenceName, level: level, combo: combination }); } /** * 结合多种组合到同一个回调 * * @param {Array} combinations * @param {Function} callback * @param {string|undefined} action * @returns void */ self._bindMultiple = function (combinations, callback, action) { for (var i = 0; i < combinations.length; ++i) { _bindSingle(combinations[i], callback, action); } }; // start! U.M.AddEvent(targetElement, 'keypress', _handleKeyEvent); U.M.AddEvent(targetElement, 'keydown', _handleKeyEvent); U.M.AddEvent(targetElement, 'keyup', _handleKeyEvent); } U.K.Mousetrap = function () { }; /** * / ** *绑定的事件捕鼠器 * *可以是单一的键,以+分离键的组合, *键的阵列,或由空格分隔键序列 * *请务必先列出的组合键,以确保 *正确的密钥最终得到的约束(在模式的最后一个键) * * @param {string|Array} keys * @param {Function} callback * @param {string=} action - 'keypress', 'keydown', or 'keyup' * @returns void */ U.K.Mousetrap.prototype.bind = function (keys, callback, action) { var self = this; keys = keys instanceof Array ? keys : [keys]; self._bindMultiple.call(self, keys, callback, action); return self; }; /** ** *解除绑定的事件捕鼠器 * *在解除绑定设置指定组合键的回调函数 *到一个空的功能,并在删除相应的键 * _directMap字典。 * * TODO:其实从_callbacks词典中删除这个代替 结合一个空函数* * *在keycombo +操作必须是完全一样 *它在绑定方法定义 * * @param {string|Array} keys * @param {string} action * @returns void */ U.K.Mousetrap.prototype.unbind = function (keys, action) { var self = this; return self.bind.call(self, keys, function () { }, action); }; /** * *触发器已被绑定的事件 * * @param {string} keys * @param {string=} action * @returns void */ U.K.Mousetrap.prototype.trigger = function (keys, action) { var self = this; if (self._directMap[keys + ':' + action]) { self._directMap[keys + ':' + action]({}, keys); } return self; }; /** * *重置库恢复到初始状态。这是非常有用的 *如果您想清除出当前的键盘快捷键和绑定 *新的 - 例如,如果您切换到另一页 * @returns void */ U.K.Mousetrap.prototype.reset = function () { var self = this; self._callbacks = {}; self._directMap = {}; return self; }; /** * *我们应该发射了回调之前停止该事件 * * @param {Event} e * @param {Element} element * @return {boolean} */ U.K.Mousetrap.prototype.stopCallback = function (e, element) { var self = this; // if the element has the class "mousetrap" then no need to stop if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { return false; } if (U.K._getReverseMap(element, self.target)) { return false; } // stop for input, select, and textarea return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; }; /** * 公开暴露_handleKey,因此它可以通过扩展覆盖 */ U.K.Mousetrap.prototype.handleKey = function () { var self = this; return self._handleKey.apply(self, arguments); }; /** * 允许自定义键映射 */ U.K.Mousetrap.addKeycodes = function (object) { for (var key in object) { if (object.hasOwnProperty(key)) { U.K._MAP[key] = object[key]; } } _REVERSE_MAP = null; }; /** * *初始化全球捕鼠器功能 * *需要使用此方法来允许在全球捕鼠器职能工作 *现在的捕鼠器是一个构造函数。 */ U.K.Mousetrap.init = function () { var documentMousetrap = Mousetrap(document); for (var method in documentMousetrap) { if (method.charAt(0) !== '_') { Mousetrap[method] = (function (method) { return function () { return documentMousetrap[method].apply(documentMousetrap, arguments); }; } (method)); } } }; //#endregion