## 优化键盘监听事件

574 阅读6分钟

最近在工作中有使用到键盘的监听事件,发现存在以下问题:

  1. 键盘事件不连续,即你每次都需要按下一次,然后抬起来,然后再按下去,如果一直按着不动,那么这个键盘事件也只执行一次
  2. 如果,我现在按下了向左的键盘,如果再继续按下向右的键盘,那么在我向右的键盘抬起的时候,我希望还可以触发向左的键盘事件

1. 问题分析

  1. addEventListener监听的是keydown或者keyup,字面意思可以看得出来,就是键盘按下,或者抬起的时候,和一直按压没有关系
  2. 根据keydown或者keyup,在你按下向左的键盘之后,他会触发keydown,然后你按下向右的键盘,他会触发向右按键的keydown,当你向右的按键抬起的时候,会触发向右按键的keyup,然后你再抬起向左的按键,就会再次触发keyup,和你长按没关系,也不会记录你那个按键没有抬起来。

2. 解决思路

  1. 浏览器不会帮我们存住我们哪些已经按下了,之后在你相应的操作的时候触发响应的方法,那么就需要我们把按键来存储一下
  2. 我们只是存住了,我们怎么知道还有哪些没有执行的,只有再搞一个帧循环,来看看还有哪些没有执行。

3. 代码环节

  1. moveCommands来存储已经按下的指令
// 用 moveCommands 来存储
const moveCommands = [];
  1. 实现帧循环handleTicker
// 用 moveCommands 来存储
const moveCommands = [];
function handleTicker() {
	window.requestAnimationFrame(() => {
		// TODO
		handleTicker();
	});
}
handleTicker();
  1. 实现键盘按下,抬起事件
    1. 键盘按下将按下的按键操作的指令放倒moveCommands
    2. 键盘抬起的时候,将抬起的按键从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);
  1. 构造指令: 将左右上下的按键封装成响应的对象
// 用 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);
  1. 开始进行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);
  1. 帧循环开始找此时应该执行的指令
// 用 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();
  1. 优化一下:因为 updownleftright 是对立的,所以在findUpOrDownCommandfindLeftOrRightCommand的时候取消id的判断,相反的给updown加上相同的属性,给leftright加上相同的属性
// 用 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();
  1. 测试一下
// 用 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();
  1. Game Over

    1. 怎么存指令?
    2. 怎么找指令?