Edita código
Copiar al portapapeles
Ejecutar »
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Conversor Avanzado de Texto a Voz</title> <style> :root { --primary-color: #4a90e2; --secondary-color: #50c878; --accent-color: #ff6b6b; --background-color: #f0f4f8; --text-color: #333; --box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); } body { font-family: 'Roboto', Arial, sans-serif; line-height: 1.6; color: var(--text-color); margin: 0; padding: 0; background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; } .container { max-width: 900px; width: 100%; padding: 40px; background-color: rgba(255, 255, 255, 0.9); border-radius: 20px; box-shadow: var(--box-shadow); backdrop-filter: blur(10px); } h1 { text-align: center; color: var(--primary-color); margin-bottom: 30px; font-size: 3.5rem; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); animation: glow 2s ease-in-out infinite alternate; } @keyframes glow { from { text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px var(--primary-color), 0 0 35px var(--primary-color), 0 0 40px var(--primary-color), 0 0 50px var(--primary-color), 0 0 75px var(--primary-color); } to { text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px var(--primary-color), 0 0 70px var(--primary-color), 0 0 80px var(--primary-color), 0 0 100px var(--primary-color), 0 0 150px var(--primary-color); } } textarea { width: 96%; height: 200px; padding: 20px; border: 2px solid #bdc3c7; border-radius: 15px; resize: vertical; font-size: 18px; transition: all 0.3s ease; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); background-color: rgba(255, 255, 255, 0.8); } textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.3); background-color: rgba(255, 255, 255, 1); } .controls { display: flex; justify-content: space-between; margin-top: 20px; flex-wrap: wrap; } select, button { padding: 15px; border: none; border-radius: 10px; font-size: 18px; transition: all 0.3s ease; } select { flex-grow: 1; margin-right: 20px; background-color: rgba(248, 249, 250, 0.8); border: 2px solid #bdc3c7; appearance: none; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%234a90e2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: right 15px top 50%; background-size: 12px auto; } select:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.3); background-color: rgba(248, 249, 250, 1); } button { background-color: var(--secondary-color); color: white; cursor: pointer; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; flex-grow: 1; margin-top: 10px; position: relative; overflow: hidden; } button:hover { background-color: #45b36b; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } button:disabled { background-color: #95a5a6; cursor: not-allowed; transform: none; box-shadow: none; } button::after { content: ''; position: absolute; top: 50%; left: 50%; width: 5px; height: 5px; background: rgba(255, 255, 255, .5); opacity: 0; border-radius: 100%; transform: scale(1, 1) translate(-50%); transform-origin: 50% 50%; } @keyframes ripple { 0% { transform: scale(0, 0); opacity: 1; } 20% { transform: scale(25, 25); opacity: 1; } 100% { opacity: 0; transform: scale(40, 40); } } button:focus:not(:active)::after { animation: ripple 1s ease-out; } #status { text-align: center; margin-top: 20px; font-weight: bold; color: var(--accent-color); height: 24px; } .wave { width: 100%; height: 60px; display: flex; justify-content: center; align-items: center; } .wave .bar { width: 10px; margin: 0 3px; background-color: var(--primary-color); animation: wave 1s 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% { transform: scale(0); } 50% { transform: scale(1); } 100% { transform: scale(0); } } .settings { display: flex; justify-content: space-between; margin-top: 20px; } .settings label { display: flex; flex-direction: column; align-items: center; width: 30%; position: relative; } .settings input[type="range"] { width: 100%; margin-top: 10px; -webkit-appearance: none; background: transparent; } .settings input[type="range"]:focus { outline: none; } .settings input[type="range"]::-webkit-slider-runnable-track { width: 100%; height: 8px; cursor: pointer; background: #ddd; border-radius: 4px; } .settings input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 20px; width: 20px; border-radius: 50%; background: var(--primary-color); cursor: pointer; margin-top: -6px; box-shadow: 0 0 10px rgba(74, 144, 226, 0.5); } .settings input[type="range"]::-moz-range-track { width: 100%; height: 8px; cursor: pointer; background: #ddd; border-radius: 4px; } .settings input[type="range"]::-moz-range-thumb { height: 20px; width: 20px; border-radius: 50%; background: var(--primary-color); cursor: pointer; box-shadow: 0 0 10px rgba(74, 144, 226, 0.5); } .settings label::after { content: attr(data-value); position: absolute; top: 100%; left: 50%; transform: translateX(-50%); padding: 4px 8px; background-color: var(--primary-color); color: white; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; } .settings label:hover::after { opacity: 1; } @media (max-width: 768px) { .container { padding: 20px; margin: 20px; } .controls, .settings { flex-direction: column; } select, .settings label { margin-right: 0; margin-bottom: 15px; width: 100%; } } </style> </head> <body> <div class="container"> <h1>LECTOR DE TEXTOS</h1> <textarea id="text-input" placeholder="Escribe o pega tu texto aquÃ..."> En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivÃa un hidalgo de los de lanza en astillero, adarga antigua, rocÃn flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lantejas los viernes, algún palomino de añadidura los domingos, consumÃan las tres partes de su hacienda. El resto della concluÃan sayo de velarte, calzas de velludo para las fiestas, con sus pantuflos de lo mesmo, y los dÃas de entresemana se honraba con su vellorà de lo más fino. TenÃa en su casa una ama que pasaba de los cuarenta y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza que asà ensillaba el rocÃn como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años. Era de complexión recia, seco de carnes, enjuto de rostro, gran madrugador y amigo de la caza. Quieren decir que tenÃa el sobrenombre de «Quijada», o «Quesada», que en esto hay alguna diferencia en los autores que deste caso escriben, aunque por conjeturas verisÃmiles se deja entender que se llamaba «Quijana». Pero esto importa poco a nuestro cuento: basta que en la narración dél no se salga un punto de la verdad. </textarea> <div class="controls"> <select id="voice-select"> <option value="">Selecciona una voz</option> </select> <button id="speak-button">Convertir a Voz</button> <button id="clear-button">Limpiar Texto</button> </div> <div class="settings"> <label data-value="1"> Velocidad <input type="range" id="rate" min="0.5" max="2" step="0.1" value="1"> </label> <label data-value="1"> Tono <input type="range" id="pitch" min="0.5" max="2" step="0.1" value="1"> </label> <label data-value="1"> Volumen <input type="range" id="volume" min="0" max="1" step="0.1" value="1"> </label> </div> <div id="status"></div> <div class="wave" id="wave" style="display: none;"> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const textInput = document.getElementById('text-input'); const voiceSelect = document.getElementById('voice-select'); const speakButton = document.getElementById('speak-button'); const clearButton = document.getElementById('clear-button'); const status = document.getElementById('status'); const wave = document.getElementById('wave'); const rateInput = document.getElementById('rate'); const pitchInput = document.getElementById('pitch'); const volumeInput = document.getElementById('volume'); let synth = window.speechSynthesis; let voices = []; function populateVoiceList() { voices = synth.getVoices(); voiceSelect.innerHTML = ''; voices.forEach((voice, index) => { const option = document.createElement('option'); option.value = index; option.textContent = `${voice.name} (${voice.lang})`; voiceSelect.appendChild(option); }); console.log(voices); // Mostrar todas las voces disponibles en la consola } populateVoiceList(); if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = populateVoiceList; } speakButton.addEventListener('click', function() { const text = textInput.value; const selectedVoiceIndex = voiceSelect.value; if (text && selectedVoiceIndex !== "") { let utterance = new SpeechSynthesisUtterance(text); utterance.voice = voices[selectedVoiceIndex]; utterance.pitch = parseFloat(pitchInput.value); utterance.rate = parseFloat(rateInput.value); utterance.volume = parseFloat(volumeInput.value); status.textContent = "Reproduciendo audio..."; speakButton.disabled = true; wave.style.display = 'flex'; utterance.onend = function() { status.textContent = "Reproducción completada"; speakButton.disabled = false; wave.style.display = 'none'; setTimeout(() => { status.textContent = ""; }, 3000); }; synth.cancel(); synth.speak(utterance); } else { status.textContent = "Por favor, selecciona una voz y escribe algún texto."; setTimeout(() => { status.textContent = ""; }, 3000); } }); // Limpiar el contenido del área de texto clearButton.addEventListener('click', function() { textInput.value = ''; speakButton.disabled = true; // Deshabilitar el botón de convertir a voz cuando se limpia el texto }); // Habilitar botón si hay texto textInput.addEventListener('input', function() { speakButton.disabled = textInput.value.length === 0 || voiceSelect.value === ""; }); // Habilitar botón si hay voz seleccionada voiceSelect.addEventListener('change', function() { speakButton.disabled = textInput.value.length === 0 || voiceSelect.value === ""; }); // Actualizar valores en tiempo real rateInput.addEventListener('input', updateValue); pitchInput.addEventListener('input', updateValue); volumeInput.addEventListener('input', updateValue); function updateValue(e) { const value = parseFloat(e.target.value).toFixed(1); const label = e.target.closest('label'); label.setAttribute('data-value', value); } }); </script> </body> </html>
Resultado