WhatsApp

  

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 octubre, 2025
Compartir
Iniciar sesión dejar un comentario

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