Un carrusel de opiniones (HTML+JavaScript+CSS)

OK, esto no tiene mucho qué con lo que suelo publicar, pero ps no hay pex, aquí se los dejo.

No sé, tal vez a alguien le sirve.

HTML:

<div class='opiniones'>
  <button class="botones previa">&#10094;</button>
  <div class='opiniones-viewport'>
    <div class='opiniones-track'>
      <div class='opinion'>
        <q class='opinion-cita'>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</q>
        <p class='opinion-opinante'>Opinante</p>
      </div>
      <div class='opinion'>
        <q class='opinion-cita'>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</q>
        <p class='opinion-opinante'>Opinante</p>
      </div>
      <div class='opinion'>
        <q class='opinion-cita'>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</q>
        <p class='opinion-opinante'>Opinante</p>
      </div>
    </div>
  </div>
  <button class="botones siguiente">&#10095;</button>
  <div class="puntitos"></div>
</div>

CSS:

.opiniones p {
  text-align: center;
  padding-bottom: 20px;
}
.opiniones {
  position: relative;
  width: 100%;
  margin: 0 auto;
  overflow: hidden;
  cursor: default;
}
.opiniones-viewport {
  overflow: hidden;
}
.opiniones-track {
  display: flex;
  transition: transform 0.6s ease;
}
.opinion {
  color: black;
  background: white;
  min-width: 100%;
  padding: 40px 60px 10px 60px;
  text-align: center;
  box-sizing: border-box;
}
.opinion-cita {
  display: block;
  font-size: 1.2rem;
  margin-bottom: 15px;
}
.opinion-opinante {
  font-weight: bold;
  font-size: 1rem;
  opacity: 0.8;
}
/* Botones */
.botones {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: pink;
  color: black;
  border: none;
  font-size: 2rem;
  padding: 10px 15px;
  cursor: pointer;
  z-index: 10;
  transition: 0.3s;
}
.previa { left: 0; }
.siguiente { right: 0; }
.botones:hover {
  background: yellow;
}
/* puntitos */
.puntitos {
  text-align: center;
  margin-top: 20px;
}
.puntito {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin: 5px;
  background: white;
  opacity: 0.4;
  border-radius: 50%;
  cursor: pointer;
  transition: 0.3s;
}
.puntito.active {
  opacity: 1;
  background: pink;
}

JS:

document.addEventListener("DOMContentLoaded", function(){
  const track = document.querySelector(".opiniones-track");
  const slides = Array.from(track.children);
  const previabotones = document.querySelector(".previa");
  const siguientebotones = document.querySelector(".siguiente");
  const puntitosContainer = document.querySelector(".puntitos");
  const carousel = document.querySelector(".opiniones");
  let index = 0;
  let startX = 0;
  let currentTranslate = 0;
  let isDragging = true;
  let autoSlide = null;
  // Crear puntitos
  slides.forEach((_, i) => {
    const puntito = document.createElement("span");
    puntito.classList.add("puntito");
    if(i === 0) puntito.classList.add("active");
    puntito.addEventListener("click", () => goToSlide(i));
    puntitosContainer.appendChild(puntito);
  });
  const puntitos = document.querySelectorAll(".puntito");
  function updatepuntitos(){
    puntitos.forEach(puntito => puntito.classList.remove("active"));
    puntitos[index].classList.add("active");
  }
  function goToSlide(i){
    index = i;
    track.style.transform = `translateX(-${index * 100}%)`;
    updatepuntitos();
  }
  function siguienteSlide(){
    index = (index + 1) % slides.length;
    goToSlide(index);
  }
  function previaSlide(){
    index = (index - 1 + slides.length) % slides.length;
    goToSlide(index);
  }
  siguientebotones.addEventListener("click", siguienteSlide);
  previabotones.addEventListener("click", previaSlide);
  // Auto slide
  function startAuto(){
    if(autoSlide) return; // evita duplicados
    autoSlide = setInterval(siguienteSlide, 5000);
  }
  function stopAuto(){
    clearInterval(autoSlide);
    autoSlide = null;
  }
  carousel.addEventListener("mouseenter", stopAuto);
  carousel.addEventListener("mouseleave", startAuto);
  startAuto();
  // Drag functionality
  carousel.addEventListener("mousedown", e => {
    isDragging = true;
    startX = e.pageX;
    carousel.classList.add("dragging");
    stopAuto();
  });
  carousel.addEventListener("mouseup", e => {
    if(!isDragging) return;
    const moved = e.pageX - startX;
    if(moved > 50) previaSlide();
    if(moved < -50) siguienteSlide();
    isDragging = false;
    startAuto();
  });
  carousel.addEventListener("touchstart", e => {
    startX = e.touches[0].clientX;
    stopAuto();
  });
  carousel.addEventListener("touchend", e => {
    const moved = e.changedTouches[0].clientX - startX;
    if(moved > 50) previaSlide();
    if(moved < -50) siguienteSlide();
    startAuto();
  });
  // Altura dinámica según la opinión más alta
  function setHeight(){
    let maxHeight = 0;
    slides.forEach(slide => {
      slide.style.height = "auto";
      maxHeight = Math.max(maxHeight, slide.offsetHeight);
    });
    slides.forEach(slide => {
      slide.style.height = maxHeight + "px";
    });
  }
  window.addEventListener("load", setHeight);
  window.addEventListener("resize", setHeight);
});

Acá pueden ver cómo se ejecuta: https://codepen.io/Carlos-Basald-a/pen/raMdwxq

Yo lo puse en Blogger y quedó bien.

Comentarios