Galería de videos personalizada en JavaScript

Cuando trabajamos con galerías de videos en la web, lo normal es apoyarnos en librerías externas o depender de los controles nativos del navegador. Sin embargo, con un poco de JavaScript puro (vanilla JS) podemos tener un control total sobre la experiencia de usuario.

En este artículo te mostraré cómo construir una galería de videos que cumple con estas características:

  • Reproduce un video al hacer clic sobre él.
  • Si haces clic nuevamente en el mismo video, se pausa.
  • Solo un video puede estar activo al mismo tiempo (los demás se detienen automáticamente).
  • Incluye una línea de tiempo personalizada que refleja el progreso y permite saltar a cualquier momento del video.

HTML

<div class="videos-list">
  <div class="video-item">
    <video muted loop poster="waterfall-in-a-wild-nature-free-video.jpg" preload="auto" src="waterfall-in-a-wild-nature-free-video.mp4"></video>
    <div class="wrap-timeline">
      <div class="timeline-range"></div>
      <input type="range" class="timeline" value="0" min="0" step="0.1">
    </div>
  </div>

  <div class="video-item">
    <video muted loop poster="the-park-free-video.jpg" preload="auto" src="the-park-free-video.mp4"></video>
    <div class="wrap-timeline">
      <div class="timeline-range"></div>
      <input type="range" class="timeline" value="0" min="0" step="0.1">
    </div>
  </div>
</div>

Javascript

document.querySelector(".videos-list").addEventListener("click", function (e) {
  const videoEl = e.target.closest("video");
  if (!videoEl) return;
  const videoItem = videoEl.closest(".video-item");
  if (!videoItem || !this.contains(videoItem)) return;
  if (videoItem.classList.contains("active")) {
    videoEl.pause();
    videoItem.classList.remove("active");
    return;
  }
  document.querySelectorAll(".videos-list .video-item").forEach((el) => {
    el.classList.remove("active");
    const v = el.querySelector("video");
    if (v) v.pause();
  });
  videoItem.classList.add("active");
  videoEl.play();
});

// CUSTOM TIMELINE VIDEO
function setTimelineVideo() {
  document.querySelectorAll(".video-item").forEach((container) => {
    const video = container.querySelector("video");
    const timeline = container.querySelector(".timeline");
    const rangeOverlay = container.querySelector(".timeline-range");
    if (!video || !timeline || !rangeOverlay) return;
    video.addEventListener("timeupdate", () => {
      timeline.max = video.duration;
      timeline.value = video.currentTime;
      const percentage = (video.currentTime / video.duration) * 100;
      rangeOverlay.style.width = percentage + "%";
    });
    video.addEventListener("loadedmetadata", () => {
      timeline.max = video.duration;
    });
    timeline.addEventListener("input", () => {
      video.currentTime = timeline.value;
    });
  });
}
setTimelineVideo();

CSS

.videos-list {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 2rem;
  display: grid;
  grid-gap: 2rem;
  grid-template-columns: repeat(auto-fill, minmax(min(30rem, 100%), 1fr));
}

.video-item {
  position: relative;

  video {
    width: 100%;
    height: auto;
    border-radius: 1rem;
    cursor: url("https://jupa.co/assets/icon-play.svg"), pointer;
  }

  .wrap-timeline {
    --progress-height: 10px;
    opacity: 0;
    position: absolute;
    border-radius: 1rem;
    width: calc(100% - 2rem);
    left: 1rem;
    bottom: 1.5rem;
    height: var(--progress-height);
    background: #0091d0;
    background: linear-gradient(
      90deg,
      rgba(0, 145, 208, 1) 0%,
      rgba(245, 232, 162, 1) 33%,
      rgba(244, 169, 64, 1) 66%,
      rgba(251, 133, 122, 1) 100%
    );
    z-index: 999;
    transition: opacity 0.3s ease-out;
    pointer-events: none;

    .timeline {
      opacity: 0;
      width: 100%;
      height: var(--progress-height);
      position: absolute;
      left: 0;
      top: 0;
      z-index: 5;
      cursor: pointer;
    }

    .timeline-range {
      position: absolute;
      border-radius: 1rem;
      width: 0;
      bottom: 0;
      height: var(--progress-height);
      background-color: rgba(0, 0, 0, 0.25);
      mix-blend-mode: multiply;
    }
  }

  &.active:hover {
    video {
      cursor: url("https://jupa.co/assets/icon-pause.svg"), pointer;
    }

    .wrap-timeline {
      opacity: 1;
      pointer-events: all;
    }
  }
}

Ventajas de este ejemplo

  • No dependes de librerías externas como jQuery.
  • La lógica es clara y reutilizable en cualquier proyecto.
  • La experiencia de usuario es mucho más intuitiva, ya que se controla qué video está activo y cómo interactúa con la barra de progreso.

Departamentos y ciudades de Colombia para Contact Form 7

Descarga el plugin Departamentos y ciudades de Colombia para Contact Form 7 gratis y usalo en tu proyecto web. Hace poco tuve que desarrollar un proyecto en el cual debía crear un formulario de contacto donde las personas eligieran el departamento y después la ciudad donde vivían en Colombia. Por…