pin_drop当前位置:知识文库 ❯ 图文
JS集成:HTML onkeydown事 - 详细教程与实战指南
一、教程简介
键盘事件是Web应用中处理用户键盘输入的核心机制。onkeydown事件在用户按下键盘按键时触发,是实现快捷键、按键过滤、游戏控制、表单验证等功能的基础。与keyup和已废弃的keypress不同,keydown在按键按下的瞬间就触发,能够捕获所有按键包括功能键和修饰键。本教程将全面讲解onkeydown事件的触发时机、键盘事件对象的丰富属性、快捷键的实现方式、按键过滤技巧,以及键盘事件的最佳实践。
二、核心概念
三种键盘事件对比
keydown事件的触发顺序
当用户按下一个字符键时,事件触发顺序为:
代码示例
1. keydown(首次按下)
2. (如果按键持续按住,重复触发keydown)
3. keydown(重复)
4. ...
5. keyup(释放按键)
对于字符键,keydown之后还会触发keypress(已废弃)和input事件。
长按重复触发
当用户长按一个键时,keydown事件会重复触发。可以通过event.repeat属性判断是否为重复触发:
代码示例
element.addEventListener('keydown', function(event) {
if (event.repeat) {
console.log('长按重复触发');
} else {
console.log('首次按下');
}
});键盘事件对象属性
key vs code 的区别
推荐:使用
key处理字符输入,使用code处理物理按键(如快捷键)。
三、语法与用法
基本用法
代码示例
// 监听特定元素的键盘事件
input.addEventListener('keydown', function(event) {
console.log('按下按键:', event.key);
});
// 监听全局键盘事件
document.addEventListener('keydown', function(event) {
console.log('全局按键:', event.key);
});阻止默认行为
代码示例
element.addEventListener('keydown', function(event) {
// 阻止特定按键的默认行为
if (event.key === 'Enter') {
event.preventDefault(); // 阻止Enter提交表单
}
});检测修饰键组合
代码示例
document.addEventListener('keydown', function(event) {
// Ctrl + S
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
// Ctrl + Shift + N
if (event.ctrlKey && event.shiftKey && event.key === 'N') {
event.preventDefault();
openNewWindow();
}
});四、代码示例
示例1:键盘事件对象属性详解
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>键盘事件对象属性详解</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
}
.key-display {
text-align: center;
padding: 30px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
margin: 20px 0;
}
.key-visual {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 80px;
background: #E3F2FD;
border: 3px solid #1565C0;
border-radius: 12px;
font-size: 32px;
font-weight: bold;
color: #1565C0;
box-shadow: 0 4px 0 #1565C0, 0 6px 10px rgba(0,0,0,0.1);
transition: all 0.1s;
margin: 10px;
}
.key-visual.pressed {
transform: translateY(3px);
box-shadow: 0 1px 0 #1565C0, 0 2px 4px rgba(0,0,0,0.1);
}
.props-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin: 20px 0;
}
.prop-item {
background: #f8f9fa;
padding: 10px 14px;
border-radius: 6px;
border-left: 3px solid #2196F3;
}
.prop-item .label {
font-size: 12px;
color: #999;
display: block;
}
.prop-item .value {
font-size: 16px;
font-weight: bold;
color: #333;
font-family: 'Consolas', monospace;
}
.modifier-keys {
display: flex;
gap: 10px;
justify-content: center;
margin: 16px 0;
}
.mod-key {
padding: 6px 14px;
border-radius: 6px;
font-size: 13px;
background: #e0e0e0;
color: #666;
transition: all 0.2s;
}
.mod-key.active {
background: #4CAF50;
color: white;
}
.event-log {
background: #263238;
color: #ECEFF1;
padding: 12px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<h1>键盘事件对象属性详解</h1>
<p>在页面任意位置按下键盘按键,查看事件属性</p>
<div class="key-display" id="keyDisplay">
<div class="key-visual" id="keyVisual">?</div>
<div class="modifier-keys">
<span class="mod-key" id="modCtrl">Ctrl</span>
<span class="mod-key" id="modShift">Shift</span>
<span class="mod-key" id="modAlt">Alt</span>
<span class="mod-key" id="modMeta">Meta</span>
</div>
</div>
<div class="props-grid" id="propsGrid">
<div class="prop-item">
<span class="label">event.key</span>
<span class="value" id="propKey">-</span>
</div>
<div class="prop-item">
<span class="label">event.code</span>
<span class="value" id="propCode">-</span>
</div>
<div class="prop-item">
<span class="label">event.keyCode (已废弃)</span>
<span class="value" id="propKeyCode">-</span>
</div>
<div class="prop-item">
<span class="label">event.which (已废弃)</span>
<span class="value" id="propWhich">-</span>
</div>
<div class="prop-item">
<span class="label">event.location</span>
<span class="value" id="propLocation">-</span>
</div>
<div class="prop-item">
<span class="label">event.repeat</span>
<span class="value" id="propRepeat">-</span>
</div>
</div>
<h3>事件日志</h3>
<div class="event-log" id="eventLog"></div>
<script>
const keyVisual = document.getElementById('keyVisual');
const eventLog = document.getElementById('eventLog');
const locationNames = {
0: '标准 (0)',
1: '左侧 (1)',
2: '右侧 (2)',
3: '数字键盘 (3)'
};
document.addEventListener('keydown', function(event) {
// 更新按键视觉
keyVisual.textContent = event.key.length === 1 ? event.key : event.key.substring(0, 6);
keyVisual.classList.add('pressed');
// 更新属性显示
document.getElementById('propKey').textContent = `"${event.key}"`;
document.getElementById('propCode').textContent = event.code;
document.getElementById('propKeyCode').textContent = event.keyCode;
document.getElementById('propWhich').textContent = event.which;
document.getElementById('propLocation').textContent = locationNames[event.location] || event.location;
document.getElementById('propRepeat').textContent = event.repeat ? '是 (长按)' : '否';
// 更新修饰键状态
document.getElementById('modCtrl').className = 'mod-key' + (event.ctrlKey ? ' active' : '');
document.getElementById('modShift').className = 'mod-key' + (event.shiftKey ? ' active' : '');
document.getElementById('modAlt').className = 'mod-key' + (event.altKey ? ' active' : '');
document.getElementById('modMeta').className = 'mod-key' + (event.metaKey ? ' active' : '');
// 日志
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const mods = [];
if (event.ctrlKey) mods.push('Ctrl');
if (event.shiftKey) mods.push('Shift');
if (event.altKey) mods.push('Alt');
if (event.metaKey) mods.push('Meta');
const modStr = mods.length > 0 ? `[${mods.join('+')}] ` : '';
eventLog.innerHTML += `<div>[${time}] keydown ${modStr}key="${event.key}" code="${event.code}"${event.repeat ? ' (repeat)' : ''}</div>`;
eventLog.scrollTop = eventLog.scrollHeight;
});
document.addEventListener('keyup', function() {
keyVisual.classList.remove('pressed');
});
</script>
</body>
</html>示例2:快捷键实现
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>快捷键实现</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
}
.shortcut-panel {
background: white;
border-radius: 10px;
padding: 20px;
margin: 16px 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.shortcut-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 10px;
margin: 16px 0;
}
.shortcut-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: #f8f9fa;
border-radius: 6px;
}
.shortcut-item .action { font-size: 14px; color: #333; }
.shortcut-item .keys { display: flex; gap: 4px; }
kbd {
display: inline-block;
padding: 3px 8px;
font-size: 12px;
font-family: 'Consolas', monospace;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 1px 0 #ccc;
}
.editor-area {
width: 100%;
height: 200px;
padding: 14px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
font-family: 'Consolas', monospace;
box-sizing: border-box;
outline: none;
resize: vertical;
}
.editor-area:focus { border-color: #2196F3; }
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #333;
color: white;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
transform: translateX(200%);
transition: transform 0.3s;
z-index: 100;
}
.toast.visible { transform: translateX(0); }
</style>
</head>
<body>
<h1>快捷键实现</h1>
<div class="shortcut-panel">
<h3>编辑器快捷键</h3>
<p>在下方编辑器中使用以下快捷键</p>
<div class="shortcut-list">
<div class="shortcut-item">
<span class="action">保存</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>S</kbd></span>
</div>
<div class="shortcut-item">
<span class="action">加粗</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>B</kbd></span>
</div>
<div class="shortcut-item">
<span class="action">搜索</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>F</kbd></span>
</div>
<div class="shortcut-item">
<span class="action">撤销</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>Z</kbd></span>
</div>
<div class="shortcut-item">
<span class="action">重做</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>Y</kbd></span>
</div>
<div class="shortcut-item">
<span class="action">全选</span>
<span class="keys"><kbd>Ctrl</kbd>+<kbd>A</kbd></span>
</div>
</div>
<textarea class="editor-area" id="editorArea" placeholder="在此输入文本,使用快捷键操作...">这是一个支持快捷键的编辑器。
尝试使用 Ctrl+S 保存、Ctrl+B 加粗、Ctrl+F 搜索等快捷键。
每个快捷键操作都会在右上角显示提示。</textarea>
</div>
<div class="toast" id="toast"></div>
<script>
const editor = document.getElementById('editorArea');
const toast = document.getElementById('toast');
let toastTimer = null;
function showToast(message) {
toast.textContent = message;
toast.classList.add('visible');
clearTimeout(toastTimer);
toastTimer = setTimeout(function() {
toast.classList.remove('visible');
}, 2000);
}
// 快捷键映射表
const shortcuts = {
'ctrl+s': function(e) {
e.preventDefault();
showToast('文档已保存 (Ctrl+S)');
localStorage.setItem('editorContent', editor.value);
},
'ctrl+b': function(e) {
e.preventDefault();
showToast('加粗文本 (Ctrl+B)');
const start = editor.selectionStart;
const end = editor.selectionEnd;
const selected = editor.value.substring(start, end);
if (selected) {
editor.value = editor.value.substring(0, start) + '**' + selected + '**' + editor.value.substring(end);
editor.setSelectionRange(start + 2, end + 2);
}
},
'ctrl+f': function(e) {
e.preventDefault();
showToast('搜索功能 (Ctrl+F)');
const keyword = prompt('搜索:', '');
if (keyword) {
const index = editor.value.indexOf(keyword);
if (index >= 0) {
editor.focus();
editor.setSelectionRange(index, index + keyword.length);
} else {
showToast('未找到: ' + keyword);
}
}
},
'ctrl+z': function(e) {
showToast('撤销 (Ctrl+Z)');
},
'ctrl+y': function(e) {
showToast('重做 (Ctrl+Y)');
}
};
editor.addEventListener('keydown', function(event) {
const parts = [];
if (event.ctrlKey || event.metaKey) parts.push('ctrl');
if (event.shiftKey) parts.push('shift');
if (event.altKey) parts.push('alt');
parts.push(event.key.toLowerCase());
const shortcut = parts.join('+');
if (shortcuts[shortcut]) {
shortcuts[shortcut](event);
}
});
const saved = localStorage.getItem('editorContent');
if (saved) {
editor.value = saved;
}
</script>
</body>
</html>示例3:按键过滤与输入控制
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>按键过滤与输入控制</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 600px;
margin: 40px auto;
padding: 20px;
}
.form-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}
h1 { text-align: center; }
.form-group { margin-bottom: 20px; }
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: bold;
color: #555;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 10px 14px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s;
}
.form-group input:focus { border-color: #2196F3; }
.form-group input.rejected {
border-color: #f44336;
animation: shake 0.3s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.hint { font-size: 12px; color: #999; margin-top: 4px; }
.hint.rejected { color: #f44336; }
</style>
</head>
<body>
<div class="form-card">
<h1>按键过滤与输入控制</h1>
<div class="form-group">
<label>仅数字输入</label>
<input type="text" id="numbersOnly" placeholder="只能输入数字...">
<div class="hint" id="numbersHint">只允许 0-9、Backspace、Delete、方向键</div>
</div>
<div class="form-group">
<label>仅字母输入</label>
<input type="text" id="lettersOnly" placeholder="只能输入字母...">
<div class="hint">只允许 a-z A-Z 和空格</div>
</div>
<div class="form-group">
<label>手机号输入(限制长度)</label>
<input type="text" id="phoneInput" placeholder="输入11位手机号..." maxlength="11">
<div class="hint">只允许数字,最多11位</div>
</div>
<div class="form-group">
<label>金额输入(允许小数)</label>
<input type="text" id="amountInput" placeholder="输入金额...">
<div class="hint">允许数字和一个小数点,小数点后最多2位</div>
</div>
<div class="form-group">
<label>禁止粘贴的输入框</label>
<input type="text" id="noPasteInput" placeholder="此输入框禁止粘贴...">
<div class="hint">Ctrl+V 和右键粘贴被禁止</div>
</div>
</div>
<script>
// ========== 仅数字输入 ==========
const numbersOnly = document.getElementById('numbersOnly');
const numbersHint = document.getElementById('numbersHint');
numbersOnly.addEventListener('keydown', function(event) {
const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End'];
const isNumber = /^[0-9]$/.test(event.key);
const isAllowed = allowedKeys.includes(event.key);
const hasModifier = event.ctrlKey || event.altKey || event.metaKey;
if (hasModifier) return; // 允许Ctrl+C/V等
if (!isNumber && !isAllowed) {
event.preventDefault();
numbersHint.textContent = `按键 "${event.key}" 被过滤,只允许数字`;
numbersHint.classList.add('rejected');
this.classList.add('rejected');
setTimeout(function() {
numbersHint.classList.remove('rejected');
numbersOnly.classList.remove('rejected');
}, 1000);
} else {
numbersHint.textContent = '只允许 0-9、Backspace、Delete、方向键';
numbersHint.classList.remove('rejected');
}
});
// ========== 仅字母输入 ==========
const lettersOnly = document.getElementById('lettersOnly');
lettersOnly.addEventListener('keydown', function(event) {
const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End', ' '];
const isLetter = /^[a-zA-Z]$/.test(event.key);
const isAllowed = allowedKeys.includes(event.key);
const hasModifier = event.ctrlKey || event.altKey || event.metaKey;
if (hasModifier) return;
if (!isLetter && !isAllowed) {
event.preventDefault();
this.classList.add('rejected');
setTimeout(() => this.classList.remove('rejected'), 300);
}
});
// ========== 手机号输入 ==========
const phoneInput = document.getElementById('phoneInput');
phoneInput.addEventListener('keydown', function(event) {
const hasModifier = event.ctrlKey || event.altKey || event.metaKey;
if (hasModifier) return;
const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'];
const isNumber = /^[0-9]$/.test(event.key);
if (!isNumber && !allowedKeys.includes(event.key)) {
event.preventDefault();
return;
}
if (isNumber && this.value.length >= 11) {
event.preventDefault();
}
});
// ========== 金额输入 ==========
const amountInput = document.getElementById('amountInput');
amountInput.addEventListener('keydown', function(event) {
const hasModifier = event.ctrlKey || event.altKey || event.metaKey;
if (hasModifier) return;
const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'];
const isNumber = /^[0-9]$/.test(event.key);
const isDot = event.key === '.';
if (!isNumber && !isDot && !allowedKeys.includes(event.key)) {
event.preventDefault();
return;
}
if (isDot && this.value.includes('.')) {
event.preventDefault();
return;
}
if (isNumber && this.value.includes('.')) {
const decimalPart = this.value.split('.')[1];
if (decimalPart && decimalPart.length >= 2) {
const selectionStart = this.selectionStart;
const dotIndex = this.value.indexOf('.');
if (selectionStart > dotIndex) {
event.preventDefault();
}
}
}
});
// ========== 禁止粘贴 ==========
const noPasteInput = document.getElementById('noPasteInput');
noPasteInput.addEventListener('keydown', function(event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
event.preventDefault();
this.classList.add('rejected');
setTimeout(() => this.classList.remove('rejected'), 300);
}
});
noPasteInput.addEventListener('paste', function(event) {
event.preventDefault();
});
</script>
</body>
</html>示例4:游戏中的键盘控制
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>游戏中的键盘控制</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
overflow: hidden;
}
.game-container {
position: relative;
background: #1a1a2e;
border-radius: 12px;
overflow: hidden;
margin: 20px 0;
}
canvas { display: block; width: 100%; }
.game-info {
display: flex;
justify-content: space-between;
padding: 12px 20px;
background: #16213e;
color: white;
font-size: 14px;
}
.controls-hint {
background: white;
border-radius: 10px;
padding: 16px;
margin: 16px 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.key-hint {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 4px 8px;
}
kbd {
display: inline-block;
padding: 4px 10px;
background: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
font-family: 'Consolas', monospace;
font-size: 13px;
box-shadow: 0 1px 0 #ccc;
}
.key-state {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #ccc;
margin-left: 4px;
}
.key-state.active { background: #4CAF50; }
</style>
</head>
<body>
<h1>游戏中的键盘控制</h1>
<div class="game-container" id="gameContainer">
<div class="game-info">
<span>得分: <strong id="score">0</strong></span>
<span>使用方向键或WASD控制移动</span>
</div>
<canvas id="gameCanvas" width="760" height="400"></canvas>
</div>
<div class="controls-hint">
<h3 style="margin:0 0 8px 0;">按键状态</h3>
<span class="key-hint"><kbd>W</kbd>/<kbd>↑</kbd> 上 <span class="key-state" id="keyUp"></span></span>
<span class="key-hint"><kbd>S</kbd>/<kbd>↓</kbd> 下 <span class="key-state" id="keyDown"></span></span>
<span class="key-hint"><kbd>A</kbd>/<kbd>←</kbd> 左 <span class="key-state" id="keyLeft"></span></span>
<span class="key-hint"><kbd>D</kbd>/<kbd>→</kbd> 右 <span class="key-state" id="keyRight"></span></span>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const player = { x: 380, y: 200, size: 20, speed: 4, color: '#4CAF50' };
const keys = {};
let score = 0;
let collectibles = [];
function spawnCollectible() {
collectibles.push({
x: Math.random() * (canvas.width - 20) + 10,
y: Math.random() * (canvas.height - 20) + 10,
size: 10,
color: '#FFD700'
});
}
for (let i = 0; i < 5; i++) spawnCollectible();
document.addEventListener('keydown', function(event) {
keys[event.code] = true;
updateKeyIndicators();
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(event.code)) {
event.preventDefault();
}
});
document.addEventListener('keyup', function(event) {
keys[event.code] = false;
updateKeyIndicators();
});
function updateKeyIndicators() {
const up = keys['ArrowUp'] || keys['KeyW'];
const down = keys['ArrowDown'] || keys['KeyS'];
const left = keys['ArrowLeft'] || keys['KeyA'];
const right = keys['ArrowRight'] || keys['KeyD'];
document.getElementById('keyUp').className = 'key-state' + (up ? ' active' : '');
document.getElementById('keyDown').className = 'key-state' + (down ? ' active' : '');
document.getElementById('keyLeft').className = 'key-state' + (left ? ' active' : '');
document.getElementById('keyRight').className = 'key-state' + (right ? ' active' : '');
}
function gameLoop() {
if (keys['ArrowUp'] || keys['KeyW']) player.y = Math.max(player.size, player.y - player.speed);
if (keys['ArrowDown'] || keys['KeyS']) player.y = Math.min(canvas.height - player.size, player.y + player.speed);
if (keys['ArrowLeft'] || keys['KeyA']) player.x = Math.max(player.size, player.x - player.speed);
if (keys['ArrowRight'] || keys['KeyD']) player.x = Math.min(canvas.width - player.size, player.x + player.speed);
collectibles = collectibles.filter(function(c) {
const dx = player.x - c.x;
const dy = player.y - c.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.size + c.size) {
score += 10;
document.getElementById('score').textContent = score;
spawnCollectible();
return false;
}
return true;
});
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#16213e';
ctx.lineWidth = 1;
for (let x = 0; x < canvas.width; x += 40) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += 40) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
collectibles.forEach(function(c) {
ctx.fillStyle = c.color;
ctx.beginPath();
ctx.arc(c.x, c.y, c.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#FFA000';
ctx.lineWidth = 2;
ctx.stroke();
});
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.arc(player.x, player.y, player.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#2E7D32';
ctx.lineWidth = 2;
ctx.stroke();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>五、浏览器兼容性
IE兼容性说明:IE不支持
event.key和event.code,需使用已废弃的event.keyCode。建议使用event.key为主,event.keyCode为降级方案。
六、注意事项与最佳实践
1. 优先使用event.key和event.code
代码示例
// 不推荐:使用已废弃的keyCode
if (event.keyCode === 13) { /* Enter */ }
// 推荐:使用key
if (event.key === 'Enter') { /* Enter */ }
// 推荐:使用code(物理按键,不受输入法影响)
if (event.code === 'KeyA') { /* A键 */ }2. 快捷键使用event.code更可靠
代码示例
// 推荐:使用code检测快捷键(不受输入法影响)
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.code === 'KeyS') {
event.preventDefault();
saveDocument();
}
});3. 按键过滤时允许功能键和修饰键
代码示例
input.addEventListener('keydown', function(event) {
// 始终允许功能键
const controlKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End'];
if (controlKeys.includes(event.key)) return;
// 允许Ctrl组合键(如Ctrl+C/V/A)
if (event.ctrlKey || event.metaKey) return;
// 过滤逻辑
if (!/^[0-9]$/.test(event.key)) {
event.preventDefault();
}
});4. 防止方向键滚动页面
代码示例
document.addEventListener('keydown', function(event) {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(event.code)) {
event.preventDefault();
}
});5. 游戏中使用keydown/keyup追踪按键状态
代码示例
const keys = {};
document.addEventListener('keydown', function(e) {
keys[e.code] = true;
});
document.addEventListener('keyup', function(e) {
keys[e.code] = false;
});
function gameLoop() {
if (keys['ArrowUp'] || keys['KeyW']) moveUp();
if (keys['ArrowDown'] || keys['KeyS']) moveDown();
requestAnimationFrame(gameLoop);
}6. 注意IME组合中的keydown事件
代码示例
element.addEventListener('keydown', function(event) {
// 在IME组合中,某些按键的key值可能为'Process'
if (event.isComposing || event.key === 'Process') {
return; // 忽略IME组合中的按键
}
handleKeyDown(event);
});七、代码规范示例
代码示例
/**
* 快捷键管理器 - 规范化的快捷键注册与处理
*/
class ShortcutManager {
constructor() {
this.shortcuts = new Map();
this._handleKeyDown = this._handleKeyDown.bind(this);
document.addEventListener('keydown', this._handleKeyDown);
}
/**
* 注册快捷键
* @param {string} combination - 快捷键组合,如 'ctrl+s', 'ctrl+shift+n'
* @param {Function} handler - 处理函数
* @param {Object} options - 选项
*/
register(combination, handler, options = {}) {
const normalized = this._normalize(combination);
this.shortcuts.set(normalized, {
handler,
description: options.description || '',
preventDefault: options.preventDefault !== false
});
}
unregister(combination) {
this.shortcuts.delete(this._normalize(combination));
}
_normalize(combination) {
return combination.toLowerCase().split('+').sort().join('+');
}
_getCombination(event) {
const parts = [];
if (event.ctrlKey || event.metaKey) parts.push('ctrl');
if (event.shiftKey) parts.push('shift');
if (event.altKey) parts.push('alt');
parts.push(event.key.toLowerCase());
return parts.sort().join('+');
}
_handleKeyDown(event) {
const combination = this._getCombination(event);
const shortcut = this.shortcuts.get(combination);
if (shortcut) {
if (shortcut.preventDefault) {
event.preventDefault();
}
shortcut.handler(event);
}
}
destroy() {
document.removeEventListener('keydown', this._handleKeyDown);
this.shortcuts.clear();
}
}
// 使用示例
const shortcuts = new ShortcutManager();
shortcuts.register('ctrl+s', function() {
console.log('保存文档');
}, { description: '保存' });
shortcuts.register('ctrl+shift+n', function() {
console.log('新建窗口');
}, { description: '新建窗口' });
shortcuts.register('escape', function() {
console.log('取消操作');
}, { description: '取消' });八、常见问题与解决方案
常见问题
问题1:IE不支持event.key,如何兼容?
可以使用key的polyfill方案:通过检测KeyboardEvent.prototype是否包含key属性,如果不支持则通过Object.defineProperty定义一个getter,根据keyCode映射到对应的key值。维护一个键码映射表(如8→'Backspace'、13→'Enter'、37→'ArrowLeft'等),对于字母和数字键则使用String.fromCharCode转换。
问题2:中文输入法下keydown行为异常怎么办?
IME组合过程中keydown的key值可能为'Process'或'Unidentified'。解决方案是检查event.isComposing属性,如果为true则忽略该事件。兼容方案是同时监听compositionstart和compositionend事件,用标志变量控制是否处理keydown。
问题3:keydown重复触发导致操作重复执行?
长按按键时keydown会重复触发。解决方案1:检查event.repeat属性,如果为true则忽略重复触发。解决方案2:对于游戏等需要持续移动的场景,使用keydown/keyup追踪按键状态(存入对象),在游戏循环中检查状态而非依赖事件驱动,这样可以实现流畅的持续移动效果。
问题4:Mac的Command键与Windows的Ctrl键如何统一处理?
Mac使用Command键(对应metaKey),Windows使用Ctrl键(对应ctrlKey)。解决方案是同时检查两个属性:if (event.ctrlKey || event.metaKey),这样无论在Mac还是Windows上都能正确响应快捷键。
问题5:在input中过滤按键时如何允许Ctrl组合键?
过滤输入时如果直接阻止非数字按键,会同时阻止Ctrl+C(复制)、Ctrl+V(粘贴)、Ctrl+A(全选)等操作。解决方案是在过滤逻辑前添加判断:if (event.ctrlKey || event.metaKey) return;,始终允许Ctrl/Meta组合键通过。
九、总结
onkeydown事件是处理键盘输入的核心机制,掌握其特性和最佳实践对于构建键盘友好的Web应用至关重要:
-
事件属性:优先使用
event.key(字符值)和event.code(物理键码),避免使用已废弃的keyCode和which。key适合字符处理,code适合快捷键。 -
修饰键检测:通过
ctrlKey、shiftKey、altKey、metaKey检测组合键,注意Mac使用Command键(metaKey)而Windows使用Ctrl键。 -
快捷键实现:使用事件映射表管理快捷键,统一处理修饰键组合,注意
preventDefault阻止浏览器默认行为。 -
按键过滤:过滤输入时始终允许功能键(Backspace、Delete、方向键等)和Ctrl组合键(复制、粘贴、全选),避免影响正常编辑操作。
-
游戏控制:使用keydown/keyup追踪按键状态,在游戏循环中检查状态而非依赖事件驱动,实现流畅的持续移动。
-
IME兼容:中文输入法组合过程中keydown行为可能异常,使用
event.isComposing或composition事件过滤组合中的按键。
本文涉及AI创作
内容由AI创作,请仔细甄别