CodeAE 发表于 2025-6-8 21:36:10

HTML在线打字练习源码

这是由吾爱论坛大神用html写的一个打字练习小网页
效果如下图:代码如下:<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>打字练习</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
    <script>
      tailwind.config = {
            theme: {
                extend: {
                  colors: {
                        primary: '#3B82F6',
                        secondary: '#F97316',
                        dark: '#1E293B',
                        light: '#F8FAFC',
                        accent: '#8B5CF6',
                        success: '#10B981',
                        danger: '#EF4444',
                        neutral: '#64748B'
                  },
                  fontFamily: {
                        inter: ['Inter', 'sans-serif'],
                  },
                }
            }
      }
    </script>
    <style type="text/tailwindcss">
      @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .typing-text-shadow {
                text-shadow: 0 0 8px rgba(59, 130, 246, 0.6);
            }
            .typing-container {
                background-image: radial-gradient(circle, rgba(59, 130, 246, 0.1) 0%, rgba(30, 41, 59, 0) 70%);
            }
            .key-press-animation {
                animation: keyPress 0.2s ease-out;
            }
            .char-correct {
                color: #10B981;
                text-decoration: underline;
                text-decoration-color: #10B981;
            }
            .char-incorrect {
                color: #EF4444;
                text-decoration: underline;
                text-decoration-color: #EF4444;
            }
            .bg-gradient-game {
                background: linear-gradient(135deg, #1E293B 0%, #0F172A 100%);
            }
            .typing-cursor {
                position: relative;
            }
            .typing-cursor::after {
                content: '';
                position: absolute;
                right: -2px;
                top: 0;
                height: 100%;
                width: 2px;
                background-color: #3B82F6;
                animation: blink 1s infinite;
            }
            .stat-card {
                transition: all 0.3s ease;
            }
            .stat-card:hover {
                transform: translateY(-5px);
                box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 10px 10px -5px rgba(59, 130, 246, 0.04);
            }
            .keyboard-row {
                display: flex;
                justify-content: center;
                margin-bottom: 0.375rem;
            }
            .key {
                display: flex;
                align-items: center;
                justify-content: center;
                border-radius: 0.375rem;
                transition: all 0.15s ease;
                user-select: none;
                transform-origin: center;
                position: relative;
                overflow: hidden;
            }
            .key::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0) 100%);
                pointer-events: none;
            }
            .typing-text-container {
                max-height: 8rem;
                overflow-y: auto;
                scrollbar-width: thin;
                scrollbar-color: #3B82F6 rgba(255, 255, 255, 0.1);
            }
            .typing-text-container::-webkit-scrollbar {
                width: 6px;
            }
            .typing-text-container::-webkit-scrollbar-thumb {
                background-color: #3B82F6;
                border-radius: 3px;
            }
            .typing-text-container::-webkit-scrollbar-track {
                background-color: rgba(255, 255, 255, 0.1);
            }
      }

      @keyframes keyPress {
            0% { transform: scale(1); box-shadow: 0 0 0 rgba(59, 130, 246, 0); }
            50% { transform: scale(0.95); box-shadow: 0 0 10px rgba(59, 130, 246, 0.5); }
            100% { transform: scale(1); box-shadow: 0 0 0 rgba(59, 130, 246, 0); }
      }

      @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
      }

      @keyframes pulseGlow {
            0% { box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); }
            50% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.8); }
            100% { box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); }
      }

      @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
      }

      .animate-fade-in {
            animation: fadeIn 0.5s ease-out forwards;
      }

      .animate-pulse-glow {
            animation: pulseGlow 2s infinite;
      }
    </style>
</head>
<body class="font-inter bg-gradient-game min-h-screen text-light flex flex-col">
    <!-- 导航栏 -->
    <nav class="bg-dark/80 backdrop-blur-md border-b border-primary/20 sticky top-0 z-50 transition-all duration-300">
      <div class="container mx-auto px-4 py-3 flex justify-between items-center">
            <div class="flex items-center space-x-2">
                <i class="fa-solid fa-keyboard text-primary text-2xl"></i>
                <h1 class="text-xl md:text-2xl font-bold bg-gradient-to-r from-primary to-blue-400 text-transparent bg-clip-text">
                  打字练习
                </h1>
            </div>
            <button class="md:hidden text-gray-300 hover:text-primary transition-colors duration-300">
                <i class="fa-solid fa-bars text-xl"></i>
            </button>
      </div>
    </nav>

    <!-- 主要内容区 -->
    <main class="flex-grow container mx-auto px-4 py-8">
      <!-- 游戏区域 -->
      <section class="max-w-4xl mx-auto bg-dark/60 backdrop-blur-sm rounded-2xl p-6 md:p-8 border border-primary/30 shadow-xl shadow-primary/10 mb-8 transform transition-all duration-500 hover:shadow-primary/20">
            <!-- 游戏状态信息 -->
            <div class="flex flex-wrap justify-between items-center mb-6 gap-4">
                <div class="flex items-center space-x-4">
                  <div class="stat-card bg-dark/80 rounded-xl p-3 border border-primary/20 flex items-center space-x-2 animate-fade-in" style="animation-delay: 0.1s">
                        <i class="fa-solid fa-clock text-primary text-xl"></i>
                        <div>
                            <p class="text-xs text-gray-400">剩余时间</p>
                            <p id="timer" class="text-2xl font-bold">60</p>
                        </div>
                  </div>
                  <div class="stat-card bg-dark/80 rounded-xl p-3 border border-primary/20 flex items-center space-x-2 animate-fade-in" style="animation-delay: 0.2s">
                        <i class="fa-solid fa-tachometer-alt text-primary text-xl"></i>
                        <div>
                            <p class="text-xs text-gray-400">当前速度</p>
                            <p id="wpm" class="text-2xl font-bold">0</p>
                            <p class="text-xs text-gray-400">WPM</p>
                        </div>
                  </div>
                </div>
                <div class="flex items-center space-x-4">
                  <div class="stat-card bg-dark/80 rounded-xl p-3 border border-primary/20 flex items-center space-x-2 animate-fade-in" style="animation-delay: 0.3s">
                        <i class="fa-solid fa-check-circle text-green-500 text-xl"></i>
                        <div>
                            <p class="text-xs text-gray-400">正确率</p>
                            <p id="accuracy" class="text-2xl font-bold">0%</p>
                        </div>
                  </div>
                  <button id="start-btn" class="bg-primary hover:bg-primary/90 text-white font-bold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center space-x-2 animate-fade-in animate-pulse-glow" style="animation-delay: 0.4s">
                        <i class="fa-solid fa-play"></i>
                        <span>开始游戏</span>
                  </button>
                  <!-- 添加刷新按钮 -->
                  <button id="refresh-btn" class="bg-primary hover:bg-primary/90 text-white font-bold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center space-x-2 animate-fade-in" style="animation-delay: 0.4s">
                        <i class="fa-solid fa-sync"></i>
                        <span>刷新文本</span>
                  </button>
                </div>
            </div>

            <!-- 打字区域 -->
            <div class="typing-container bg-dark/80 rounded-xl p-6 md:p-8 border border-primary/20 mb-6 animate-fade-in transform transition-all duration-300 hover:shadow-lg hover:shadow-primary/10" style="animation-delay: 0.5s">
                <div class="flex items-center justify-center mb-4">
                  <div class="w-24 h-1 bg-primary/30 rounded-full overflow-hidden">
                        <div id="progress-bar" class="h-full bg-primary" style="width: 0%"></div>
                  </div>
                </div>
                <!-- 滚动容器 -->
                <div id="typing-text-container" class="typing-text-container mb-4">
                  <p id="typing-text" class="text-2xl md:text-3xl font-medium leading-relaxed">
                        准备好了吗?点击开始按钮,然后开始输入显示的文本。尽量快速且准确地输入!
                  </p>
                </div>
                <div class="relative">
                  <input type="text" id="typing-input" class="w-full bg-dark/50 text-light text-xl md:text-2xl py-4 px-6 rounded-lg border border-primary/40 focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all duration-300 placeholder-gray-500" placeholder="开始输入..." disabled>
                  <div class="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-500">
                        <i id="caps-lock-indicator" class="fa-solid fa-lock-open hidden"></i>
                  </div>
                </div>
            </div>

            <!-- 键盘可视化 -->
            <div class="keyboard-container bg-dark/80 rounded-xl p-4 border border-primary/20 animate-fade-in transform transition-all duration-300 hover:shadow-lg hover:shadow-primary/10" style="animation-delay: 0.6s">
                <div id="keyboard" class="keyboard-wrapper w-full overflow-x-auto pb-2">
                  <!-- 键盘将由JS动态生成 -->
                </div>
            </div>
      </section>

      <!-- 游戏结果 -->
      <section id="results-section" class="max-w-4xl mx-auto bg-dark/60 backdrop-blur-sm rounded-2xl p-6 md:p-8 border border-primary/30 shadow-xl shadow-primary/10 mb-8 hidden transform transition-all duration-500 hover:shadow-primary/20">
            <h2 class="text-2xl md:text-3xl font-bold text-center mb-6 bg-gradient-to-r from-primary to-blue-400 text-transparent bg-clip-text">游戏结果</h2>
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div class="stat-card bg-dark/80 rounded-xl p-6 border border-primary/20 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-primary/20">
                  <div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
                        <i class="fa-solid fa-tachometer-alt text-primary text-3xl"></i>
                  </div>
                  <h3 class="text-lg font-medium text-gray-400 mb-1">最终速度</h3>
                  <p id="final-wpm" class="text-4xl font-bold">0 WPM</p>
                  <div class="mt-2 h-1 w-full bg-primary/20 rounded-full overflow-hidden">
                        <div class="final-wpm-progress h-full bg-primary" style="width: 0%"></div>
                  </div>
                </div>
                <div class="stat-card bg-dark/80 rounded-xl p-6 border border-primary/20 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-primary/20">
                  <div class="w-16 h-16 bg-green-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
                        <i class="fa-solid fa-check-circle text-green-500 text-3xl"></i>
                  </div>
                  <h3 class="text-lg font-medium text-gray-400 mb-1">正确率</h3>
                  <p id="final-accuracy" class="text-4xl font-bold">0%</p>
                  <div class="mt-2 h-1 w-full bg-green-500/20 rounded-full overflow-hidden">
                        <div class="final-accuracy-progress h-full bg-green-500" style="width: 0%"></div>
                  </div>
                </div>
                <div class="stat-card bg-dark/80 rounded-xl p-6 border border-primary/20 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-primary/20">
                  <div class="w-16 h-16 bg-secondary/10 rounded-full flex items-center justify-center mx-auto mb-4">
                        <i class="fa-solid fa-pencil-alt text-secondary text-3xl"></i>
                  </div>
                  <h3 class="text-lg font-medium text-gray-400 mb-1">总输入</h3>
                  <p id="total-characters" class="text-4xl font-bold">0 字符</p>
                  <div class="mt-2 h-1 w-full bg-secondary/20 rounded-full overflow-hidden">
                        <div class="final-chars-progress h-full bg-secondary" style="width: 0%"></div>
                  </div>
                </div>
            </div>
            <div class="mt-8 flex justify-center">
                <button id="play-again-btn" class="bg-primary hover:bg-primary/90 text-white font-bold py-3 px-8 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center space-x-2">
                  <i class="fa-solid fa-redo"></i>
                  <span>再玩一次</span>
                </button>
            </div>
      </section>

      <!-- 难度选择 -->
      <section class="max-w-4xl mx-auto bg-dark/60 backdrop-blur-sm rounded-2xl p-6 md:p-8 border border-primary/30 shadow-xl shadow-primary/10 mb-8 transform transition-all duration-500 hover:shadow-primary/20">
            <h2 class="text-xl md:text-2xl font-bold mb-4 bg-gradient-to-r from-primary to-blue-400 text-transparent bg-clip-text">难度设置</h2>
            <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                <div class="difficulty-option border-2 border-primary/30 rounded-xl p-4 cursor-pointer hover:border-primary transition-all duration-300 transform hover:scale-105 bg-dark/80" data-difficulty="easy">
                  <div class="flex items-center justify-between mb-2">
                        <h3 class="font-bold">简单</h3>
                        <span class="bg-green-500/20 text-green-400 text-xs px-2 py-1 rounded-full">初学者</span>
                  </div>
                  <p class="text-sm text-gray-400 mb-3">简短的句子,较慢的倒计时</p>
                  <div class="flex justify-between items-center">
                        <span class="text-xs text-gray-500">时间: 90秒</span>
                        <i class="fa-solid fa-check-circle text-green-500 opacity-0"></i>
                  </div>
                </div>
                <div class="difficulty-option border-2 border-primary/30 rounded-xl p-4 cursor-pointer hover:border-primary transition-all duration-300 transform hover:scale-105 bg-dark/80" data-difficulty="medium">
                  <div class="flex items-center justify-between mb-2">
                        <h3 class="font-bold">中等</h3>
                        <span class="bg-yellow-500/20 text-yellow-400 text-xs px-2 py-1 rounded-full">进阶者</span>
                  </div>
                  <p class="text-sm text-gray-400 mb-3">中等长度句子,标准倒计时</p>
                  <div class="flex justify-between items-center">
                        <span class="text-xs text-gray-500">时间: 60秒</span>
                        <i class="fa-solid fa-check-circle text-green-500 opacity-100"></i>
                  </div>
                </div>
                <div class="difficulty-option border-2 border-primary/30 rounded-xl p-4 cursor-pointer hover:border-primary transition-all duration-300 transform hover:scale-105 bg-dark/80" data-difficulty="hard">
                  <div class="flex items-center justify-between mb-2">
                        <h3 class="font-bold">困难</h3>
                        <span class="bg-red-500/20 text-red-400 text-xs px-2 py-1 rounded-full">专家</span>
                  </div>
                  <p class="text-sm text-gray-400 mb-3">长句子,快速倒计时</p>
                  <div class="flex justify-between items-center">
                        <span class="text-xs text-gray-500">时间: 30秒</span>
                        <i class="fa-solid fa-check-circle text-green-500 opacity-0"></i>
                  </div>
                </div>
            </div>
      </section>
    </main>

    <!-- 页脚 -->
    <footer class="bg-dark/80 backdrop-blur-md border-t border-primary/20 py-6">
      <div class="container mx-auto px-4">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                  <div class="flex items-center space-x-2">
                        <i class="fa-solid fa-keyboard text-primary text-xl"></i>
                        <span class="font-bold text-lg">打字练习</span>
                  </div>
                  <p class="text-sm text-gray-400 mt-1">提升你的打字速度,挑战极限!</p>
                </div>
                <div class="flex space-x-6">
                  <a href="#" class="text-gray-400 hover:text-primary transition-colors duration-300">
                        <i class="fa-brands fa-github text-xl"></i>
                  </a>
                  <a href="#" class="text-gray-400 hover:text-primary transition-colors duration-300">
                        <i class="fa-brands fa-twitter text-xl"></i>
                  </a>
                  <a href="#" class="text-gray-400 hover:text-primary transition-colors duration-300">
                        <i class="fa-brands fa-instagram text-xl"></i>
                  </a>
                </div>
            </div>
            <div class="border-t border-primary/10 mt-6 pt-6 text-center text-sm text-gray-500">
                &copy; 2025 打字练习 | 提升你的打字技能
            </div>
      </div>
    </footer>

    <script>
      // 打字游戏逻辑
      document.addEventListener('DOMContentLoaded', function() {
            // DOM元素
            const startBtn = document.getElementById('start-btn');
            const typingInput = document.getElementById('typing-input');
            const typingTextContainer = document.getElementById('typing-text-container');
            const typingText = document.getElementById('typing-text');
            const timerEl = document.getElementById('timer');
            const wpmEl = document.getElementById('wpm');
            const accuracyEl = document.getElementById('accuracy');
            const resultsSection = document.getElementById('results-section');
            const finalWpmEl = document.getElementById('final-wpm');
            const finalAccuracyEl = document.getElementById('final-accuracy');
            const totalCharactersEl = document.getElementById('total-characters');
            const playAgainBtn = document.getElementById('play-again-btn');
            const progressBar = document.getElementById('progress-bar');
            const capsLockIndicator = document.getElementById('caps-lock-indicator');
            const difficultyOptions = document.querySelectorAll('.difficulty-option');
            const keyboardWrapper = document.getElementById('keyboard');
            const refreshBtn = document.getElementById('refresh-btn'); // 获取刷新按钮

            // 游戏状态
            let gameStarted = false;
            let timer = 60;
            let timerInterval;
            let startTime;
            let currentText = '';
            let correctChars = 0;
            let totalChars = 0;
            let currentDifficulty = 'medium';

            // 文本库
            const textBank = [
                "编程是与计算机对话的艺术,每个程序员都是代码世界的诗人。",
                "在数字的海洋中,算法是指引我们前行的灯塔,数据是无尽的宝藏。",
                "学习编程不仅是掌握一门技术,更是培养一种解决问题的思维方式。",
                "当你的代码开始有了生命,你会发现编程是世界上最神奇的魔法。",
                "最好的代码是那些既能够高效运行,又能够被人理解的代码。",
                "在编程的世界里,没有完美的代码,但有不断追求完美的程序员。",
                "调试是将错误从你的代码中解放出来的过程,也是编程中最有趣的部分。",
                "每一个bug都是一次学习的机会,每一次修复都是一次成长。",
                "编程教会我们,复杂的问题可以分解为简单的步骤来解决。",
                "真正的程序员不写代码,他们只是和编译器交流自己的想法。",
                "代码是写给人看的,只是恰好能被机器执行。",
                "如果你认为数学很难,那你应该试试编程。",
                "编程就像下棋,一步错可能导致满盘皆输,但坚持下去总会有转机。",
                "在编程中,如同在生活中,重要的不是你遇到了多少问题,而是你如何解决它们。",
                "编程是未来的语言,学习它就是在为明天做准备。",
                "Web开发需要掌握HTML、CSS和JavaScript,这三者缺一不可。",
                "数据结构和算法是编程的基础,良好的基础能让你走得更远。",
                "人工智能正在改变世界,机器学习是人工智能的核心技术。",
                "响应式设计确保网站在各种设备上都能提供良好的用户体验。",
                "版本控制系统如Git,是现代软件开发中不可或缺的工具。",
                "前端框架如React、Vue和Angular正在重塑Web开发的方式。",
                "数据库设计是构建高效应用程序的关键环节。",
                "云计算让我们能够更高效地利用计算资源,降低成本。",
                "移动应用开发需要考虑屏幕尺寸、触摸交互等多种因素。",
                "用户体验设计是创造成功产品的重要组成部分。",
                "测试是软件开发过程中不可或缺的环节,能确保代码质量。"
            ];

            // 生成键盘可视化
            function generateKeyboard() {
                keyboardWrapper.innerHTML = '';

                // 键盘布局定义
                const keyboardLayout = [
                  ['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'],
                  ['Tab', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', '\\'],
                  ['Caps Lock', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', '\'', 'Enter'],
                  ['Shift', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'Shift'],
                  [' ', 'Space', ' ']
                ];

                // 特殊按键宽度配置 - 使用相对单位
                const keyWidths = {
                  'Backspace': 1.5,
                  'Tab': 1.25,
                  '\\': 1.25,
                  'Caps Lock': 1.35,
                  'Enter': 1.85,
                  'Shift': 1.75,
                  'Space': 5
                };

                // 计算基础尺寸
                const baseKeyWidth = 50; // 基础按键宽度(px)
                const baseKeyHeight = 50; // 基础按键高度(px)
                const baseSpacing = 4; // 基础间距(px)

                // 计算键盘总宽度
                let maxRowWidth = 0;
                keyboardLayout.forEach(row => {
                  let rowWidth = 0;
                  row.forEach(key => {
                        const width = keyWidths || 1;
                        rowWidth += width;
                  });
                  if(rowWidth > maxRowWidth) {
                        maxRowWidth = rowWidth;
                  }
                });

                // 计算缩放因子,使键盘适应容器
                const containerWidth = keyboardWrapper.clientWidth;
                const availableWidth = containerWidth - (baseSpacing * (14 - 1)); // 14个按键的间距
                const scaleFactor = Math.min(1, availableWidth / (maxRowWidth * baseKeyWidth));

                // 应用缩放后的尺寸
                const scaledKeyWidth = baseKeyWidth * scaleFactor;
                const scaledKeyHeight = baseKeyHeight * scaleFactor;
                const scaledSpacing = baseSpacing * scaleFactor;

                // 为每个键盘行创建容器
                keyboardLayout.forEach((row, rowIndex) => {
                  const rowEl = document.createElement('div');
                  rowEl.className = 'keyboard-row';
                  rowEl.style.marginBottom = `${scaledSpacing}px`;

                  // 创建每行的按键
                  row.forEach(key => {
                        const keyEl = document.createElement('div');

                        // 计算按键宽度
                        const keyWidth = (keyWidths || 1) * scaledKeyWidth;

                        // 设置按键样式
                        keyEl.className = `key bg-dark/50 text-light border border-primary/30 rounded-md mx-${scaledSpacing / 2} transition-all duration-150 transform hover:scale-105 hover:bg-primary/20 text-center select-none`;
                        keyEl.style.width = `${keyWidth}px`;
                        keyEl.style.height = `${scaledKeyHeight}px`;
                        keyEl.dataset.key = key;

                        // 特殊键的显示文本
                        let displayText = key;
                        if(key === ' ') displayText = 'Space';

                        // 根据屏幕尺寸调整字体大小
                        const isMobile = window.innerWidth < 768;
                        const fontSize = isMobile ? 'text-xs' : key.length > 1 ? 'text-xs' : 'text-sm';

                        keyEl.innerHTML = `<span class="${fontSize}">${displayText}</span>`;
                        rowEl.appendChild(keyEl);
                  });

                  keyboardWrapper.appendChild(rowEl);
                });
            }

            // 高亮按下的键
            function highlightKey(key) {
                // 处理特殊情况
                if (key === ' ') {
                  key = 'Space';
                }
                // 统一转换为大写,以匹配键盘布局
                key = key.toUpperCase();

                const keyEl = document.querySelector(`.key`);
                if (keyEl) {
                  keyEl.classList.add('bg-primary/30', 'key-press-animation');
                  setTimeout(() => {
                        keyEl.classList.remove('bg-primary/30', 'key-press-animation');
                  }, 150);
                }
            }

            // 根据难度获取文本
            function getTextByDifficulty() {
                // 复制原数组
                const filteredTexts = [...textBank];

                // 根据难度调整文本长度
                if(currentDifficulty === 'easy') {
                  // 简单难度:短文本
                  filteredTexts.sort((a, b) => a.length - b.length);
                  return filteredTexts;
                } else if(currentDifficulty === 'hard') {
                  // 困难难度:长文本
                  filteredTexts.sort((a, b) => b.length - a.length);
                  return filteredTexts;
                } else {
                  // 中等难度:随机文本
                  return filteredTexts;
                }
            }

            // 选择随机文本
            function getRandomText() {
                return getTextByDifficulty();
            }

            // 格式化打字文本,添加高亮效果和光标
            function formatTypingText(input) {
                input = input || '';
                let html = '';

                for(let i = 0; i < currentText.length; i++) {
                  if(i < input.length) {
                        if(input === currentText) {
                            html += `<span class="char-correct">${currentText}</span>`;
                        } else {
                            html += `<span class="char-incorrect">${currentText}</span>`;
                        }
                  } else if(i === input.length) {
                        html += `<span class="typing-cursor">${currentText}</span>`;
                  } else {
                        html += currentText;
                  }
                }

                typingText.innerHTML = html;
               
                // 自动滚动到当前光标位置
                const cursorElement = typingText.querySelector('.typing-cursor');
                if (cursorElement) {
                  const cursorRect = cursorElement.getBoundingClientRect();
                  const containerRect = typingTextContainer.getBoundingClientRect();
                  
                  // 计算需要滚动的距离
                  if (cursorRect.bottom > containerRect.bottom) {
                        typingTextContainer.scrollTop += cursorRect.bottom - containerRect.bottom;
                  } else if (cursorRect.top < containerRect.top) {
                        typingTextContainer.scrollTop -= containerRect.top - cursorRect.top;
                  }
                }
            }

            // 计算WPM(每分钟字数)
            function calculateWPM() {
                const timeSpent = (Date.now() - startTime) / 60000; // 转换为分钟
                if(timeSpent === 0) return 0;
                const wpm = Math.round(correctChars / 5 / timeSpent);
                return wpm;
            }

            // 计算正确率
            function calculateAccuracy() {
                if(totalChars === 0) return 0;
                return Math.round((correctChars / totalChars) * 100);
            }

            // 更新计时器和进度条
            function updateTimer() {
                timer--;
                timerEl.textContent = timer;

                // 更新进度条
                const percentage = (timer / getTimeLimit()) * 100;
                progressBar.style.width = `${percentage}%`;

                // 时间快结束时的视觉效果
                if(timer <= 10) {
                  timerEl.classList.add('text-red-500', 'animate-pulse');
                  progressBar.classList.add('bg-red-500');
                  progressBar.classList.remove('bg-primary');
                } else {
                  progressBar.classList.remove('bg-red-500');
                  progressBar.classList.add('bg-primary');
                }

                if(timer <= 0) {
                  endGame();
                }
            }

            // 根据难度获取时间限制
            function getTimeLimit() {
                switch(currentDifficulty) {
                  case 'easy': return 90;
                  case 'medium': return 60;
                  case 'hard': return 30;
                  default: return 60;
                }
            }

            // 开始游戏
            function startGame() {
                if(gameStarted) return;

                // 设置游戏状态
                gameStarted = true;
                startBtn.disabled = true;
                startBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i><span>游戏进行中</span>';
                typingInput.disabled = false;
                typingInput.focus();

                // 重置游戏数据
                timer = getTimeLimit();
                timerEl.textContent = timer;
                timerEl.classList.remove('text-red-500', 'animate-pulse');
                correctChars = 0;
                totalChars = 0;
                wpmEl.textContent = '0';
                accuracyEl.textContent = '0%';
                progressBar.style.width = '100%';
                progressBar.classList.remove('bg-red-500');
                progressBar.classList.add('bg-primary');

                // 获取随机文本
                currentText = getRandomText();
                typingText.textContent = currentText; // 在游戏开始前显示要打的文本
                formatTypingText();

                // 开始计时器
                startTime = Date.now();
                timerInterval = setInterval(updateTimer, 1000);

                // 滚动到打字区域
                typingInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
            }

            // 结束游戏
            function endGame() {
                if(!gameStarted) return;

                // 清除计时器
                clearInterval(timerInterval);
                gameStarted = false;

                // 更新UI
                startBtn.disabled = false;
                startBtn.innerHTML = '<i class="fa-solid fa-play"></i><span>开始游戏</span>';
                typingInput.disabled = true;
                typingInput.value = '';

                // 计算最终结果
                const finalWpm = calculateWPM();
                const accuracy = calculateAccuracy();

                // 显示结果
                finalWpmEl.textContent = `${finalWpm} WPM`;
                finalAccuracyEl.textContent = `${accuracy}%`;
                totalCharactersEl.textContent = `${totalChars} 字符`;

                // 结果动画
                animateResultBar('.final-wpm-progress', finalWpm, 150);
                animateResultBar('.final-accuracy-progress', accuracy, 150);
                animateResultBar('.final-chars-progress', Math.min(totalChars / 100 * 100, 100), 150);

                // 显示结果部分并添加动画
                resultsSection.classList.remove('hidden');
                resultsSection.classList.add('animate-fade-in');

                // 滚动到结果部分
                setTimeout(() => {
                  resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }, 300);
            }

            // 结果进度条动画
            function animateResultBar(selector, value, duration) {
                const elements = document.querySelectorAll(selector);
                elements.forEach(el => {
                  let start = 0;
                  const interval = setInterval(() => {
                        start += 1;
                        el.style.width = `${start}%`;
                        if(start >= value) {
                            clearInterval(interval);
                        }
                  }, duration / value);
                });
            }

            // 重置游戏
            function resetGame() {
                if(gameStarted) {
                  clearInterval(timerInterval);
                  gameStarted = false;
                }

                // 重置UI
                startBtn.disabled = false;
                startBtn.innerHTML = '<i class="fa-solid fa-play"></i><span>开始游戏</span>';
                typingInput.disabled = true;
                typingInput.value = '';
                timerEl.textContent = getTimeLimit();
                timerEl.classList.remove('text-red-500', 'animate-pulse');
                wpmEl.textContent = '0';
                accuracyEl.textContent = '0%';
                progressBar.style.width = '100%';
                progressBar.classList.remove('bg-red-500');
                progressBar.classList.add('bg-primary');
                typingText.textContent = '准备好了吗?点击开始按钮,然后开始输入显示的文本。尽量快速且准确地输入!';

                // 隐藏结果部分
                resultsSection.classList.add('hidden');
            }

            // 切换文本函数
            function changeText() {
                currentText = getRandomText();
                typingText.textContent = currentText;
                if (gameStarted) {
                  typingInput.value = '';
                  correctChars = 0;
                  totalChars = 0;
                  wpmEl.textContent = '0';
                  accuracyEl.textContent = '0%';
                  formatTypingText();
                }
            }

            // 事件监听器
            startBtn.addEventListener('click', startGame);
            playAgainBtn.addEventListener('click', () => {
                resetGame();
                startGame();
            });
            refreshBtn.addEventListener('click', changeText); // 为刷新按钮添加点击事件监听器

            typingInput.addEventListener('input', function() {
                if(!gameStarted) return;

                const input = typingInput.value;
                totalChars = input.length;

                // 计算正确字符数
                correctChars = 0;
                for(let i = 0; i < Math.min(input.length, currentText.length); i++) {
                  if(input === currentText) {
                        correctChars++;
                  }
                }

                // 更新显示
                formatTypingText(input);
                wpmEl.textContent = calculateWPM();
                accuracyEl.textContent = `${calculateAccuracy()}%`;

                // 检查是否完成
                if(input.length === currentText.length) {
                  endGame();
                }
            });

            // 键盘事件监听
            document.addEventListener('keydown', function(e) {
                if(e.key === 'CapsLock') {
                  capsLockIndicator.classList.toggle('hidden');
                  capsLockIndicator.classList.toggle('fa-lock-open');
                  capsLockIndicator.classList.toggle('fa-lock');
                }

                // 高亮按下的键
                if(e.key.length === 1 || ['Backspace', 'Tab', 'Enter', 'Shift', ' '].includes(e.key)) {
                  let keyDisplay = e.key;
                  if(keyDisplay === ' ') keyDisplay = 'Space';
                  highlightKey(keyDisplay);
                }

                // 如果游戏未开始,按任意键不聚焦输入框
                if(!gameStarted) {
                  e.preventDefault();
                }
            });

            // 难度选择
            difficultyOptions.forEach(option => {
                option.addEventListener('click', function() {
                  // 移除所有选中状态
                  difficultyOptions.forEach(opt => {
                        opt.querySelector('i').classList.add('opacity-0');
                        opt.classList.remove('border-primary');
                        opt.classList.add('border-primary/30');
                  });

                  // 添加当前选中状态
                  this.querySelector('i').classList.remove('opacity-0');
                  this.classList.remove('border-primary/30');
                  this.classList.add('border-primary');

                  // 更新难度
                  currentDifficulty = this.dataset.difficulty;

                  // 如果游戏未开始,更新计时器显示
                  if(!gameStarted) {
                        timerEl.textContent = getTimeLimit();
                        // 重新获取文本并显示
                        currentText = getRandomText();
                        typingText.textContent = currentText;
                  }
                });
            });

            // 初始化
            generateKeyboard();
            // 在游戏开始前显示要打的文本
            currentText = getRandomText();
            typingText.textContent = currentText;

            // 页面滚动效果
            window.addEventListener('scroll', function() {
                const nav = document.querySelector('nav');
                if (window.scrollY > 10) {
                  nav.classList.add('py-2');
                  nav.classList.remove('py-3');
                } else {
                  nav.classList.add('py-3');
                  nav.classList.remove('py-2');
                }
            });

            // 窗口大小变化时重新生成键盘
            window.addEventListener('resize', generateKeyboard);
      });
    </script>
</body>
</html>   
运行代码测试

页: [1]
查看完整版本: HTML在线打字练习源码