目录
倒计时的实现
找不同实现
对canvas的初始化
实现画布的分割, 父盒子>子盒子>canvas
找不同逻辑的判断
实现图片的导入
DOM节点的监听
声明两个需要监听的节点
禁用页面点击事件的函数
DOM监听
实现勋章分数增加和生命的减少
减命逻辑
死亡响应
websocket交互
定义给后端发送的数据包
断线重连
最后,调用封装好的websocket创建
初始之物,其行必丑
代码写的非常繁琐,有很多细节在复盘的时候也会想不起来为什么会这样写,今天算是梳理一遍所有的代码,时隔多日又写一篇还是很激动的
此小游戏由两位前端,和一位后端共同完成,我主要说明我写的部分(初学者,还是很水的)
完整源码:khatung/find difference - Gitee.com
效果实现图如下:
setTimeout(function () {
let fill = document.querySelector('.fill');,这个就是进度条盒子(div),减少div长度达到进度条递减的效果
console.log(fill);
document.querySelector('.overlay').style.display = 'none'
//执行回调函数
var timer = setInterval(function () {
// 检测网络连接状态
if (!navigator.onLine) {
// clearInterval(timer); // 停止倒计时
document.addEventListener('click', disableClickEvent, true);
alertT.style.display = 'block';
} else {
alertT.style.display = 'none';
}
if (countdown == 0) {
clearInterval(timer);
// alert('加载完成');
data.type = "endGame"
data.data.time = 0
// data.time = 0;
ws.send(JSON.stringify(data))
return;
} else {
countdown--; // 每秒减少计时器的值
fill.style.width = (parseFloat(fill.style.width) - 1.2) + 'vw'; // 将宽度属性转换为数值类型并递减
j.innerHTML = countdown + "s";
}
}, 1000);
}, 0);
};
我最开始不相信这么麻烦,尝试了很多别的方法都失败了,最后还是用canvas画布实现吧
function createCanvasPiece(img, containerId) {
let container = document.getElementById(containerId);
//创建一个父盒子
let canvasPieceContainer = document.createElement('div');
canvasPieceContainer.classList.add('canvasPiece');
for (let row = 0; row < 6; row++) {
for (let col = 0; col < 6; col++) {
// 创建一个新的画布元素
let canvas = document.createElement('canvas');
let divBox = document.createElement('div'); // 创建 div 盒子
divBox.className = `canvas_${row}_${col}`; // 设置盒子的类名
// canvas.width = 80; // 设置画布宽度为100像素
// canvas.height = 50; // 设置画布高度为100像素
let ctx = canvas.getContext('2d'); // 获取画布的2D上下文
// 在画布上绘制图像的一部分
ctx.drawImage(
img, // 图像对象
col * img.width / 6, // 源图像的起始水平位置
row * img.height / 6, // 源图像的起始垂直位置
img.width / 6, // 源图像的宽度
img.height / 6, // 源图像的高度
0, // 目标图像的起始水平位置
0, // 目标图像的起始垂直位置
300, // 目标图像的宽度290
150 // 目标图像的高度180
);
// 添加类名到画布容器元素
// 将画布元素添加到画布容器元素
divBox.appendChild(canvas);
// 将画布容器元素添加到父容器中
canvasPieceContainer.appendChild(divBox);
}
}
container.appendChild(canvasPieceContainer); // let huoq = document.querySelector('.canvasPiece');
}
对所有子盒子取类名,根据类名来判断哪些是不同的盒子(一共八处不同,当然为了更加精确可以继续细分canvas)
//添加事件监听
//标记八个盒子,禁止重复生成
var boxCreated1 = false; // 用于标记盒子是否已经创建过
var boxCreated2 = false; // 用于标记盒子是否已经创建过
var boxCreated3 = false; // 用于标记盒子是否已经创建过
var boxCreated4 = false; // 用于标记盒子是否已经创建过
var boxCreated5 = false; // 用于标记盒子是否已经创建过
var boxCreated6 = false; // 用于标记盒子是否已经创建过
var boxCreated7 = false; // 用于标记盒子是否已经创建过
var boxCreated8 = false; // 用于标记盒子是否已经创建过
function ab() {
let playerDiv = document.querySelector('.canvasPiece');
console.log(playerDiv)
//鼠标监听事件
function handleClickone(event) {
//获取到canvas
let clickedElement = event.target;
// console.log(clickedElement + "1111111111111111111");
//获取到canvas的父级
let divBox = clickedElement.parentNode;
console.log(divBox.className)
console.log(1111)
// 检查是否为特定子盒子元素
if (divBox.className == 'canvas_0_1') {
if (!boxCreated1) {
let newBox = createBox(-4, 0);
divBox.appendChild(newBox);
boxCreated1 = true
count++;
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_0_2') {
if (!boxCreated2) {
let newBox = createBox(3, 3);
divBox.appendChild(newBox);
boxCreated2 = true
count++;
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_1_5') {
if (!boxCreated3) {
let newBox = createBox(0, -4);
divBox.appendChild(newBox);
boxCreated3 = true
count++;
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_2_1') {
if (!boxCreated4) {
let newBox = createBox(-2.5, -3);
divBox.appendChild(newBox);
boxCreated4 = true
count++;
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_2_3') {
if (!boxCreated5) {
let newBox = createBox(-6, -1);
divBox.appendChild(newBox);
boxCreated5 = true
count++;
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_2_2') {
if (!boxCreated5) {
let newBox = createBox(7, -2);
divBox.appendChild(newBox);
boxCreated5 = true
count++;
// console.log(count)
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_3_4') {
if (!boxCreated6) {
let newBox = createBox(0, 0);
divBox.appendChild(newBox);
boxCreated6 = true
count++;
// console.log(count)
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_5_3') {
if (!boxCreated7) {
let newBox = createBox(-4, -5);
divBox.appendChild(newBox);
boxCreated7 = true
count++;
// console.log(count)
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_4_3') {
if (!boxCreated7) {
let newBox = createBox(-4, 4);
divBox.appendChild(newBox);
boxCreated7 = true
count++;
// console.log(count)
//调用添加标签函数
addImageToLi(liElements2)
}
} else if (divBox.className == 'canvas_4_1') {
if (!boxCreated8) {
let newBox = createBox(2, -2);
divBox.appendChild(newBox);
boxCreated8 = true
count++;
// console.log(count)
//调用添加标签函数
addImageToLi(liElements2)
}
} else {
var firstLi = HP.querySelector('li:first-child');
if (firstLi) {
HP.removeChild(firstLi);
}
}
}
// let playerDiv = document.querySelector('.canvasPiece');
playerDiv.addEventListener('click', handleClickone);
}
因为只在右侧图片也就是img2上实现点击不同,所以需要先加载出img2,防止ab(); 方法被img1调用,导致显示在img上
//初始化图片一
function initializeCanvasPieces() {
//必须先执行这个
let img2 = new Image();
img2.src = 'images/Gallery/greenHouse2.png';
img2.onload = function () {
console.log(111);
createCanvasPiece(img2, 'dao2');
ab();
let img1 = new Image();
img1.src = 'images/Gallery/greenHouse1.png';
img1.onload = function () {
createCanvasPiece(img1, 'dao1');
};
};
}
实现页面随机生成不同组的图片
//随机加载图片
window.addEventListener('load', function () {
//根据当前分钟数来随机页面
// 生成一个1到2之间的随机数
var date = new Date();
var minutes = date.getMinutes();
var random = minutes % 2; // 取分钟数的余数,结果为0或1
if (random === 1) {
initializeCanvasPieces(); // 加载方法1
} else {
// 加载另一个方法
initializeCanvasPieces_2(); //加载方法2
}
});
当增加分数以后就得一分,添加一个盒子(勋章)
具体实现如下:
//命
const targetUL1 = document.querySelector('.three ul');
let liCount = targetUL1.querySelectorAll('li').length;;
//勋章
const targetUL2 = document.querySelector('.center ul');
let imgCount = targetUL2.querySelectorAll('li img').length;
这个函数调用在下面MutationObserver 中
function disableClickEvent(event) {
event.stopPropagation();
event.preventDefault();
}
创建了一个 MutationObserver 实例,并传入一个回调函数作为参数。当被观察的 DOM 节点发生变化时,MutationObserver 将调用这个回调函数,并传入一个 mutations 数组作为参数,该数组包含了所有发生的变化。
在回调函数中,通过遍历 mutations 数组的每个 mutation 对象,可以获取有关 DOM 变化的详细信息。
// 创建 MutationObserver 实例,监听删除
const observer1 = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.removedNodes) {
liCount = targetUL1.querySelectorAll('li').length;
if (liCount === 0) {
die.style.display = 'block'
c = 22
document.addEventListener('click', disableClickEvent, true);
}
}
});
});
// 配置观察选项,并开始观察目标 UL 元素的子节点变化
const config1 = { childList: true };
observer1.observe(targetUL1, config1);
// 创建 MutationObserver 实例,监听删除
const observer2 = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!mutation) {
imgCount = 0
}
//结点的增加或删除
if (mutation.addedNodes || mutation.removedNodes) {
imgCount = targetUL2.querySelectorAll('li img').length;
//分数增加,给后端发数据包
console.log("分数增加")
// 每更新一次就给后端发一次数据
setLiCount();
// console.log(imgCount+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
//假如是最后一次,一个页面没有响应,此时,该页面还是最开始给后端发送的数据,此时后端知道了有一方完成了任务
if (imgCount === 8) {
data.type = "endGame"
ws.send(JSON.stringify(data))
// 跳转页面,存储数据到本地
console.log(data)
}
}
});
});
// 配置观察选项,并开始观察目标 UL 元素的子节点变化
const config2 = { childList: true, subtree: true };
observer2.observe(targetUL2, config2);
//声明勋章,往盒子中添加勋章
var ulElement2 = document.querySelector('.own ul')
//获取li
var liElements2 = ulElement2.querySelectorAll('li');
//生命
var HP = document.querySelector('.three ul');
//创建添加img标签函数
function addImageToLi(liElements) {
var firstLiWithoutImage = null;
for (var i = 0; i < liElements.length; i++) {
var liElement = liElements[i];
var existingImage = liElement.querySelector('img');
if (!existingImage) {
firstLiWithoutImage = liElement;
break; // 找到第一个没有
标签的 元素后跳出循环
}
}
if (firstLiWithoutImage) {
var imgElement = document.createElement('img');
imgElement.src = './images/medal.png'; // 替换为你的勋章图片的 URL
firstLiWithoutImage.appendChild(imgElement);
}
}
//创建盒子
function createBox(leftPosition, topPosition) {
var box = document.createElement('div');
box.className = 'game-box';
box.style.position = 'absolute';
box.style.left = leftPosition + 'vh';
box.style.top = topPosition + 'vh';
return box;
}
这个相当于添加一个死亡提示,当死亡以后此时出现游戏失败弹窗(div盒子),无法对页面进行任何操作(与上面的禁用页面点击事件一起实现效果),等待倒计时结束,双方同时跳转页面,并结算分数
const die = document.querySelector('.die')
die.addEventListener('click',function() {
die.style.display = 'none'
})
var data = {
type: "gameProgress",
data: {
username: found.data.username,
userGender: found.data.userGender,
uuid: found.data.uuid,
score: imgCount, //得分
time: countdown, //倒计时
userIcon: found.data.userIcon,
roomId: found.data.roomId || ""
}
};
// 自定义 setter 函数,在 liCount 变量的值改变时发送数据给后端
function setLiCount() {
//获取本地存储
data = {
type: "gameProgress",
data: {
username: found.data.username,
userGender: found.data.userGender,
uuid: found.data.uuid,
score: imgCount, //得分
time: countdown, //倒计时
userIcon: found.data.userIcon,
roomId: found.data.roomId || ""
}
};
定义临时存储
一般地,当我修改数据了以后,就将数据存储到本地存储,再将本地存储中的数据发送给后端,这样不容易混乱
let found = JSON.parse(sessionStorage.getItem('data'));
创建websocket连接
//创建连接
function createWebSocket(url) {
try {
ws = new WebSocket(url);
initEventHandle();
} catch (e) {
reconnect(url);
}
}
//连接
function initEventHandle() {
// 监听 WebSocket 连接关闭事件
ws.onclose = function () {
console.log("WebSocket连接已关闭");
// 停止倒计时
// clearInterval(timer);
//存储data数据包(只需要分数即可)
sessionStorage.setItem("data", JSON.stringify(data))
reconnect(wsUrl);
};
//连接异常 - 重连
ws.onerror = function (event) {
console.log('onerror:' + event.data);
reconnect(wsUrl);
};
// 建立连接
ws.onopen = function () {
// WebSocket 连接已建立
console.log('链接已建立')
// 获取存储在 sessionStorage 中的数据
var gai = JSON.parse(sessionStorage.getItem('data'));
// 修改数据
gai.type = "updateConn";
console.log(gai)
// 将修改后的数据存储回 sessionStorage 中
sessionStorage.setItem('data', JSON.stringify(gai));
ws.send(sessionStorage.getItem('data'))
//如果是重新连接
// if(){
// ws.send(sessionStorage.getItem('data'))
// }
//保持连接:每5秒发送一个消息
//发送心跳检测前判断链接状态
setInterval(function () {
ws.send(JSON.stringify({
"type": "check",
"data": {
}
}));
}, 5000);
// 监听后端发送的消息,只有第一次和最后一次
ws.onmessage = function (event) {
var receivedData = event.data;
console.log("接收到后端发送的数据:", JSON.parse(receivedData));
const real = JSON.parse(receivedData)
if (real == "check") {
console.log("连上了,哥")
// 启用页面点击事件
if(c == 11){
//如果没死还能点
document.removeEventListener('click', disableClickEvent, true);
}
} else {
// 将两方最后的数据转换为 JSON 字符串并存储到 sessionStorage
//更新时间
data.data.time = countdown
var jsonData = JSON.stringify(data);
sessionStorage.setItem("data", jsonData);
console.log(jsonData)
if (real.type == "endGame") {
//本地存储json字符串
console.log("213333333333333333333333333333")
//接受对方的数据
if (real.code == "200") {
//本地存储,发送数据包
// 跳转页面
ws.send(sessionStorage.getItem('data'))
window.location.href = '../doubleEnd/end2.html'; // 修改为你要跳转的页面的 URL
}
}
//从后端获取信息,渲染数据
let roomId = real.data.roomId;
let username = real.data.username;
let score = real.data.score;
console.log(score)
console.log("roomId", roomId);
if (enemy) {
enemy.innerHTML = `敌方得分:${score}`; //渲染敌方分数
//如果增加分数肯定调用增加盒子函数
if(score ==1 && !boxCreat1){
addImageToLi(liElements3);
boxCreat1 =true
}else if(score ==2 && !boxCreat2){
addImageToLi(liElements3);
boxCreat2 =true
} else if(score ==3 && !boxCreat3){
addImageToLi(liElements3);
boxCreat3 =true
} else if(score ==4 && !boxCreat4){
addImageToLi(liElements3);
boxCreat4 =true
} else if(score ==5 && !boxCreat5){
addImageToLi(liElements3);
boxCreat5 =true
} else if(score ==6 && !boxCreat6){
addImageToLi(liElements3);
boxCreat6 =true
} else if(score ==7 && !boxCreat7){
addImageToLi(liElements3);
boxCreat7 =true
} else if(score ==8 && !boxCreat8){
addImageToLi(liElements3);
boxCreat8 =true
}
}
console.log(score + "========")
console.log(real + "hhhhhhhhhhhhhh");
}
};
}
//重连操作
function reconnect(url) {
if (lockReconnect) {
return;
}
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 3000); //5秒后重连
}
//调用websocket
createWebSocket(wsUrl);