」轻量前端场景中,缺少开箱即用、可自定义参数的文字转语音实现方案。基于原生HTML+CSS+JavaScript实现AI文字转语音单页应用,可直接部署使用。支持多种音色选择,适配不同业务需求。支持语速自定义调节,调节范围0.5倍至2倍速。支持音调自定义调节,调节范围0.5至2.0。支持音量自定义调节,调节范围0%至100%。支持外部文本导入,兼容txt、md、doc、docx格式文件。支持生成音频导出功能。内置文本编辑快捷操作:清空文本、快捷粘贴、插入示例文本。内置文本字符、词汇统计功能。支持最近朗读记录存储,支持一键清空历史。提供完整播放控制能力,支持播放、暂停、停止、选中文本朗读。自带播放状态音频可视化动效,直观展示播放状态。该方案可快速集成到各类前端项目中,也可独立部署为工具,适配内容朗读、音频生成等多类场景。```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://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght300;400;500;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> body { font-family: 'Noto Sans SC', sans-serif; } / Custom Range Slider / input[type=range] { -webkit-appearance: none; background: transparent; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; height: 20px; width: 20px; border-radius: 50%; background: #6366f1; cursor: pointer; margin-top: -8px; box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4); transition: transform 0.1s; } input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.2); } input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 4px; cursor: pointer; background: #e5e7eb; border-radius: 2px; } input[type=range]:focus::-webkit-slider-runnable-track { background: #c7c7c7; } / Wave Animation / .wave-bar { animation: wave 1.2s ease-in-out infinite; } .wave-bar:nth-child(2) { animation-delay: 0.1s; } .wave-bar:nth-child(3) { animation-delay: 0.2s; } .wave-bar:nth-child(4) { animation-delay: 0.3s; } .wave-bar:nth-child(5) { animation-delay: 0.4s; } keyframes wave { 0%, 100% { height: 20%; } 50% { height: 100%; } } .paused .wave-bar { animation-play-state: paused; height: 20%; } / Glassmorphism / .glass { background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); } / Scrollbar / ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } ::-webkit-scrollbar-thumb { background: #c7c7c7; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #a0a0a0; } .voice-card.active { border-color: #6366f1; background: #eef2ff; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.15); } .btn-press:active { transform: scale(0.95); } </style> </head> <body class="bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50 min-h-screen text-gray-800"> <!-- Header --> <header class="glass sticky top-0 z-50 border-b border-white/50 shadow-sm"> <div class="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between"> <div class="flex items-center gap-3"> <div class="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center text-white shadow-lg shadow-indigo-200"> <i class="fa-solid fa-wave-square text-lg"></i> </div> <div> <h1 class="text-xl font-bold text-gray-900 tracking-tight">语音助手</h1> <p class="text-xs text-gray-500">AI 文字转语音</p> </div> </div> <div class="flex items-center gap-3"> <button onclick="document.getElementById('fileInput').click()" class="btn-press px-4 py-2 bg-white border border-gray-200 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm flex items-center gap-2"> <i class="fa-regular fa-file-lines"></i> 导入文本 </button> <input type="file" id="fileInput" accept=".txt,.md,.doc,.docx" class="hidden" onchange="handleFileImport(event)"> <button onclick="exportAudio()" id="exportBtn" class="btn-press px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700 transition-all shadow-md shadow-indigo-200 flex items-center gap-2 opacity-50 cursor-not-allowed" disabled> <i class="fa-solid fa-download"></i> 导出音频 </button> </div> </div> </header> <main class="max-w-6xl mx-auto px-4 py-8 grid grid-cols-1 lg:grid-cols-12 gap-6"> <!-- Left Panel: Text Input --> <div class="lg:col-span-7 space-y-6"> <!-- Text Editor --> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden"> <div class="px-5 py-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/50"> <div class="flex items-center gap-2 text-sm font-medium text-gray-700"> <i class="fa-solid fa-pen-to-square text-indigo-500"></i> 文本内容 </div> <div class="flex items-center gap-2 text-xs text-gray-400"> <span id="charCount">0</span> 字符 <span class="w-px h-3 bg-gray-300"></span> <span id="wordCount">0</span> 词 </div> </div> <textarea id="textInput" class="w-full h-80 p-5 resize-none focus:outline-none text-gray-700 leading-relaxed text-base placeholder-gray-400" placeholder="在此输入或粘贴您想要转换为语音的文本...&#10;&#10;支持中文、英文等多种语言,系统将自动识别并优化朗读效果。" oninput="updateCounts()" ></textarea> <div class="px-5 py-3 bg-gray-50/50 border-t border-gray-100 flex justify-between items-center"> <div class="flex gap-2"> <button onclick="clearText()" class="text-xs text-gray-500 hover:text-red-500 transition-colors px-2 py-1 rounded hover:bg-red-50"> <i class="fa-solid fa-trash-can mr-1"></i>清空 </button> <button onclick="pasteText()" class="text-xs text-gray-500 hover:text-indigo-500 transition-colors px-2 py-1 rounded hover:bg-indigo-50"> <i class="fa-regular fa-paste mr-1"></i>粘贴 </button> </div> <div class="flex gap-2"> <button onclick="insertSample()" class="text-xs text-indigo-600 hover:text-indigo-700 transition-colors px-2 py-1 rounded hover:bg-indigo-50 font-medium"> <i class="fa-solid fa-wand-magic-sparkles mr-1"></i>示例文本 </button> </div> </div> </div> <!-- Recent History --> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5"> <div class="flex items-center justify-between mb-4"> <h3 class="text-sm font-semibold text-gray-700 flex items-center gap-2"> <i class="fa-solid fa-clock-rotate-left text-gray-400"></i> 最近朗读 </h3> <button onclick="clearHistory()" class="text-xs text-gray-400 hover:text-red-500 transition-colors">清空历史</button> </div> <div id="historyList" class="space-y-2 max-h-48 overflow-y-auto"> <div class="text-center text-gray-400 text-sm py-8" id="emptyHistory"> <i class="fa-regular fa-folder-open text-2xl mb-2 block"></i> 暂无朗读记录 </div> </div> </div> </div> <!-- Right Panel: Controls --> <div class="lg:col-span-5 space-y-6"> <!-- Voice Settings --> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6"> <h3 class="text-sm font-semibold text-gray-700 mb-4 flex items-center gap-2"> <i class="fa-solid fa-sliders text-indigo-500"></i> 语音设置 </h3> <!-- Voice Selection --> <div class="mb-6"> <label class="block text-xs font-medium text-gray-500 mb-2 uppercase tracking-wider">选择音色</label> <div class="grid grid-cols-2 gap-3" id="voiceList"> <!-- Voices will be populated by JS --> </div> </div> <!-- Parameters --> <div class="space-y-5"> <!-- Speed --> <div> <div class="flex justify-between mb-2"> <label class="text-sm font-medium text-gray-700">语速</label> <span class="text-sm text-indigo-600 font-semibold" id="rateValue">1.0x</span> </div> <input type="range" id="rateSlider" min="0.5" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" oninput="updateRate(this.value)"> <div class="flex justify-between text-xs text-gray-400 mt-1"> <span>慢</span> <span>标准</span> <span>快</span> </div> </div> <!-- Pitch --> <div> <div class="flex justify-between mb-2"> <label class="text-sm font-medium text-gray-700">音调</label> <span class="text-sm text-indigo-600 font-semibold" id="pitchValue">1.0</span> </div> <input type="range" id="pitchSlider" min="0.5" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" oninput="updatePitch(this.value)"> <div class="flex justify-between text-xs text-gray-400 mt-1"> <span>低</span> <span>标准</span> <span>高</span> </div> </div> <!-- Volume --> <div> <div class="flex justify-between mb-2"> <label class="text-sm font-medium text-gray-700">音量</label> <span class="text-sm text-indigo-600 font-semibold" id="volumeValue">100%</span> </div> <input type="range" id="volumeSlider" min="0" max="1" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" oninput="updateVolume(this.value)"> </div> </div> </div> <!-- Playback Control --> <div class="bg-white rounded-2xl shadow-lg shadow-indigo-100 border border-indigo-100 p-6 relative overflow-hidden"> <div class="absolute top-0 right-0 w-32 h-32 bg-indigo-50 rounded-full -mr-10 -mt-10 opacity-50"></div> <div class="relative z-10"> <div class="flex items-center justify-center mb-6"> <div id="visualizer" class="flex items-end gap-1 h-12 paused"> <div class="wave-bar w-1.5 bg-indigo-500 rounded-full"></div> <div class="wave-bar w-1.5 bg-indigo-500 rounded-full"></div> <div class="wave-bar w-1.5 bg-indigo-500 rounded-full"></div> <div class="wave-bar w-1.5 bg-indigo-500 rounded-full"></div> <div class="wave-bar w-1.5 bg-indigo-500 rounded-full"></div> </div> </div> <div class="text-center mb-6"> <p class="text-sm text-gray-500 mb-1" id="statusText">准备就绪</p> <p class="text-xs text-gray-400" id="voiceInfo">默认语音</p> </div> <div class="flex items-center justify-center gap-4"> <button onclick="stopSpeech()" class="btn-press w-12 h-12 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 flex items-center justify-center transition-all" title="停止"> <i class="fa-solid fa-stop"></i> </button> <button onclick="toggleSpeech()" id="playBtn" class="btn-press w-16 h-16 rounded-full bg-indigo-600 hover:bg-indigo-700 text-white flex items-center justify-center shadow-lg shadow-indigo-300 transition-all transform hover:scale-105" title="播放/暂停"> <i class="fa-solid fa-play text-xl ml-1" id="playIcon"></i> </button> <button onclick="pauseSpeech()" class="btn-press w-12 h-12 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 flex items-center justify-center transition-all" title="暂停"> <i class="fa-solid fa-pause"></i> </button> </div> <div class="mt-6 flex items-center gap-3 text-xs text-gray-500 bg-gray-50 p-3 rounded-lg"> <i class="fa-solid fa-circle-info text-indigo-400"></i> <span id="hintText">点击播放按钮开始朗读,支持后台播放</span> </div> </div>