首页 > 其他分享 >自定义快捷键实操与踩坑

自定义快捷键实操与踩坑

时间:2024-01-10 15:00:13浏览次数:43  
标签:return 自定义 快捷键 export 实操 key const keyCode

0. 缘起

要做一个自定义快捷键的功能,web 端实现。这里分为两块逻辑,一部分是快捷键的应用,一部分是快捷键的定义。先从应用说起,快捷键实际上是对浏览器按键动作的监听,不过由于浏览器本身也有快捷键,就会有冲突的情况,自定义的要求应运而生。快捷键的定义,其实类似于设置的功能,也是存、取两个要点,不多解释。

1. 快捷键的监听

键盘事件中,event 事件类型为 KeyboardEvent
在本项目中,有两处特殊判断,1.26 字母触发以 keyCode(72)判断,2.兼容中文输入法下的快捷键,keyCode 为 229 时以 code(KeyH)判断,其他情况以 key(h)判断。

pF9PAZF.png

快捷键触发事件的值

2. 功能函数 2024-1-10

export const AlphabetList =
  "A、B、C、D、E、F、G、H、I、J、K、L、M、N、O、P、Q、R、S、T、U、V、W、X、Y、Z".split(
    "、"
  );

// 判断是否为26英文字符
export const is26Letter = (e) => {
  // A 65 Z 90
  const isEqualOrLargerThanA = e.keyCode >= 65;
  const isEqualOrSmallerThanZ = e.keyCode <= 90;
  return isEqualOrLargerThanA && isEqualOrSmallerThanZ;
};

// 根据keyCode获得字母 65 -> A
export const getKeyCode2Letter = (e) => {
  const letter = AlphabetList[e.keyCode - 65];
  return letter;
};

// 中文输入法特殊处理
export const getChineseInputLetter = (e) => {
  // 根据code KeyH 获得按下的字母
  const letter = e.code?.replace("Key", "");
  return letter;
};

// 修饰符键组
const DecoratorKeyList = ["alt", "ctrl", "meta", "shift"];

// 首字母大写
export const getFirstLetterWordUpper = (word) => {
  if (!(word && word.length)) {
    return "";
  }
  const capitalized = word.charAt(0).toUpperCase() + word.slice(1);
  return capitalized;
};

// 首字母小写
export const getFirstLetterWordLower = (word) => {
  if (!(word && word.length)) {
    return "";
  }
  const capitalized = word.charAt(0).toLowerCase() + word.slice(1);
  return capitalized;
};

// 获取当前按键事件组合 eg. ['Ctrl','1']
export const getInputKey = (e) => {
  // 如果keyCode处于26字母期间,这里需转换为对应的字符
  const isLetter = is26Letter(e);
  if (isLetter) {
    return getKeyCode2Letter(e);
  }
  // ATTENTION: 中文输入法下,26键为Process keyCode 229 无法判断具体哪个
  // 以code判断
  const isProcess = e.keyCode === 229;
  if (isProcess) {
    return getChineseInputLetter(e);
  }

  // 字母全部转为大写,其他字符用key
  // if (e.key && AlphabetList.includes(e.key.toUpperCase())) {
  //   return e.key.toUpperCase();
  // }
  return e.key;
};

// 获取修饰符组合
export const getDecoratorKey = (e) => {
  const totalDecoratorKeyStatusList = DecoratorKeyList.reduce((prev, cur) => {
    // 修饰符键组是否按下
    if (e[`${cur}Key`]) {
      // mac机型处理 如果按下command按键 效果同等于按下ctrlKey
      if (cur === "meta") {
        prev.push("Ctrl");
      } else {
        prev.push(getFirstLetterWordUpper(cur));
      }
    }
    return prev;
  }, []);
  return totalDecoratorKeyStatusList;
};

// 接受事件,返回当前输入的快捷键组合 形如['Ctrl','A']
export const getShortcut = (e) => {
  const array = getDecoratorKey(e);
  array.push(getInputKey(e));
  return array;
};

// 将文本字符串的按键记忆 以+号分割 转化为快捷键组合
// eg. 批量向下替换 Ctrl+Shift+1 => ['ctrl','shift','1']
export const getString2Shortcut = (text) => {
  const array = text.split("+");
  const letterUpperArray = array.map((key) => {
    if (DecoratorKeyList.includes(key)) {
      return getFirstLetterWordLower(key);
    }
    return key;
  });
  return letterUpperArray;
};

// 接受事件,返回当前输入的快捷键组合 形如Ctrl+A
export const getShortcut2Sring = (e) => {
  const array = getDecoratorKey(e);
  array.push(getInputKey(e));
  return array.join("+");
};

// 判断当前按键是否符合传入事件 只有触发快捷键动作时才会启用
export const isSuitableEvent = (e, action) => {
  // 否定守卫,如果在编辑快捷键期间,不允许触发已定义的快捷键
  const isEdit = localStorage.getItem("isShortcutEditor")?.length;
  if (isEdit) {
    return false;
  }

  // step 1 获取按键事件
  const inputActions = getShortcut(e);
  // step 2 拆解传入事件
  const needJudgeEventList = getString2Shortcut(action);
  // step 3 是否每个按键都能找到对应值
  const isEveryOneSuit = needJudgeEventList.every((input) =>
    inputActions.includes(input)
  );

  return isEveryOneSuit;
};

// 快捷键重复的判断
// [{label:'保存', shortcut:'Ctrl+S',key:'save'}]
export const isExistShortcut = (str, shortCutsArray) => {
  // step 1 拆分快捷键 Ctrl+A -> ['Ctrl','A']
  const inputActions = getString2Shortcut(str);
  // step 2 遍历当前快捷键组,看是否有分开后,每个按键都能找到对应
  const isExist = shortCutsArray.some((item) => {
    const { shortcut } = item;
    // 这部分逻辑 和上面判断按键是否符合类似
    const needJudgeEventList = getString2Shortcut(shortcut);
    const isEveryOneSuit = needJudgeEventList.every((input) =>
      inputActions.includes(input)
    );
    return isEveryOneSuit;
  });
  return isExist;
};

// 首字母大写的修饰符键组
const FirstLetterUpperDecoratorKeyList = DecoratorKeyList.map((key) =>
  getFirstLetterWordUpper(key)
);

// 快捷键符合规定的判断 必须为一个修饰符+26字母
export const isLegalShortcut = (str) => {
  // step 1 拆解快捷键
  const inputActions = getString2Shortcut(str);
  // step 2 判断是否合规 同时拥有一个修饰符、一个字母
  // 因为这里要判断长度 所以用filter
  // 一个修饰符
  const isHaveOneDecoratorKey =
    inputActions.filter((key) => FirstLetterUpperDecoratorKeyList.includes(key))
      ?.length === 1;
  // 一个字母
  const isHaveOneLetter =
    inputActions.filter((key) => AlphabetList.includes(key))?.length === 1;
  const isLegal = isHaveOneDecoratorKey && isHaveOneLetter;

  return isLegal;
};

3. 使用

使用上方的功能 判断当前按下是否符合规则

const rule = "Alt+V";
const isValid = isSuitableEvent(e, rule);
console.log("isValid: ", isValid);

// if (e.keyCode === 49 && e.ctrlKey && e.shiftKey)
if (isValid) {
  console.log("批量向下填充并替换");
  // 批量向下替换
  elem.blur();
  that.batchDownReplacement(elem, 1);
  //   注意此行代码,特殊快捷键一定要阻止浏览器动作!!!
  e.preventDefault();
}

image.png

4. 坑

中文输入法的兼容处理

keyCode为 229 时,判断为是中文输入法,以 code(KeyH)判断

浏览器级别按键冲突 2024-1-9

可参考下方的知乎回答,其中关于 e 动作解释的很好
https://zhuanlan.zhihu.com/p/300659062

CMD + W 类的事件和 CMD + S 类的事件有着本质差别,我们可以在原有的流程图上继续做一个推测,当浏览器对这部分优先级更高的快捷键做出不可逆的副作用响应时,listener 的 cb 即便 preventDefault 也将变得无能为力,因为更高优先级的副作用已经产生了

5. 参考

速查 key\which\code
https://www.zhangxinxu.com/wordpress/2021/01/js-keycode-deprecated/

由 code 查询对应字符
https://segmentfault.com/a/1190000005828048#comment-area

在线查询 key
https://www.dute.org/keycodes

两篇吃透按键事件:你应该了解的 js 键盘事件和使用注意事项
https://juejin.cn/post/7034682307667558437

标签:return,自定义,快捷键,export,实操,key,const,keyCode
From: https://www.cnblogs.com/lepanyou/p/17956488

相关文章

  • go 新建一个自定义包
    一、概述在go中新建一个自定义包供其他包使用。步骤:1.新建一个目录2.目录下新建一个xxx.go文件3.在xxx.go文件中使用packagexxx(包名)4.此时你的包已经新建好了5.在需要使用上面包的地方导入即可,如:import"xxxx"p......
  • springboot通过自定义注解@Log实现日志打印
    springboot通过自定义注解@Log实现日志打印效果图实操步骤注意,本代码在springboot环境下运行,jdk1.81.引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency>......
  • 自定义ADFS登录页
    修改adfs登录页公司名称:Set-AdfsGlobalWebContent-CompanyName"ExchangeOWA" 参考:ADFS自定义:https://learn.microsoft.com/zh-cn/windows-server/identity/ad-fs/operations/ad-fs-customization-in-windows-server#custom-themes-and-advanced-custom-themes 修改ADFS登录页......
  • Qt读取文件对比:每次获取自定义的长度和使用系统的API,耗时对比
    0.前言在编程过程中,经常遇到文件读写操作,太频繁了。每次也都写的不一样。突发奇想,想测试下几种不同的读取文件的效率。测试以下三种方式读取文件效率:自定义读取文件耗时使用QFile类API读取文件耗时使用QTextStream类API读取文件耗时在测试前,说一下使用到的知识点。1.Qt......
  • elixir mix 自定义任务
    elixir的mix比较灵活,同时也比较强大,很多时候我们可以自己定义一个任务,方便运行以及构建,不少三方框架会提供一些方便的cli,一般也会使用此方法比如ectoorm框架,以下是一个简单的自定义task学习参考开发目录位置这个实际上都f放那里可以,只是有一个简单的约定,建议放到lib/mix/......
  • textarea 添加回车和 ctrl+回车快捷键
    <!DOCTYPEhtml><html><head><title>WebSocketDemo</title><style>body{margin:0;padding:0;height:100vh;display:flex;flex-direction:......
  • 【C++】STL 容器 - STL 容器的值语意 ( 容器存储任意类型元素原理 | STL 容器元素可拷
    文章目录一、STL容器的值(Value)语意1、STL容器存储任意类型元素原理2、STL容器元素可拷贝原理3、STL容器元素类型需要满足的要求4、STL容器迭代器遍历二、代码示例-自定义可存放入STL容器的元素类1、代码示例2、执行结果一、STL容器的值(Value)语意1、STL......
  • 一款可实现个性化配置的低代码开发平台,打造完美自定义首页
    通常系统软件都会有首页展示,是用户首次打开系统软件时看到的页面,是用户对软件的第一印象。同时不同的软件类型和目标用户群体对首页的风格和内容会有所差异,JVS低代码平台就可以通过自定义页面引用外部站点来设置成首页,还可以直接在nacos中设置配置文件来切换首页,从而可以提高用户对......
  • 夜莺自定义告警模板
    !!大家好,我是乔克,一个爱折腾的运维工程,一个睡觉都被自己丑醒的云原生爱好者。作者:乔克公众号:运维开发故事博客:www.jokerbai.com预期目标Pastedimage20230906090309.png希望在告警通知里有以下数据:告知当前系统还有多少未处理的告警告知当前告警恢复时候的具体值告警通知里增加查......
  • ubuntu怎么设置快捷键?(依照选取截图为例)
    本次的实验环境是ubuntu18.04第一步:点击下三角第二步:打开设置第三步:点开后的界面如下,滑到底端选择设备第四步:点击键盘选项,然后按照图中标签进行修改即可!其他快捷键也皆可以如此修改!......