Skip to content

键盘事件

键盘事件(Keyboard Events)用于响应用户的键盘操作,包括按键按下、释放等。键盘事件是实现快捷键、表单验证、游戏控制和无障碍访问的重要基础。现代 Web 开发中推荐使用 keydown/keyup 事件配合 event.keyevent.code 来识别按键。

前置知识

阅读本节前,建议先了解:鼠标事件

基础概念

键盘事件类型

事件触发时机说明
keydown按键按下时重复触发(长按时持续触发)
keyup按键释放时每次释放触发一次
keypress字符输入时已废弃,使用 keydown 替代
html
<!-- HTML 事件属性 -->
<input type="text"
       onkeydown="console.log('按下:', event.key)"
       onkeyup="console.log('释放:', event.key)">

<!-- JavaScript 方式 -->
<script>
  document.addEventListener('keydown', (e) => {
    console.log('按键:', e.key, '代码:', e.code);
  });
</script>

event.key vs event.code

属性含义示例特点
event.key按键的值"a", "Enter", "+"受键盘布局影响,有可打印字符
event.code物理按键位置"KeyA", "Enter", "Equal"不受键盘布局影响,基于物理位置
javascript
document.addEventListener('keydown', (e) => {
  // event.key: 受输入法影响
  // 按下 QWERTY 键盘的 'A' 键:key = "a"
  // 在 Dvorak 键盘上按同一个键:key = "'"(不同)

  // event.code: 始终标识物理按键
  // 无论什么键盘布局,按左下角第二个键:code = "KeyA"
  console.log(`key: ${e.key}, code: ${e.code}`);
});

语法

HTML 事件属性

html
<!-- 基本键盘事件 -->
<input onkeydown="handleKeyDown(event)" onkeyup="handleKeyUp(event)">

<!-- 快捷键示例 -->
<div onkeydown="if(event.ctrlKey && event.key==='s'){event.preventDefault();save();}">
  按 Ctrl+S 保存
</div>

JavaScript 绑定

javascript
const input = document.getElementById('my-input');

// keydown:按键按下时触发
input.addEventListener('keydown', (e) => {
  console.log('按下:', e.key, e.code);
});

// keyup:按键释放时触发
input.addEventListener('keyup', (e) => {
  console.log('释放:', e.key, e.code);
});

详细说明

KeyboardEvent 对象属性

属性类型说明
keystring按键值(如 "a", "Enter", "F1"
codestring物理按键代码(如 "KeyA", "Digit1"
keyCodenumber已废弃,旧的按键码
whichnumber已废弃,等同于 keyCode
locationnumber按键位置(0=标准 1=左 2=右 3=数字键盘)
repeatboolean是否为长按重复触发
isComposingboolean是否正在使用输入法组合
ctrlKey / altKey / shiftKey / metaKeyboolean修饰键状态

常用按键对照表

按键event.keyevent.code
A 键"a""KeyA"
1 键"1""Digit1"
Enter"Enter""Enter"
Space" ""Space"
Tab"Tab""Tab"
Escape"Escape""Escape"
Backspace"Backspace""Backspace"
Delete"Delete""Delete"
Arrow keys"ArrowUp""ArrowUp"
F1-F12"F1" - "F12""F1" - "F12"
Shift"Shift""ShiftLeft" / "ShiftRight"
Ctrl"Control""ControlLeft" / "ControlRight"
Alt"Alt""AltLeft" / "AltRight"
Meta (Cmd)"Meta""MetaLeft" / "MetaRight"

修饰键组合

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>键盘快捷键示例</title>
  <style>
    .shortcut-list {
      max-width: 400px;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
      overflow: hidden;
    }

    .shortcut-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
      border-bottom: 1px solid #e2e8f0;
    }

    kbd {
      background: #f1f5f9;
      border: 1px solid #d1d5db;
      border-radius: 4px;
      padding: 2px 8px;
      font-family: monospace;
      font-size: 13px;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    }
  </style>
</head>
<body>
  <div class="shortcut-list">
    <div class="shortcut-item">
      <span>保存</span>
      <span><kbd>Ctrl</kbd> + <kbd>S</kbd></span>
    </div>
    <div class="shortcut-item">
      <span>撤销</span>
      <span><kbd>Ctrl</kbd> + <kbd>Z</kbd></span>
    </div>
    <div class="shortcut-item">
      <span>全选</span>
      <span><kbd>Ctrl</kbd> + <kbd>A</kbd></span>
    </div>
    <div class="shortcut-item">
      <span>搜索</span>
      <span><kbd>Ctrl</kbd> + <kbd>F</kbd></span>
    </div>
  </div>

  <script>
    // 快捷键处理
    document.addEventListener('keydown', (e) => {
      // Ctrl+S:保存
      if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        console.log('保存');
        showNotification('已保存');
      }

      // Ctrl+Z:撤销
      if (e.ctrlKey && e.key === 'z') {
        e.preventDefault();
        console.log('撤销');
        showNotification('已撤销');
      }

      // Ctrl+A:全选
      if (e.ctrlKey && e.key === 'a') {
        e.preventDefault();
        console.log('全选');
      }

      // Ctrl+F:搜索
      if (e.ctrlKey && e.key === 'f') {
        e.preventDefault();
        console.log('搜索');
      }

      // Escape:取消/关闭
      if (e.key === 'Escape') {
        console.log('取消');
      }
    });

    function showNotification(msg) {
      const el = document.createElement('div');
      el.textContent = msg;
      el.style.cssText = `
        position: fixed; bottom: 20px; right: 20px;
        background: #22c55e; color: white;
        padding: 8px 16px; border-radius: 8px;
        z-index: 9999;
      `;
      document.body.appendChild(el);
      setTimeout(() => el.remove(), 2000);
    }
  </script>
</body>
</html>

防止输入非法字符

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>输入过滤示例</title>
</head>
<body>
  <!-- 只允许数字输入 -->
  <label>
    手机号:<input type="text" id="phone" maxlength="11"
           placeholder="请输入手机号">
  </label>

  <!-- 只允许字母和数字 -->
  <label>
    用户名:<input type="text" id="username"
           placeholder="只允许字母、数字和下划线">
  </label>

  <script>
    // 只允许数字
    document.getElementById('phone').addEventListener('keydown', (e) => {
      // 允许的功能键:退格、删除、方向键、Tab 等
      const allowed = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight',
                       'Home', 'End', 'Tab'];

      if (allowed.includes(e.key)) return;

      // 允许 Ctrl 组合键(如 Ctrl+A)
      if (e.ctrlKey || e.metaKey) return;

      // 只允许数字
      if (!/^\d$/.test(e.key)) {
        e.preventDefault();
      }
    });

    // 只允许字母、数字和下划线
    document.getElementById('username').addEventListener('keydown', (e) => {
      const allowed = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight',
                       'Home', 'End', 'Tab'];

      if (allowed.includes(e.key)) return;
      if (e.ctrlKey || e.metaKey) return;

      if (!/^[a-zA-Z0-9_]$/.test(e.key)) {
        e.preventDefault();
      }
    });
  </script>
</body>
</html>

实战示例

完整的键盘导航

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>键盘导航示例</title>
  <style>
    .nav-list {
      list-style: none;
      padding: 0;
      max-width: 300px;
    }

    .nav-item {
      padding: 10px 16px;
      border: 1px solid #e2e8f0;
      border-bottom: none;
      cursor: pointer;
      transition: background 0.15s;
    }

    .nav-item:last-child {
      border-bottom: 1px solid #e2e8f0;
    }

    .nav-item:hover,
    .nav-item.focused {
      background: #eff6ff;
    }

    .nav-item.active {
      background: #3b82f6;
      color: white;
      border-color: #3b82f6;
    }

    .nav-item.active + .nav-item {
      border-top-color: #3b82f6;
    }

    .hint {
      margin-top: 16px;
      font-size: 13px;
      color: #64748b;
    }
  </style>
</head>
<body>
  <ul class="nav-list" id="nav-list" role="listbox" aria-label="导航菜单">
    <li class="nav-item" role="option" data-index="0">首页</li>
    <li class="nav-item" role="option" data-index="1">产品</li>
    <li class="nav-item" role="option" data-index="2">关于我们</li>
    <li class="nav-item" role="option" data-index="3">联系方式</li>
    <li class="nav-item" role="option" data-index="4">帮助中心</li>
  </ul>

  <p class="hint">使用上下方向键导航,Enter 确认选择</p>

  <script>
    const navList = document.getElementById('nav-list');
    const items = navList.querySelectorAll('.nav-item');
    let focusedIndex = -1;

    navList.addEventListener('keydown', (e) => {
      switch (e.key) {
        case 'ArrowDown':
          e.preventDefault();
          focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
          updateFocus();
          break;

        case 'ArrowUp':
          e.preventDefault();
          focusedIndex = Math.max(focusedIndex - 1, 0);
          updateFocus();
          break;

        case 'Enter':
        case ' ':
          e.preventDefault();
          if (focusedIndex >= 0) {
            selectItem(focusedIndex);
          }
          break;

        case 'Home':
          e.preventDefault();
          focusedIndex = 0;
          updateFocus();
          break;

        case 'End':
          e.preventDefault();
          focusedIndex = items.length - 1;
          updateFocus();
          break;
      }
    });

    function updateFocus() {
      items.forEach((item, i) => {
        item.classList.toggle('focused', i === focusedIndex);
        item.setAttribute('aria-selected', i === focusedIndex);
      });
      if (focusedIndex >= 0) {
        items[focusedIndex].scrollIntoView({ block: 'nearest' });
      }
    }

    function selectItem(index) {
      items.forEach(item => item.classList.remove('active'));
      items[index].classList.add('active');
      console.log('选中:', items[index].textContent);
    }
  </script>
</body>
</html>

注意事项

keydown 的重复触发

长按按键时,keydown 会持续触发(event.repeattrue)。如果只需要首次按下时响应:

javascript
document.addEventListener('keydown', (e) => {
  if (e.repeat) return; // 忽略长按重复
  console.log('首次按下:', e.key);
});

输入法组合问题

使用中文、日文等输入法时,keydown 会在组合输入过程中触发。应检查 e.isComposing

javascript
input.addEventListener('keydown', (e) => {
  // 输入法组合过程中,不处理按键
  if (e.isComposing) return;

  if (e.key === 'Enter') {
    console.log('提交:', input.value);
  }
});

已废弃的 keypress 和 keyCode

javascript
// 已废弃,不要使用
// element.addEventListener('keypress', handler);
// if (e.keyCode === 13) { ... }  // 不推荐

// 推荐:使用 keydown + event.key
element.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    // 处理回车
  }
});

最佳实践

1. 使用 event.key 而非 event.keyCode

javascript
// 推荐
if (e.key === 'Enter') { /* ... */ }
if (e.key === 'Escape') { /* ... */ }

// 不推荐(已废弃)
if (e.keyCode === 13) { /* ... */ }
if (e.which === 27) { /* ... */ }

2. 实现键盘快捷键时防止浏览器默认行为

javascript
// 阻止浏览器默认的 Ctrl+S(保存页面)、Ctrl+F(搜索)等
document.addEventListener('keydown', (e) => {
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault(); // 阻止浏览器弹出保存对话框
    saveDocument();     // 执行自定义保存
  }
});

下一节

继续学习:表单事件

参考链接