pin_drop当前位置:知识文库 ❯ 图文

JS集成:HTML onkeydown事 - 详细教程与实战指南

一、教程简介

键盘事件是Web应用中处理用户键盘输入的核心机制。onkeydown事件在用户按下键盘按键时触发,是实现快捷键、按键过滤、游戏控制、表单验证等功能的基础。与keyup和已废弃的keypress不同,keydown在按键按下的瞬间就触发,能够捕获所有按键包括功能键和修饰键。本教程将全面讲解onkeydown事件的触发时机、键盘事件对象的丰富属性、快捷键的实现方式、按键过滤技巧,以及键盘事件的最佳实践。


二、核心概念

三种键盘事件对比

事件 触发时机 能否捕获功能键 能否获取字符 推荐用途
keydown 按键按下时 部分能 快捷键、按键过滤、游戏控制
keyup 按键释放时 部分能 输入完成检测、游戏控制
keypress (已废弃) 按键按下时(字符键) 不能 不推荐使用

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 String 按键的字符串值 'a', 'Enter', 'ArrowUp', 'Shift'
code String 按键的物理键码 'KeyA', 'Enter', 'ArrowUp', 'ShiftLeft'
keyCode Number 已废弃的键码 65, 13, 38, 16
which Number 已废弃的键码 65, 13, 38, 16
altKey Boolean 是否按住Alt true/false
ctrlKey Boolean 是否按住Ctrl true/false
shiftKey Boolean 是否按住Shift true/false
metaKey Boolean 是否按住Meta(Win/Command) true/false
repeat Boolean 是否为长按重复 true/false
location Number 按键位置 0=标准, 1=左, 2=右, 3=数字键盘
isComposing Boolean 是否在IME组合中 true/false

key vs code 的区别

按键 key值 code值 说明
A键(英文输入法) 'a' 'KeyA' key受输入法和大小写影响
A键(Shift+A) 'A' 'KeyA' key反映实际字符
A键(中文输入法) 'a'或'Process' 'KeyA' code始终是物理键
左Ctrl 'Control' 'ControlLeft' code区分左右
右Ctrl 'Control' 'ControlRight' code区分左右
数字1(主键盘) '1' 'Digit1' -
数字1(数字键盘) '1' 'Numpad1' 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>

五、浏览器兼容性

特性 Chrome Firefox Safari Edge IE
keydown事件 全部 全部 全部 全部 全部
event.key 51+ 23+ 10.1+ 12+ 不支持(IE9+)
event.code 48+ 38+ 10.1+ 12+ 不支持
event.keyCode 全部 全部 全部 全部 全部(已废弃)
event.which 全部 全部 全部 全部 全部(已废弃)
event.repeat 全部 全部 全部 全部 不支持(IE9+)
event.location 全部 全部 全部 全部 不支持(IE9+)
event.isComposing 60+ 31+ 不支持 17+ 不支持
修饰键属性 全部 全部 全部 全部 全部

IE兼容性说明:IE不支持event.keyevent.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则忽略该事件。兼容方案是同时监听compositionstartcompositionend事件,用标志变量控制是否处理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(物理键码),避免使用已废弃的keyCodewhichkey适合字符处理,code适合快捷键。

  • 修饰键检测:通过ctrlKeyshiftKeyaltKeymetaKey检测组合键,注意Mac使用Command键(metaKey)而Windows使用Ctrl键。

  • 快捷键实现:使用事件映射表管理快捷键,统一处理修饰键组合,注意preventDefault阻止浏览器默认行为。

  • 按键过滤:过滤输入时始终允许功能键(Backspace、Delete、方向键等)和Ctrl组合键(复制、粘贴、全选),避免影响正常编辑操作。

  • 游戏控制:使用keydown/keyup追踪按键状态,在游戏循环中检查状态而非依赖事件驱动,实现流畅的持续移动。

  • IME兼容:中文输入法组合过程中keydown行为可能异常,使用event.isComposing或composition事件过滤组合中的按键。

标签: onkeydown事件 键盘事件 快捷键实现 按键过滤 事件对象属性 游戏控制 IME兼容 修饰键

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: JS集成:HTML onfocus与on - 详细教程与实战指南 下一篇: JS集成:HTML onkeyup事件: - 详细教程与实战指南

poll相关推荐