跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python

网页版井字棋的前端实现

网页版井字棋的前端实现 介绍如何使用 HTML、CSS 和 JavaScript 实现一个网页版井字棋游戏。 前置知识 在开始之前,请确保掌握以下基础知识: **HTML**: 基础标签使用,结构搭建。 **CSS**: 通用样式重置,Flexbox 布局,Grid 布局,背景图像处理,伪元素,动画与过渡效果。 **JavaScript**: DOM 操作,类操作,事件监听,回调函数,条件判断…

独立开发者发布于 2026/4/6更新于 2026/5/2142K 浏览
网页版井字棋的前端实现

网页版井字棋的前端实现

本文介绍如何使用 HTML、CSS 和 JavaScript 实现一个网页版井字棋游戏。

前置知识

在开始之前,请确保掌握以下基础知识:

  • HTML: 基础标签使用,结构搭建。
  • CSS: 通用样式重置,Flexbox 布局,Grid 布局,背景图像处理,伪元素,动画与过渡效果。
  • JavaScript: DOM 操作,类操作,事件监听,回调函数,条件判断与数组方法。

1. HTML 骨架

首先搭建 HTML 骨架。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网页版井字棋</title>
    <link rel="stylesheet" href="./index.css">
    <script defer src="./index.js"></script>
</head>
<body>
    <div id="board" class="wrapper">
        <div id="currentStatus" class="current-status">
            <img id="currentBeastImg" src="./1.gif" alt="unicorn">
            <p>&nbsp; 's turn</p>
        </div>
        <div class="board">
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
        </div>
        <div id="gameEndOverlay" class="game-end-overlay">
            <div data-winning-message>
                <p></p>
            </div>
            <div class="btn-container">
                <button id="resetButton">play again</button>
            </div>
        </div>
    </div>
</body>
</html>

2. CSS 装饰

1. 引入字体和全局样式

@import url("https://fonts.googleapis.com/css2?family=Bungee+Inline&display=swap");
* {
    padding: 0;
    margin: 0;
    box-sizing: inherit;
}

2. 设置 body 样式

body {
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    text-align: center;
    font-family: "Bungee Inline", cursive;
    color: #f5f5f5;
    overflow: hidden;
    background-image: linear-gradient(to top, #a8edea 0%, #ffc7d9 100%);
}

3. 设置 .wrapper 样式

.wrapper {
    background-color: #55acee53;
    padding: 50px;
}

4. 设置 .current-status 样式

.current-status {
    display: flex;
    justify-content: center;
    align-items: center;
    margin-bottom: 25px;
}
.current-status p {
    margin: 0 5px 0 0;
    font-size: 24px;
}
.current-status img {
    width: auto;
    height: 32px;
}

5. 设置 board 和 .cell 样式

.board {
    display: grid;
    grid-template-columns: repeat(3, minmax(90px, 1fr));
    grid-template-rows: repeat(3, minmax(90px, 1fr));
    grid-gap: 12px;
    width: 100%;
    height: 100%;
    max-width: 495px;
    margin: 0 auto 15px;
}
.cell {
    cursor: pointer;
    position: relative;
    background-color: #f5f5f5;
    width: 90px;
    height: 90px;
    opacity: 0.5;
    transition: opacity 0.2s ease-in-out;
}
.cell:hover {
    opacity: 1;
}

6. 鼠标悬浮时的图片效果

.board.unicorn .cell:not(.dragon):not(.unicorn):hover::before,
.board.dragon .cell:not(.dragon):not(.unicorn):hover::before {
    content: "";
    width: 70%;
    height: 70%;
    display: block;
    position: absolute;
    background-repeat: no-repeat;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%, -50%, 0);
    background-size: contain;
    opacity: 50%;
}
.board.unicorn .cell:not(.dragon):hover::before {
    background-image: url("./1.gif");
}
.board.dragon .cell:not(.unicorn):hover::before {
    background-image: url("./2.gif");
}

7. 设置 game-end-overlay 样式

.game-end-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #0d1021;
}
.game-end-overlay.show {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

8. 设置 .winning-message 样式

.winning-message {
    margin: -50px 0 20px;
}
.winning-message img {
    width: 100px;
}
.winning-message p {
    font-size: 48px;
    margin: 0;
}

9. 重启按钮样式

.reset-button {
    color: #f5f5f5;
    font-family: "Bungee Inline", cursive;
    font-size: 30px;
    white-space: nowrap;
    border: none;
    padding: 10px 20px;
    background-color: #a186be;
    box-shadow: 5px 5px 0 #55acee;
    cursor: pointer;
    transition: transform 0.1s ease-in-out;
    position: relative;
}
.reset-button:hover {
    transform: scale(1.2);
}
.reset-button:active {
    top: 6px;
    left: 6px;
    box-shadow: none;
    background-color: #9475b5;
}

3. JavaScript 交互

1. 获取页面元素

const board = document.getElementById('board');
const cells = document.querySelectorAll('[data-cell]');
const currentStatus = document.getElementById('currentStatus');
const resetButton = document.getElementById('resetButton');
const gameEndOverlay = document.getElementById('gameEndOverlay');
const currentBeastStatusImg = document.getElementById('currentBeastImg');
const winningMessage = document.querySelector('[data-winning-message]');
const winningMessageText = document.querySelector('[data-winning-message] p');
const winningMessageImg = document.createElement('img');

2. 初始化游戏状态

let gameIsLive = true;
let unicornTurn = true;
let winner = null;

3. 所有获胜组合

const winningCombinations = [
    [0, 1, 2], [3, 4, 5], [6, 7, 8],
    [0, 3, 6], [1, 4, 7], [2, 5, 8],
    [0, 4, 8], [2, 4, 6]
];

4. 设置鼠标悬停时的样式

const setBoardHoverClass = () => {
    board.classList.remove('unicorn');
    board.classList.remove('dragon');
    if (unicornTurn) {
        board.classList.add('unicorn');
    } else {
        board.classList.add('dragon');
    }
}

5. 在格子上放置图片

const placeBeastImg = (cell, currentBeast) => {
    cell.classList.add(currentBeast);
}

6. 切换回合

const swapTurns = () => {
    unicornTurn = !unicornTurn;
}

7. 更新当前状态

const updateCurrentStatus = () => {
    if (unicornTurn) {
        currentBeastStatusImg.src = './1.gif';
        currentBeastStatusImg.alt = 'unicorn';
    } else {
        currentBeastStatusImg.src = './2.gif';
        currentBeastStatusImg.alt = 'dragon';
    }
}

8. 检查是否获胜

const checkWin = (currentBeast) => {
    return winningCombinations.some(combination => {
        return combination.every(i => {
            return cells[i].classList.contains(currentBeast);
        });
    });
}

9. 判断是否平局

const isDraw = () => {
    return [...cells].every(cell => {
        return cell.classList.contains('unicorn') || cell.classList.contains('dragon');
    });
}

10. 开始游戏

const startGame = () => {
    cells.forEach(cell => {
        winningMessageImg.remove();
        cell.classList.remove('unicorn', 'dragon');
        cell.removeEventListener('click', handleCellClick);
        cell.addEventListener('click', handleCellClick, { once: true });
    });
    setBoardHoverClass();
    gameEndOverlay.classList.remove('show');
}

11. 结束游戏

const endGame = (draw) => {
    if (draw) {
        winningMessageText.innerText = `draw!`;
    } else {
        winningMessageImg.src = unicornTurn ? './1.gif' : './2.gif';
        winningMessageImg.alt = unicornTurn ? 'unicorn' : 'dragon';
        winningMessage.insertBefore(winningMessageImg, winningMessageText);
        winningMessageText.innerText = `wins!!!`;
    }
    gameEndOverlay.classList.add('show');
}

12. 处理格子点击事件

const handleCellClick = (e) => {
    const cell = e.target;
    const currentBeast = unicornTurn ? 'unicorn' : 'dragon';
    placeBeastImg(cell, currentBeast);
    if (checkWin(currentBeast)) {
        endGame(false);
    } else if (isDraw()) {
        endGame(true);
    } else {
        swapTurns();
        updateCurrentStatus();
        setBoardHoverClass();
    }
}

13. 重置游戏与启动

resetButton.addEventListener('click', startGame);
startGame();

完整代码

以下是整合后的完整 HTML 文件代码,可直接保存为 .html 文件运行。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网页版井字棋</title>
    <style>
        @import url("https://fonts.googleapis.com/css2?family=Bungee+Inline&display=swap");
        * { padding: 0; margin: 0; box-sizing: inherit; }
        body { margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; height: 100vh; text-align: center; font-family: "Bungee Inline", cursive; color: #f5f5f5; overflow: hidden; background-image: linear-gradient(to top, #a8edea 0%, #ffc7d9 100%); }
        .wrapper { background-color: #55acee53; padding: 50px; }
        .current-status { display: flex; justify-content: center; align-items: center; margin-bottom: 25px; }
        .current-status p { margin: 0 5px 0 0; font-size: 24px; }
        .current-status img { width: auto; height: 32px; }
        .board { display: grid; grid-template-columns: repeat(3, minmax(90px, 1fr)); grid-template-rows: repeat(3, minmax(90px, 1fr)); grid-gap: 12px; width: 100%; height: 100%; max-width: 495px; margin: 0 auto 15px; }
        .board.unicorn .cell:not(.dragon):not(.unicorn):hover::before, .board.dragon .cell:not(.dragon):not(.unicorn):hover::before { content: ""; width: 70%; height: 70%; display: block; position: absolute; background-repeat: no-repeat; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); background-size: contain; opacity: 50%; }
        .board.unicorn .cell:not(.dragon):hover::before { background-image: url("./1.gif"); }
        .board.dragon .cell:not(.unicorn):hover::before { background-image: url("./2.gif"); }
        .cell { cursor: pointer; position: relative; background-color: #f5f5f5; width: 90px; height: 90px; opacity: 0.5; transition: opacity 0.2s ease-in-out; }
        .cell:hover { opacity: 1; }
        .cell.dragon, .cell.unicorn { opacity: 1; position: relative; cursor: not-allowed; }
        .cell.dragon::before, .cell.unicorn::before { content: ""; width: 70%; height: 70%; display: block; position: absolute; background-repeat: no-repeat; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); background-size: contain; }
        .cell.dragon::before { background-image: url("./2.gif"); }
        .cell.unicorn::before { background-image: url("./1.gif"); }
        .game-end-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: #0d1021; }
        .game-end-overlay.show { display: flex; flex-direction: column; justify-content: center; align-items: center; }
        .winning-message { margin: -50px 0 20px; }
        .winning-message img { width: 100px; }
        .winning-message p { font-size: 48px; margin: 0; }
        .btn-container { position: relative; }
        .reset-button { color: #f5f5f5; font-family: "Bungee Inline", cursive; font-size: 30px; white-space: nowrap; border: none; padding: 10px 20px; background-color: #a186be; box-shadow: 5px 5px 0 #55acee; cursor: pointer; transition: transform 0.1s ease-in-out; position: relative; }
        .reset-button:hover { transform: scale(1.2); }
        .reset-button:active { top: 6px; left: 6px; box-shadow: none; background-color: #9475b5; }
    </style>
</head>
<body>
    <div id="board" class="wrapper">
        <div id="currentStatus" class="current-status">
            <img id="currentBeastImg" src="./1.gif" alt="unicorn">
            <p>&nbsp; 's turn</p>
        </div>
        <div class="board">
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
            <div data-cell></div>
        </div>
        <div id="gameEndOverlay" class="game-end-overlay">
            <div data-winning-message>
                <p></p>
            </div>
            <div class="btn-container">
                <button id="resetButton">play again</button>
            </div>
        </div>
    </div>
    <script>
        const board = document.getElementById('board');
        const cells = document.querySelectorAll('[data-cell]');
        const currentStatus = document.getElementById('currentStatus');
        const resetButton = document.getElementById('resetButton');
        const gameEndOverlay = document.getElementById('gameEndOverlay');
        const currentBeastStatusImg = document.getElementById('currentBeastImg');
        const winningMessage = document.querySelector('[data-winning-message]');
        const winningMessageText = document.querySelector('[data-winning-message] p');
        const winningMessageImg = document.createElement('img');

        let gameIsLive = true;
        let unicornTurn = true;
        let winner = null;

        const winningCombinations = [
            [0, 1, 2], [3, 4, 5], [6, 7, 8],
            [0, 3, 6], [1, 4, 7], [2, 5, 8],
            [0, 4, 8], [2, 4, 6]
        ];

        const setBoardHoverClass = () => {
            board.classList.remove('unicorn');
            board.classList.remove('dragon');
            if (unicornTurn) {
                board.classList.add('unicorn');
            } else {
                board.classList.add('dragon');
            }
        }

        const placeBeastImg = (cell, currentBeast) => {
            cell.classList.add(currentBeast);
        }

        const swapTurns = () => {
            unicornTurn = !unicornTurn;
        }

        const updateCurrentStatus = () => {
            if (unicornTurn) {
                currentBeastStatusImg.src = './1.gif';
                currentBeastStatusImg.alt = 'unicorn';
            } else {
                currentBeastStatusImg.src = './2.gif';
                currentBeastStatusImg.alt = 'dragon';
            }
        }

        const checkWin = (currentBeast) => {
            return winningCombinations.some(combination => {
                return combination.every(i => {
                    return cells[i].classList.contains(currentBeast);
                });
            });
        }

        const isDraw = () => {
            return [...cells].every(cell => {
                return cell.classList.contains('unicorn') || cell.classList.contains('dragon');
            });
        }

        const startGame = () => {
            cells.forEach(cell => {
                winningMessageImg.remove();
                cell.classList.remove('unicorn', 'dragon');
                cell.removeEventListener('click', handleCellClick);
                cell.addEventListener('click', handleCellClick, { once: true });
            });
            setBoardHoverClass();
            gameEndOverlay.classList.remove('show');
        }

        const endGame = (draw) => {
            if (draw) {
                winningMessageText.innerText = `draw!`;
            } else {
                winningMessageImg.src = unicornTurn ? './1.gif' : './2.gif';
                winningMessageImg.alt = unicornTurn ? 'unicorn' : 'dragon';
                winningMessage.insertBefore(winningMessageImg, winningMessageText);
                winningMessageText.innerText = `wins!!!`;
            }
            gameEndOverlay.classList.add('show');
        }

        const handleCellClick = (e) => {
            const cell = e.target;
            const currentBeast = unicornTurn ? 'unicorn' : 'dragon';
            placeBeastImg(cell, currentBeast);
            if (checkWin(currentBeast)) {
                endGame(false);
            } else if (isDraw()) {
                endGame(true);
            } else {
                swapTurns();
                updateCurrentStatus();
                setBoardHoverClass();
            }
        }

        resetButton.addEventListener('click', startGame);
        startGame();
    </script>
</body>
</html>

注意:运行前请确保 1.gif 和 2.gif 图片文件存在于同一目录下。

目录

  1. 网页版井字棋的前端实现
  2. 前置知识
  3. 1. HTML 骨架
  4. 2. CSS 装饰
  5. 1. 引入字体和全局样式
  6. 2. 设置 body 样式
  7. 3. 设置 .wrapper 样式
  8. 4. 设置 .current-status 样式
  9. 5. 设置 board 和 .cell 样式
  10. 6. 鼠标悬浮时的图片效果
  11. 7. 设置 game-end-overlay 样式
  12. 8. 设置 .winning-message 样式
  13. 9. 重启按钮样式
  14. 3. JavaScript 交互
  15. 1. 获取页面元素
  16. 2. 初始化游戏状态
  17. 3. 所有获胜组合
  18. 4. 设置鼠标悬停时的样式
  19. 5. 在格子上放置图片
  20. 6. 切换回合
  21. 7. 更新当前状态
  22. 8. 检查是否获胜
  23. 9. 判断是否平局
  24. 10. 开始游戏
  25. 11. 结束游戏
  26. 12. 处理格子点击事件
  27. 13. 重置游戏与启动
  28. 完整代码
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AIGC 重塑文学创作:机遇、挑战与应对
  • Linux 下 HTTP 会话机制实战:Cookie 与 Session 原理及 C++ 实现
  • RHEL 8 通过 yum 快速部署 OpenJDK 17 开发环境
  • 通过自适应屏蔽提示保护多模态大模型免受结构攻击
  • Python 内置函数 enumerate() 用法详解
  • xAI 计划推出独立应用,Grok 将直面 ChatGPT 竞争
  • ToDesk 集成 ToClaw:AI Agent 实现远程桌面自动化执行
  • OpenClaw 本地部署与配置实战指南
  • Ubuntu 环境下 JDK 1.8 环境变量配置指南
  • C++ spdlog 日志库编译与安装详解
  • Gemini 全能 QQ 机器人部署手册
  • Claude Code 本地环境配置与使用指南
  • PyQt5 入门教程:基础架构与常用控件详解
  • 使用 Langchain-Chatchat 构建本地专属 GPT 助手
  • GitHub Copilot AI 编程助手安装与使用指南
  • 利用 VibeThinker 自动生成 Git 提交记录
  • AI 安全研究:基于 PGD 的 Stable Diffusion 视觉提示词注入分析
  • bit7z:C++ 压缩解压缩库快速上手指南
  • ms-Mamba: 多尺度 Mamba 时间序列预测模型解析
  • Oracle 基础查询语句实战示例

相关免费在线工具

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online