<section class="metronome">
  <!--Boton para reproducir o parar -->
  <button @click.prevent="play = !play">
    <!--Icono de parado -->
    <svg  v-if="play" x="0px" y="0px" viewBox="0 0 42 42" style="enable-background:new 0 0 42 42;" xml:space="preserve"> <g> <path d="M14.5,0c-0.552,0-1,0.447-1,1v40c0,0.553,0.448,1,1,1s1-0.447,1-1V1C15.5,0.447,15.052,0,14.5,0z"/> <path d="M27.5,0c-0.552,0-1,0.447-1,1v40c0,0.553,0.448,1,1,1s1-0.447,1-1V1C28.5,0.447,28.052,0,27.5,0z"/> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> </svg>
    <!--Icono de reproducir -->
    <svg v-else x="0px" y="0px" viewBox="0 0 41.999 41.999" style="enable-background:new 0 0 41.999 41.999;" xml:space="preserve"> <path d="M36.068,20.176l-29-20C6.761-0.035,6.363-0.057,6.035,0.114C5.706,0.287,5.5,0.627,5.5,0.999v40 c0,0.372,0.206,0.713,0.535,0.886c0.146,0.076,0.306,0.114,0.465,0.114c0.199,0,0.397-0.06,0.568-0.177l29-20 c0.271-0.187,0.432-0.494,0.432-0.823S36.338,20.363,36.068,20.176z M7.5,39.095V2.904l26.239,18.096L7.5,39.095z"/> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> </svg>
  </button>
  <!-- Reproductor (será invisible) -->
	<audio class="player">
    <!-- Para conseguir la mayor compatibilidad, se usa tanto OGG como MP3 -->
		<source src="https://programadorwebvalencia.com/videos/blog/2019/11/tick.ogg" type="audio/ogg">	
		<source src="https://programadorwebvalencia.com/videos/blog/2019/11/tick.mp3" type="audio/mpeg">	
	</audio>
  <!-- Muestra las pulsaciones por minuto -->
  <p>
	  {{ ppm }}
  </p>
  <!-- Rango para cambiar las PPM (pulsaciones por minuto) -->
	<input @change="togglePlayer(play)" v-model="ppm" type="range" min="40" max="218" step="1">
</section>
.metronome {
  display: flex;
  flex-direction: column;
  padding: 2rem;
  text-align: center;
  border: 1px solid orange;
  align-items: center;
}
.metronome p {
  font-size: 1.3rem;
  font-weight: bold;
  font-family: arial;
}
.metronome button {
  border: 2px solid orange;
  background: #ffce73;
  width: 3rem;
  height: 3rem;
}
.metronome input[type="range"] {
  -webkit-appearance: none;
  width: 100%;
  margin: 13.2px 0;
}
.metronome input[type="range"]:focus {
  outline: none;
}
.metronome input[type="range"]::-webkit-slider-runnable-track {
  width: 100%;
  height: 12.6px;
  cursor: pointer;
  box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
  background: #ffa500;
  border-radius: 6.2px;
  border: 0.7px solid #ffa500;
}
.metronome input[type="range"]::-webkit-slider-thumb {
  box-shadow: 1px 1px 1px #ffa500, 0px 0px 1px #ffae1a;
  border: 2.2px solid #000000;
  height: 39px;
  width: 19px;
  border-radius: 2px;
  background: #ffa500;
  cursor: pointer;
  -webkit-appearance: none;
  margin-top: -13.9px;
}
.metronome input[type="range"]:focus::-webkit-slider-runnable-track {
  background: #ffae1a;
}
.metronome input[type="range"]::-moz-range-track {
  width: 100%;
  height: 12.6px;
  cursor: pointer;
  box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
  background: #ffa500;
  border-radius: 6.2px;
  border: 0.7px solid #ffa500;
}
.metronome input[type="range"]::-moz-range-thumb {
  box-shadow: 1px 1px 1px #ffa500, 0px 0px 1px #ffae1a;
  border: 2.2px solid #000000;
  height: 39px;
  width: 19px;
  border-radius: 2px;
  background: #ffa500;
  cursor: pointer;
}
.metronome input[type="range"]::-ms-track {
  width: 100%;
  height: 12.6px;
  cursor: pointer;
  background: transparent;
  border-color: transparent;
  color: transparent;
}
.metronome input[type="range"]::-ms-fill-lower {
  background: #e69500;
  border: 0.7px solid #ffa500;
  border-radius: 12.4px;
  box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
.metronome input[type="range"]::-ms-fill-upper {
  background: #ffa500;
  border: 0.7px solid #ffa500;
  border-radius: 12.4px;
  box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
.metronome input[type="range"]::-ms-thumb {
  box-shadow: 1px 1px 1px #ffa500, 0px 0px 1px #ffae1a;
  border: 2.2px solid #000000;
  height: 39px;
  width: 19px;
  border-radius: 2px;
  background: #ffa500;
  cursor: pointer;
  height: 12.6px;
}
.metronome input[type="range"]:focus::-ms-fill-lower {
  background: #ffa500;
}
.metronome input[type="range"]:focus::-ms-fill-upper {
  background: #ffae1a;
}
document.addEventListener("DOMContentLoaded", () => {
  //======================================================================
  // METRÓNOMO
  //======================================================================

  //-----------------------------------------------------
  // Variables
  //-----------------------------------------------------
  // DOM reproductor
  let player = document.querySelector(".metronome .player");
  // Lugar donde se almacenará la ID del timeout para poder destruirlo en el momento indicado
  let timeout = undefined;

  ///-----------------------------------------------------
  // VueJS
  //-----------------------------------------------------
  let appMetronome = new Vue({
    el: ".metronome",
    data: {
      ppm: 60,
      play: false
    },
    watch: {
      play: function(state) {
        // Lanza toggler si cambia la variable de play, utilizado para centralizar la lógica
        this.togglePlayer(state);
      }
    },
    methods: {
      togglePlayer: function(state) {
        /**
         * Método que activa o desactiva el metrónomo
         * @param {bool} state - Activar o desactivar
         */
        // Detiene intervalo
        clearInterval(timeout);
        // Empieza interval si el estado es cierto
        if (state) {
          timeout = setInterval(function() {
            // Reproduce audio
            player.play();
          }, appMetronome.ppmToMiliseconds(appMetronome.ppm));
        }
      },
      ppmToMiliseconds: function(ppm) {
        /**
         * Método que transforma los PPM (pulsaciones por minuto) a Milisegundos
         * @param {int} ppm - pulsamociones por minuto
         * @return {int} milisegundos
         */
        return 60 / ppm * 1000;
      }
    }
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js