WhatsApp
Ir al contenido

  

Sound Board

crea un tablero de sonidos con HTML, CSS y JS

¿Qué aprenderás?

  • Manejar elementos <audio> (métodos .play(), .pause(), .currentTime = 0).

  • Usar event delegation para no crear un listener por botón.

  • Micro-interacciones: foco de teclado, estados activos, y layout responsivo.

Código (3 archivos)

1) index.html

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Sound Board</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <main class="container">
    <h1>Sound Board</h1>
    <p class="lead">Haz clic o usa <kbd>Tab</kbd> + <kbd>Enter</kbd>.</p>
    <section class="grid" id="soundGrid" aria-label="Tablero de sonidos">
      <!-- Cada botón declara data-sound que apunta al id del <audio> -->
      <button class="pad" data-sound="s1">Roar</button>
      <button class="pad" data-sound="s2">Clang</button>
      <button class="pad" data-sound="s3">Pop</button>
      <button class="pad" data-sound="s4">Bell</button>
    </section>
    <!-- Audios (puedes cambiar las URLs por tus archivos .mp3/.ogg) -->
    <audio id="s1" src="https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3" preload="auto"></audio>
    <audio id="s2" src="https://actions.google.com/sounds/v1/cartoon/clang_and_wobble.ogg" preload="auto"></audio>
    <audio id="s3" src="https://actions.google.com/sounds/v1/cartoon/pop.ogg" preload="auto"></audio>
    <audio id="s4" src="https://actions.google.com/sounds/v1/alarms/alarm_clock.ogg" preload="auto"></audio>
  </main>
  <script src="script.js"></script>
</body>
</html>


2) style.css 

* { box-sizing: border-box; }
:root {
  --bg: #0f1115; --card: #151a23; --txt: #f5f7fa; --muted: #9aa4b2;
  --accent: #7c5cff; --ring: #8b5cf6; --radius: 18px; --gap: 14px;
}
html, body { height: 100%; }
body {
  margin: 0;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  background: radial-gradient(1200px 600px at 20% -10%, #1b2030 0, #0f1115 45%, #0b0e13 100%);
  color: var(--txt);
}
.container {
  max-width: 1000px; margin: 48px auto; padding: 0 16px;
}
h1 { margin: 0 0 4px; font-size: clamp(28px, 4vw, 44px); }
.lead { margin: 0 0 24px; color: var(--muted); }
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--gap);
}
.pad {
  border: 0; outline: none; cursor: pointer;
  padding: 28px 20px; border-radius: var(--radius);
  background: linear-gradient(180deg, #1b2130, var(--card));
  color: #fff; font-weight: 700; letter-spacing: .4px;
  box-shadow: 0 1px 0 rgba(255,255,255,.04) inset, 0 12px 30px rgba(0,0,0,.35);
  transition: transform .12s ease, box-shadow .18s ease, background .18s ease;
}
.pad:hover { transform: translateY(-2px); }
.pad:active, .pad.is-playing {
  transform: translateY(0);
  box-shadow: 0 0 0 2px #000, 0 0 0 4px var(--ring), 0 12px 34px rgba(0,0,0,.5);
  background: linear-gradient(180deg, #21283a, #18202f);
}
.pad:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 2px #000, 0 0 0 4px var(--accent);
  transition: box-shadow .08s ease;
}


3) script.js

// Event delegation: un solo listener para todos los botones
const grid = document.getElementById('soundGrid');
function playSound(audio) {
  if (!audio) return;
  // Reinicia y reproduce (si otra suena, no pasa nada: los audios son independientes)
  audio.pause();
  audio.currentTime = 0;
  audio.play().catch(() => {
    // Algunos navegadores bloquean autoplay hasta que hay interacción real
    // Aquí ya hubo clic/tecla, así que en general no habrá error.
  });
}
grid.addEventListener('click', (e) => {
  const btn = e.target.closest('.pad');
  if (!btn) return;
  const id = btn.dataset.sound;
  const audio = document.getElementById(id);
  // Feedback visual mientras suena
  btn.classList.add('is-playing');
  playSound(audio);
  const removeState = () => btn.classList.remove('is-playing');
  // Quita estado al terminar (si el formato emite 'ended')
  audio?.addEventListener('ended', removeState, { once: true });
  // Fallback: quítalo tras N ms si no hay 'ended' (p. ej., loop o stream)
  setTimeout(removeState, 1500);
});
// Soporte teclado: Enter/Space activan el botón enfocado
grid.addEventListener('keydown', (e) => {
  if (e.key !== 'Enter' && e.key !== ' ') return;
  const btn = e.target.closest('.pad');
  if (!btn) return;
  e.preventDefault();
  btn.click();
});

 

Arquitectura (capa por capa)

1) Estructura HTML

  • Un contenedor .grid con botones <button> (no <div>), porque:

    • Son focusables por defecto (teclado).

    • Emite eventos de “click” al presionar Enter/Space.

  • Cada botón tiene un atributo data-sound="ID". Ese ID coincide con el id de un <audio> en el HTML.
    Ej.: <button data-sound="s1">Roar</button> ↔ <audio id="s1" src="...">.

Esto desacopla el UI del recurso; no necesitas hardcodear rutas de audio dentro del botón.

2) Estilos y layout

  • CSS Grid para que la cuadrícula responda al ancho de pantalla (repeat(auto-fit, minmax(...))).

  • Estados visuales:

    • :hover, :active, y una clase .is-playing que añadimos por JS mientras suena el audio.

    • Sombras y un leve “presionado” para que el usuario sienta el clic.

3) Lógica JS

  • Event delegation: en vez de asignar un addEventListener por botón, ponemos uno en el contenedor .grid y filtramos con e.target.closest('.pad').
    Ventajas: menos listeners, más simple escalar a 50+ botones.

  • Reproducción:

    • audio.pause() y audio.currentTime = 0 reinician la pista para evitar que se “encimen” clics rápidos.

    • audio.play() inicia. Si el navegador exige interacción previa, ya la hubo (clic/tecla), por lo que no debería bloquear.

  • Feedback:

    • Añadimos .is-playing al botón y la retiramos cuando el audio lanza ended.

    • Fallback con setTimeout por si el audio no emite ended (p. ej., si cambias a loop).

Detalles importantes y pitfalls

Autoplay / políticas de los navegadores

  • La mayoría de navegadores bloquean play() hasta que haya interacción del usuario (clic, tecla).

  • En este proyecto disparas play() desde el handler del clic/tecla: cumple la regla; todo bien.

  • Si quisieras pre-cargar o reproducir “al cargar la página”, te toparás con bloqueos; usa preload="auto" y reproduce solo tras el primer input.

Ejemplo )

Finalmente debe de quedar algo como el siguiente ejemplo


 

Sound Board
Paris Minero 21 de octubre de 2025
Compartir
Iniciar sesión dejar un comentario

  
Split Landing Page
Qué es, cómo funciona y código listo