Вы разрабатываете текстовый редактор. Нужно: Ctrl+S — сохранить, Ctrl+Z — отменить, Escape — закрыть диалог. Параллельно — браузерная игра, где стрелки двигают персонажа. Всё это строится на двух событиях: keydown и keyup.
Клавиатурные события позволяют добавлять горячие клавиши, валидировать ввод в реальном времени и строить игровое управление — всё без сторонних библиотек.
element.addEventListener('keydown', handler) // клавиша нажата
element.addEventListener('keyup', handler) // клавиша отпущена
element.addEventListener('keypress', handler) // УСТАРЕВШЕЕ — не использоватьkeypress не поддерживает специальные клавиши (стрелки, Delete и т.д.) и считается устаревшим. Используй keydown вместо него.
document.addEventListener('keydown', (event) => {
console.log(event.key) // 'a' / 'A' (зависит от Shift и раскладки)
console.log(event.code) // 'KeyA' (физическое расположение клавиши)
})| Клавиша | event.key | event.code |
|---|---|---|
| A в русской раскладке | 'ф' | 'KeyA' |
| A в латинской | 'a' / 'A' | 'KeyA' |
| Enter | 'Enter' | 'Enter' |
| Стрелка вверх | 'ArrowUp' | 'ArrowUp' |
| Цифра 1 | '1' | 'Digit1' |
| Пробел | ' ' | 'Space' |
Правило: используй event.code для горячих клавиш (не зависит от раскладки), event.key — для ввода символов.
document.addEventListener('keydown', (event) => {
console.log(event.ctrlKey) // true если удержан Ctrl (Command на Mac)
console.log(event.shiftKey) // true если удержан Shift
console.log(event.altKey) // true если удержан Alt (Option на Mac)
console.log(event.metaKey) // true если удержан Meta (Win/Command)
})При удержании клавиши браузер посылает повторные события keydown:
document.addEventListener('keydown', (event) => {
if (event.repeat) {
// Клавиша удерживается — это автоповтор
return // часто нужно игнорировать
}
// Первое нажатие
})document.addEventListener('keydown', (event) => {
// Ctrl+S — сохранить
if (event.ctrlKey && event.code === 'KeyS') {
event.preventDefault() // запрет стандартного сохранения страницы
saveDocument()
return
}
// Ctrl+Z — отменить
if (event.ctrlKey && !event.shiftKey && event.code === 'KeyZ') {
event.preventDefault()
undoLastAction()
return
}
// Ctrl+Shift+Z — повторить
if (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') {
event.preventDefault()
redoLastAction()
return
}
})class HotkeyRegistry {
constructor() {
this.hotkeys = new Map()
document.addEventListener('keydown', (event) => {
const key = this.buildKey(event)
const action = this.hotkeys.get(key)
if (action) {
event.preventDefault()
action()
}
})
}
buildKey(event) {
const parts = []
if (event.ctrlKey) parts.push('Ctrl')
if (event.shiftKey) parts.push('Shift')
if (event.altKey) parts.push('Alt')
parts.push(event.code)
return parts.join('+')
}
register(combo, action) {
this.hotkeys.set(combo, action)
}
}
const hotkeys = new HotkeyRegistry()
hotkeys.register('Ctrl+KeyS', () => console.log('Сохранение...'))
hotkeys.register('Ctrl+KeyZ', () => console.log('Отмена...'))
hotkeys.register('Ctrl+Shift+KeyZ', () => console.log('Повтор...'))const pressedKeys = new Set()
document.addEventListener('keydown', (event) => {
pressedKeys.add(event.code)
// В игре: проверяем одновременное нажатие стрелок
if (pressedKeys.has('ArrowUp') && pressedKeys.has('ArrowRight')) {
movePlayerDiagonally()
}
})
document.addEventListener('keyup', (event) => {
pressedKeys.delete(event.code)
})nameInput.addEventListener('keydown', (event) => {
// Разрешить: Backspace, Delete, стрелки, Tab
const allowed = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Space', 'Tab']
if (allowed.includes(event.code)) return
// Запретить цифры
if (event.code.startsWith('Digit')) {
event.preventDefault()
}
})Ошибка 1: используют event.key для горячих клавиш — ломается при смене раскладки
// Сломано: при русской раскладке 's' становится 'ы' — Ctrl+S не работает
if (event.ctrlKey && event.key === 's') {
saveDocument()
}
// Исправлено: event.code не зависит от раскладки
if (event.ctrlKey && event.code === 'KeyS') {
saveDocument()
}Ошибка 2: не предотвращают стандартное поведение браузера
// Сломано: Ctrl+S открывает диалог "Сохранить страницу" браузера
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.code === 'KeyS') {
saveDocument() // браузер тоже сохраняет!
}
})
// Исправлено:
if (e.ctrlKey && e.code === 'KeyS') {
e.preventDefault() // сначала отменить стандартное
saveDocument()
}Ошибка 3: не обрабатывают event.repeat при удержании клавиши
// Сломано: при удержании клавиши функция вызывается сотни раз
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
shootBullet() // выстрелит сотни раз!
}
})
// Исправлено: проверяем repeat
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && !e.repeat) {
shootBullet() // только первое нажатие
}
})Реестр горячих клавиш: Ctrl+S сохранить, Ctrl+Z отменить — через мок-события клавиатуры
// Реестр горячих клавиш без DOM
// Моделируем KeyboardEvent-объекты и обрабатываем их
function buildHotkeyString(event) {
const parts = []
if (event.ctrlKey) parts.push('Ctrl')
if (event.shiftKey) parts.push('Shift')
if (event.altKey) parts.push('Alt')
if (event.metaKey) parts.push('Meta')
parts.push(event.code)
return parts.join('+')
}
class HotkeyRegistry {
constructor() {
this._hotkeys = new Map()
this._history = [] // лог выполненных команд для демонстрации
}
register(combo, description, action) {
this._hotkeys.set(combo, { description, action })
return this
}
handle(event) {
if (event.repeat) return 'repeat — ignored'
const key = buildHotkeyString(event)
const entry = this._hotkeys.get(key)
if (!entry) return `no handler for "${key}"`
const result = entry.action()
this._history.push({ key, description: entry.description, result })
return `executed: ${entry.description}`
}
getHistory() { return this._history }
}
// Создаём реестр и регистрируем горячие клавиши
const registry = new HotkeyRegistry()
// Симуляция состояния приложения
const appState = {
document: 'Привет, мир! Это тестовый документ.',
history: [],
saved: false,
}
registry
.register('Ctrl+KeyS', 'Сохранить документ', () => {
appState.saved = true
return `сохранено: "${appState.document.slice(0, 20)}..."`
})
.register('Ctrl+KeyZ', 'Отменить действие', () => {
const prev = appState.history.pop()
if (prev) { appState.document = prev; return 'откат выполнен' }
return 'нечего отменять'
})
.register('Ctrl+Shift+KeyZ', 'Повторить действие', () => {
return 'повтор выполнен'
})
.register('Ctrl+KeyA', 'Выделить всё', () => {
return `выделено ${appState.document.length} символов`
})
.register('Ctrl+KeyC', 'Копировать', () => {
return `скопировано в буфер`
})
// Симулируем нажатия клавиш (мок-события)
const mockKeyEvents = [
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyA', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyC', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyS', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyZ', repeat: false },
{ ctrlKey: true, shiftKey: true, altKey: false, metaKey: false, code: 'KeyZ', repeat: false },
{ ctrlKey: false, shiftKey: false, altKey: false, metaKey: false, code: 'KeyA', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyS', repeat: true }, // автоповтор
]
console.log('=== Обработка горячих клавиш ===')
mockKeyEvents.forEach(event => {
const combo = buildHotkeyString(event) + (event.repeat ? ' [repeat]' : '')
const result = registry.handle(event)
console.log(`${combo.padEnd(25)} → ${result}`)
})
console.log('\n=== История выполненных команд ===')
registry.getHistory().forEach(entry => {
console.log(`[${entry.key}] ${entry.description}: ${entry.result}`)
})
// Демонстрация отслеживания нескольких клавиш
console.log('\n=== Несколько клавиш одновременно (игровой паттерн) ===')
const pressedKeys = new Set()
const gameEvents = [
{ type: 'keydown', code: 'ArrowUp' },
{ type: 'keydown', code: 'ArrowRight' },
{ type: 'keyup', code: 'ArrowUp' },
{ type: 'keyup', code: 'ArrowRight' },
]
gameEvents.forEach(event => {
if (event.type === 'keydown') pressedKeys.add(event.code)
else pressedKeys.delete(event.code)
const direction = []
if (pressedKeys.has('ArrowUp')) direction.push('вверх')
if (pressedKeys.has('ArrowDown')) direction.push('вниз')
if (pressedKeys.has('ArrowLeft')) direction.push('влево')
if (pressedKeys.has('ArrowRight')) direction.push('вправо')
const move = direction.length ? direction.join('+') : 'стоим'
console.log(`${event.type} ${event.code} → движение: ${move}`)
})Вы разрабатываете текстовый редактор. Нужно: Ctrl+S — сохранить, Ctrl+Z — отменить, Escape — закрыть диалог. Параллельно — браузерная игра, где стрелки двигают персонажа. Всё это строится на двух событиях: keydown и keyup.
Клавиатурные события позволяют добавлять горячие клавиши, валидировать ввод в реальном времени и строить игровое управление — всё без сторонних библиотек.
element.addEventListener('keydown', handler) // клавиша нажата
element.addEventListener('keyup', handler) // клавиша отпущена
element.addEventListener('keypress', handler) // УСТАРЕВШЕЕ — не использоватьkeypress не поддерживает специальные клавиши (стрелки, Delete и т.д.) и считается устаревшим. Используй keydown вместо него.
document.addEventListener('keydown', (event) => {
console.log(event.key) // 'a' / 'A' (зависит от Shift и раскладки)
console.log(event.code) // 'KeyA' (физическое расположение клавиши)
})| Клавиша | event.key | event.code |
|---|---|---|
| A в русской раскладке | 'ф' | 'KeyA' |
| A в латинской | 'a' / 'A' | 'KeyA' |
| Enter | 'Enter' | 'Enter' |
| Стрелка вверх | 'ArrowUp' | 'ArrowUp' |
| Цифра 1 | '1' | 'Digit1' |
| Пробел | ' ' | 'Space' |
Правило: используй event.code для горячих клавиш (не зависит от раскладки), event.key — для ввода символов.
document.addEventListener('keydown', (event) => {
console.log(event.ctrlKey) // true если удержан Ctrl (Command на Mac)
console.log(event.shiftKey) // true если удержан Shift
console.log(event.altKey) // true если удержан Alt (Option на Mac)
console.log(event.metaKey) // true если удержан Meta (Win/Command)
})При удержании клавиши браузер посылает повторные события keydown:
document.addEventListener('keydown', (event) => {
if (event.repeat) {
// Клавиша удерживается — это автоповтор
return // часто нужно игнорировать
}
// Первое нажатие
})document.addEventListener('keydown', (event) => {
// Ctrl+S — сохранить
if (event.ctrlKey && event.code === 'KeyS') {
event.preventDefault() // запрет стандартного сохранения страницы
saveDocument()
return
}
// Ctrl+Z — отменить
if (event.ctrlKey && !event.shiftKey && event.code === 'KeyZ') {
event.preventDefault()
undoLastAction()
return
}
// Ctrl+Shift+Z — повторить
if (event.ctrlKey && event.shiftKey && event.code === 'KeyZ') {
event.preventDefault()
redoLastAction()
return
}
})class HotkeyRegistry {
constructor() {
this.hotkeys = new Map()
document.addEventListener('keydown', (event) => {
const key = this.buildKey(event)
const action = this.hotkeys.get(key)
if (action) {
event.preventDefault()
action()
}
})
}
buildKey(event) {
const parts = []
if (event.ctrlKey) parts.push('Ctrl')
if (event.shiftKey) parts.push('Shift')
if (event.altKey) parts.push('Alt')
parts.push(event.code)
return parts.join('+')
}
register(combo, action) {
this.hotkeys.set(combo, action)
}
}
const hotkeys = new HotkeyRegistry()
hotkeys.register('Ctrl+KeyS', () => console.log('Сохранение...'))
hotkeys.register('Ctrl+KeyZ', () => console.log('Отмена...'))
hotkeys.register('Ctrl+Shift+KeyZ', () => console.log('Повтор...'))const pressedKeys = new Set()
document.addEventListener('keydown', (event) => {
pressedKeys.add(event.code)
// В игре: проверяем одновременное нажатие стрелок
if (pressedKeys.has('ArrowUp') && pressedKeys.has('ArrowRight')) {
movePlayerDiagonally()
}
})
document.addEventListener('keyup', (event) => {
pressedKeys.delete(event.code)
})nameInput.addEventListener('keydown', (event) => {
// Разрешить: Backspace, Delete, стрелки, Tab
const allowed = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Space', 'Tab']
if (allowed.includes(event.code)) return
// Запретить цифры
if (event.code.startsWith('Digit')) {
event.preventDefault()
}
})Ошибка 1: используют event.key для горячих клавиш — ломается при смене раскладки
// Сломано: при русской раскладке 's' становится 'ы' — Ctrl+S не работает
if (event.ctrlKey && event.key === 's') {
saveDocument()
}
// Исправлено: event.code не зависит от раскладки
if (event.ctrlKey && event.code === 'KeyS') {
saveDocument()
}Ошибка 2: не предотвращают стандартное поведение браузера
// Сломано: Ctrl+S открывает диалог "Сохранить страницу" браузера
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.code === 'KeyS') {
saveDocument() // браузер тоже сохраняет!
}
})
// Исправлено:
if (e.ctrlKey && e.code === 'KeyS') {
e.preventDefault() // сначала отменить стандартное
saveDocument()
}Ошибка 3: не обрабатывают event.repeat при удержании клавиши
// Сломано: при удержании клавиши функция вызывается сотни раз
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
shootBullet() // выстрелит сотни раз!
}
})
// Исправлено: проверяем repeat
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && !e.repeat) {
shootBullet() // только первое нажатие
}
})Реестр горячих клавиш: Ctrl+S сохранить, Ctrl+Z отменить — через мок-события клавиатуры
// Реестр горячих клавиш без DOM
// Моделируем KeyboardEvent-объекты и обрабатываем их
function buildHotkeyString(event) {
const parts = []
if (event.ctrlKey) parts.push('Ctrl')
if (event.shiftKey) parts.push('Shift')
if (event.altKey) parts.push('Alt')
if (event.metaKey) parts.push('Meta')
parts.push(event.code)
return parts.join('+')
}
class HotkeyRegistry {
constructor() {
this._hotkeys = new Map()
this._history = [] // лог выполненных команд для демонстрации
}
register(combo, description, action) {
this._hotkeys.set(combo, { description, action })
return this
}
handle(event) {
if (event.repeat) return 'repeat — ignored'
const key = buildHotkeyString(event)
const entry = this._hotkeys.get(key)
if (!entry) return `no handler for "${key}"`
const result = entry.action()
this._history.push({ key, description: entry.description, result })
return `executed: ${entry.description}`
}
getHistory() { return this._history }
}
// Создаём реестр и регистрируем горячие клавиши
const registry = new HotkeyRegistry()
// Симуляция состояния приложения
const appState = {
document: 'Привет, мир! Это тестовый документ.',
history: [],
saved: false,
}
registry
.register('Ctrl+KeyS', 'Сохранить документ', () => {
appState.saved = true
return `сохранено: "${appState.document.slice(0, 20)}..."`
})
.register('Ctrl+KeyZ', 'Отменить действие', () => {
const prev = appState.history.pop()
if (prev) { appState.document = prev; return 'откат выполнен' }
return 'нечего отменять'
})
.register('Ctrl+Shift+KeyZ', 'Повторить действие', () => {
return 'повтор выполнен'
})
.register('Ctrl+KeyA', 'Выделить всё', () => {
return `выделено ${appState.document.length} символов`
})
.register('Ctrl+KeyC', 'Копировать', () => {
return `скопировано в буфер`
})
// Симулируем нажатия клавиш (мок-события)
const mockKeyEvents = [
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyA', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyC', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyS', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyZ', repeat: false },
{ ctrlKey: true, shiftKey: true, altKey: false, metaKey: false, code: 'KeyZ', repeat: false },
{ ctrlKey: false, shiftKey: false, altKey: false, metaKey: false, code: 'KeyA', repeat: false },
{ ctrlKey: true, shiftKey: false, altKey: false, metaKey: false, code: 'KeyS', repeat: true }, // автоповтор
]
console.log('=== Обработка горячих клавиш ===')
mockKeyEvents.forEach(event => {
const combo = buildHotkeyString(event) + (event.repeat ? ' [repeat]' : '')
const result = registry.handle(event)
console.log(`${combo.padEnd(25)} → ${result}`)
})
console.log('\n=== История выполненных команд ===')
registry.getHistory().forEach(entry => {
console.log(`[${entry.key}] ${entry.description}: ${entry.result}`)
})
// Демонстрация отслеживания нескольких клавиш
console.log('\n=== Несколько клавиш одновременно (игровой паттерн) ===')
const pressedKeys = new Set()
const gameEvents = [
{ type: 'keydown', code: 'ArrowUp' },
{ type: 'keydown', code: 'ArrowRight' },
{ type: 'keyup', code: 'ArrowUp' },
{ type: 'keyup', code: 'ArrowRight' },
]
gameEvents.forEach(event => {
if (event.type === 'keydown') pressedKeys.add(event.code)
else pressedKeys.delete(event.code)
const direction = []
if (pressedKeys.has('ArrowUp')) direction.push('вверх')
if (pressedKeys.has('ArrowDown')) direction.push('вниз')
if (pressedKeys.has('ArrowLeft')) direction.push('влево')
if (pressedKeys.has('ArrowRight')) direction.push('вправо')
const move = direction.length ? direction.join('+') : 'стоим'
console.log(`${event.type} ${event.code} → движение: ${move}`)
})Напиши функцию parseHotkey(event), которая принимает объект мок-события клавиатуры { ctrlKey, shiftKey, altKey, metaKey, key, code } и возвращает строку горячей клавиши в формате "Ctrl+Shift+S". Модификаторы должны идти в порядке: Ctrl, Shift, Alt, Meta, затем event.key (не code). Если модификатор не нажат — его не включать.
[event.ctrlKey && "Ctrl", event.shiftKey && "Shift", event.altKey && "Alt", event.metaKey && "Meta", event.key].filter(Boolean).join("+")