最近在工作中有使用到键盘的监听事件,发现存在以下问题:
- 键盘事件不连续,即你每次都需要按下一次,然后抬起来,然后再按下去,如果一直按着不动,那么这个键盘事件也只执行一次
- 如果,我现在按下了向左的键盘,如果再继续按下向右的键盘,那么在我向右的键盘抬起的时候,我希望还可以触发向左的键盘事件
1. 问题分析
addEventListener监听的是keydown或者keyup,字面意思可以看得出来,就是键盘按下,或者抬起的时候,和一直按压没有关系- 根据
keydown或者keyup,在你按下向左的键盘之后,他会触发keydown,然后你按下向右的键盘,他会触发向右按键的keydown,当你向右的按键抬起的时候,会触发向右按键的keyup,然后你再抬起向左的按键,就会再次触发keyup,和你长按没关系,也不会记录你那个按键没有抬起来。
2. 解决思路
- 浏览器不会帮我们存住我们哪些已经按下了,之后在你相应的操作的时候触发响应的方法,那么
就需要我们把按键来存储一下 - 我们只是存住了,我们怎么知道还有哪些没有执行的,只有再搞一个帧循环,来看看还有哪些没有执行。
3. 代码环节
- 用
moveCommands来存储已经按下的指令
// 用 moveCommands 来存储
const moveCommands = [];
- 实现帧循环
handleTicker
// 用 moveCommands 来存储
const moveCommands = [];
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
handleTicker();
});
}
handleTicker();
- 实现键盘按下,抬起事件
- 键盘按下将按下的按键操作的指令放倒
moveCommands中 - 键盘抬起的时候,将抬起的按键从
moveCommands中移除
- 键盘按下将按下的按键操作的指令放倒
// 用 moveCommands 来存储
const moveCommands = [];
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
handleTicker();
});
}
handleTicker();
function handleKeydown() {
// TODO: 添加指令
}
function handleKeyup() {
// TODO: 移除指令
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
- 构造指令: 将左右上下的按键封装成响应的对象
// 用 moveCommands 来存储
const moveCommands = [];
const downCommand = {
id: 1, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const upCommand = {
id: 2, //唯一标识
dir: -1 // 方向: 1 表示正方向, -1 表示负方向
};
const rightCommand = {
id: 3, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const leftCommand = {
id: 4, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
handleTicker();
});
}
handleTicker();
function handleKeydown() {
// TODO: 添加指令
}
function handleKeyup() {
// TODO: 移除指令
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
- 开始进行
keydown操作
// 用 moveCommands 来存储
const moveCommands = [];
const downCommand = {
id: 1, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const upCommand = {
id: 2, //唯一标识
dir: -1 // 方向: 1 表示正方向, -1 表示负方向
};
const rightCommand = {
id: 3, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const leftCommand = {
id: 4, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
// 创造映射,方便找到
const commandMap = {
ArrowDown: downCommand,
ArrowUp: upCommand,
ArrowLeft: leftCommand,
ArrowRight: rightCommand
};
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
handleTicker();
});
}
handleTicker();
function hasExistCommand(command) {
return moveCommands.findIndex((item) => item.id === command.id) !== -1;
}
function removeFromMoveCommands(command) {
const index = moveCommands.findIndex((item) => item.id === command.id);
if (index !== -1) {
moveCommand.splice(index, 1);
}
}
function handleKeydown(e) {
// TODO: 添加指令
const command = commandMap[e.code];
// 如果已经在moveCommands中直接return
if (command && hasExistCommand(command)) return;
// 如果不存在就放在第一位,因为最新的优先级最高
moveCommands.unshift(command);
}
function handleKeyup(e) {
const command = commandMap[e.code];
// TODO: 移除指令
removeFromMoveCommands(command);
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
- 帧循环开始找此时应该执行的指令
// 用 moveCommands 来存储
const moveCommands = [];
const downCommand = {
id: 1, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const upCommand = {
id: 2, //唯一标识
dir: -1 // 方向: 1 表示正方向, -1 表示负方向
};
const rightCommand = {
id: 3, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const leftCommand = {
id: 4, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
// 创造映射,方便找到
const commandMap = {
ArrowDown: downCommand,
ArrowUp: upCommand,
ArrowLeft: leftCommand,
ArrowRight: rightCommand
};
function hasExistCommand(command) {
return moveCommands.findIndex((item) => item.id === command.id) !== -1;
}
function removeFromMoveCommands(command) {
const index = moveCommands.findIndex((item) => item.id === command.id);
if (index !== -1) {
moveCommand.splice(index, 1);
}
}
function handleKeydown(e) {
// TODO: 添加指令
const command = commandMap[e.code];
// 如果已经在moveCommands中直接return
if (command && hasExistCommand(command)) return;
// 如果不存在就放在第一位,因为最新的优先级最高
moveCommands.unshift(command);
}
function handleKeyup(e) {
const command = commandMap[e.code];
// TODO: 移除指令
removeFromMoveCommands(command);
}
function findUpOrDownCommand() {
return moveCommands.find((item) => item.id === 1 || item.id === 2);
}
function findLeftOrRightCommand() {
return moveCommands.find((item) => item.id === 3 || item.id === 4);
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
// 开始找指令,因为down和up是对立的,left和right是对立的 => 所以只要找到moveCommands中靠前的up或者down,left或者right
const upOrDownCommand = findUpOrDownCommand();
const leftOrRightCommand = findLeftOrRightCommand();
if (upAndDownCommand && leftOrRightCommand) {
// TODO: 两个方向
} else if (upOrDownCommand) {
// TODO: y 方向
} else if (leftOrRightCommand) {
// TODO: x 方向
}
handleTicker();
});
}
handleTicker();
- 优化一下:因为
up和down,left和right是对立的,所以在findUpOrDownCommand和findLeftOrRightCommand的时候取消id的判断,相反的给up和down加上相同的属性,给left和right加上相同的属性
// 用 moveCommands 来存储
const moveCommands = [];
const commandType = {
upOrDown: "upOrDown",
leftOrRight: "leftOrRight"
};
const downCommand = {
type: commandType.upOrDown,
id: 1, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const upCommand = {
type: commandType.upOrDown,
id: 2, //唯一标识
dir: -1 // 方向: 1 表示正方向, -1 表示负方向
};
const rightCommand = {
type: commandType.leftOrRight,
id: 3, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const leftCommand = {
type: commandType.leftOrRight,
id: 4, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
// 创造映射,方便找到
const commandMap = {
ArrowDown: downCommand,
ArrowUp: upCommand,
ArrowLeft: leftCommand,
ArrowRight: rightCommand
};
function hasExistCommand(command) {
return moveCommands.findIndex((item) => item.id === command.id) !== -1;
}
function removeFromMoveCommands(command) {
const index = moveCommands.findIndex((item) => item.id === command.id);
if (index !== -1) {
moveCommand.splice(index, 1);
}
}
function handleKeydown(e) {
// TODO: 添加指令
const command = commandMap[e.code];
// 如果已经在moveCommands中直接return
if (command && hasExistCommand(command)) return;
// 如果不存在就放在第一位,因为最新的优先级最高
moveCommands.unshift(command);
}
function handleKeyup(e) {
const command = commandMap[e.code];
// TODO: 移除指令
removeFromMoveCommands(command);
}
function findUpOrDownCommand() {
// return moveCommands.find((item) => item.id === 1 || item.id === 2);
return moveCommands.find((item) => item.type === commandType.upOrDown);
}
function findLeftOrRightCommand() {
// return moveCommands.find((item) => item.id === 3 || item.id === 4);
return moveCommands.find((item) => item.type === commandType.leftOrRight);
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
// 开始找指令,因为down和up是对立的,left和right是对立的 => 所以只要找到moveCommands中靠前的up或者down,left或者right
const upOrDownCommand = findUpOrDownCommand();
const leftOrRightCommand = findLeftOrRightCommand();
if (upAndDownCommand && leftOrRightCommand) {
// TODO: 两个方向
} else if (upOrDownCommand) {
// TODO: y 方向
} else if (leftOrRightCommand) {
// TODO: x 方向
}
handleTicker();
});
}
handleTicker();
- 测试一下
// 用 moveCommands 来存储
const moveCommands = [];
const commandType = {
upOrDown: "upOrDown",
leftOrRight: "leftOrRight"
};
const downCommand = {
type: commandType.upOrDown,
id: 1, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const upCommand = {
type: commandType.upOrDown,
id: 2, //唯一标识
dir: -1 // 方向: 1 表示正方向, -1 表示负方向
};
const rightCommand = {
type: commandType.leftOrRight,
id: 3, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
const leftCommand = {
type: commandType.leftOrRight,
id: 4, //唯一标识
dir: 1 // 方向: 1 表示正方向, -1 表示负方向
};
// 创造映射,方便找到
const commandMap = {
ArrowDown: downCommand,
ArrowUp: upCommand,
ArrowLeft: leftCommand,
ArrowRight: rightCommand
};
function hasExistCommand(command) {
return moveCommands.findIndex((item) => item.id === command.id) !== -1;
}
function removeFromMoveCommands(command) {
const index = moveCommands.findIndex((item) => item.id === command.id);
if (index !== -1) {
moveCommand.splice(index, 1);
}
}
function handleKeydown(e) {
// TODO: 添加指令
const command = commandMap[e.code];
// 如果已经在moveCommands中直接return
if (command && hasExistCommand(command)) return;
// 如果不存在就放在第一位,因为最新的优先级最高
moveCommands.unshift(command);
}
function handleKeyup(e) {
const command = commandMap[e.code];
// TODO: 移除指令
removeFromMoveCommands(command);
}
function findUpOrDownCommand() {
// return moveCommands.find((item) => item.id === 1 || item.id === 2);
return moveCommands.find((item) => item.type === commandType.upOrDown);
}
function findLeftOrRightCommand() {
// return moveCommands.find((item) => item.id === 3 || item.id === 4);
return moveCommands.find((item) => item.type === commandType.leftOrRight);
}
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
const app = document.querySelector(".app");
function setAppPosition(y, x) {
const _x = Number(getComputedStyle(app).left.split("p")[0]);
const _y = Number(getComputedStyle(app).top.split("p")[0]);
app.style.left = `${_x + x}px`;
app.style.top = `${_y + y}px`;
}
function handleTicker() {
window.requestAnimationFrame(() => {
// TODO
// 开始找指令,因为down和up是对立的,left和right是对立的 => 所以只要找到moveCommands中靠前的up或者down,left或者right
const upOrDownCommand = findUpOrDownCommand();
const leftOrRightCommand = findLeftOrRightCommand();
if (upAndDownCommand && leftOrRightCommand) {
// TODO: 两个方向
setAppPosition(upAndDownCommand.dir * 5, leftAndRightCommand.dir * 5);
} else if (upOrDownCommand) {
// TODO: y 方向
setAppPosition(upAndDownCommand.dir * 5, 0);
} else if (leftOrRightCommand) {
// TODO: x 方向
setAppPosition(0, leftAndRightCommand.dir * 5);
}
handleTicker();
});
}
handleTicker();
-
Game Over- 怎么存指令?
- 怎么找指令?