Как можно реализовать подобный слайдер?

Пожалуйста, посмотрите этот пример: <a href="https://jsbin.com/yaxozamoke/2/edit?html,css,js,output" rel="nofollow">https://jsbin.com/yaxozamoke/2/edit?html,css,js,output</a> <br/>
  • 23 апреля 2026 г. 15:00
Ответы на вопрос 2
Можно реализовать такой слайдер разными способами — чисто CSS (ограниченные возможности), с библиотеками (Swipper, Slick и т.п.) или на чистом JavaScript. Ниже — готовый, минимально полный пример на чистом JS: бесконечный (infinite) карусель через клонирование крайних слайдов, плавный переход, поддержка ресайза, кнопки вперед/назад, индикаторы (dots) и простая поддержка тача (drag).

Скопируйте и вставьте это в HTML-файл (всё в одном файле):

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Slider example</title>
<style>
  *{box-sizing:border-box}
  body{font-family:Arial;margin:40px;background:#f4f4f4}
  .slider{
    position:relative;
    width:100%;
    max-width:800px;
    margin:0 auto;
    overflow:hidden;
    background:#fff;
    border-radius:8px;
  }
  .track{
    display:flex;
    transition:transform .35s ease;
    will-change:transform;
  }
  .slide{
    min-width:100%;
    user-select:none;
    -webkit-user-drag:none;
    padding:30px;
    text-align:center;
    font-size:24px;
  }

  /* примеры слайда (можно заменить картинками) */
  .slide:nth-child(odd){background:linear-gradient(135deg,#6fb1ff,#8bd3ff)}
  .slide:nth-child(even){background:linear-gradient(135deg,#ffd08a,#ffd9b3)}

  .controls{
    position:absolute;
    top:50%;
    left:0; right:0;
    display:flex;
    justify-content:space-between;
    transform:translateY(-50%);
    pointer-events:none;
  }
  .btn{
    pointer-events:auto;
    background:rgba(0,0,0,.5);
    color:#fff;
    border:none;
    margin:0 10px;
    width:40px;height:40px;
    border-radius:50%;
    cursor:pointer;
  }
  .dots{
    text-align:center;
    padding:12px 0;
    background:transparent;
  }
  .dot{
    display:inline-block;
    width:10px;height:10px;
    border-radius:50%;
    margin:0 6px;
    background:rgba(0,0,0,.2);
    cursor:pointer;
  }
  .dot.active{background:rgba(0,0,0,.7)}
</style>
</head>
<body>

<div class="slider" id="slider">
  <div class="track" id="track">
    <!-- Слайды -->
    <div class="slide">Slide 1</div>
    <div class="slide">Slide 2</div>
    <div class="slide">Slide 3</div>
    <div class="slide">Slide 4</div>
  </div>

  <div class="controls">
    <button class="btn" id="prev" aria-label="Prev">&#10094;</button>
    <button class="btn" id="next" aria-label="Next">&#10095;</button>
  </div>
</div>

<div class="dots" id="dots"></div>

<script>
(function(){
  const slider = document.getElementById('slider');
  const track = document.getElementById('track');
  const prevBtn = document.getElementById('prev');
  const nextBtn = document.getElementById('next');
  const dotsWrap = document.getElementById('dots');

  let slides = Array.from(track.children);
  const slideCount = slides.length;

  // клонируем первый и последний для бесконечного перехода
  const firstClone = slides[0].cloneNode(true);
  const lastClone = slides[slides.length - 1].cloneNode(true);
  track.appendChild(firstClone);
  track.insertBefore(lastClone, track.firstChild);

  // обновляем список слайдов (с клонами)
  slides = Array.from(track.children);

  let index = 1; // начнем со второго узла (первый реальный слайд)
  let slideWidth = slider.clientWidth;
  let isTransitioning = false;

  // выставляем ширину слайдов (каждый 100% контейнера)
  function setSizes() {
    slideWidth = slider.clientWidth;
    slides.forEach(s => s.style.minWidth = slideWidth + 'px');
    moveToIndex(index, false);
  }

  window.addEventListener('resize', debounce(setSizes, 100));
  setSizes();

  // Dots
  const realCount = slideCount;
  const dots = [];
  for (let i=0;i<realCount;i++){
    const d = document.createElement('span');
    d.className = 'dot' + (i===0 ? ' active':'');
    d.dataset.slide = i;
    dotsWrap.appendChild(d);
    dots.push(d);
    d.addEventListener('click', ()=>goTo(i+1)); // +1 из-за клона в начале
  }

  function updateDots() {
    const realIndex = ((index - 1) % realCount + realCount) % realCount; // 0..realCount-1
    dots.forEach((d,i)=>d.classList.toggle('active', i===realIndex));
  }

  // Перемещение трека
  function moveToIndex(i, animate = true){
    if (!animate) track.style.transition = 'none';
    else track.style.transition = '';
    const x = -i * slideWidth;
    track.style.transform = `translateX(${x}px)`;
    if (!animate){
      // принудительно применяем изменения без transition
      requestAnimationFrame(()=>{ requestAnimationFrame(()=>{ track.style.transition = ''; }); });
    }
  }

  // переход на индекс
  function goTo(i){
    if (isTransitioning) return;
    isTransitioning = true;
    index = i;
    moveToIndex(index, true);
    updateDots();
  }

  nextBtn.addEventListener('click', ()=>goTo(index+1));
  prevBtn.addEventListener('click', ()=>goTo(index-1));

  // Когда завершился переход — проверяем на клоны
  track.addEventListener('transitionend', ()=>{
    isTransitioning = false;
    // если мы перешли на клон (первый элемент — клон последнего)
    if (slides[index].isSameNode(firstClone)) {
      index = 1; // реальный первый
      moveToIndex(index, false);
    } else if (slides[index].isSameNode(lastClone)) {
      index = realCount; // реальный последний
      moveToIndex(index, false);
    }
  });

  // Начальное положение (на реальном слайде)
  moveToIndex(index, false);

  // Простой drag (mouse/touch) — pointer events
  let startX = 0;
  let currentTranslate = 0;
  let dragging = false;

  slider.addEventListener('pointerdown', (e)=>{
    slider.setPointerCapture(e.pointerId);
    dragging = true;
    startX = e.clientX;
    currentTranslate = -index * slideWidth;
    track.style.transition = 'none';
  });

  slider.addEventListener('pointermove', (e)=>{
    if (!dragging) return;
    const dx = e.clientX - startX;
    track.style.transform = `translateX(${currentTranslate + dx}px)`;
  });

  slider.addEventListener('pointerup', (e)=>{
    if (!dragging) return;
    dragging = false;
    const dx = e.clientX - startX;
    track.style.transition = '';
    // порог для переключения — 20% ширины
    if (Math.abs(dx) > slideWidth * 0.2){
      if (dx < 0) goTo(index + 1);
      else goTo(index - 1);
    } else {
      moveToIndex(index, true);
    }
    try { slider.releasePointerCapture(e.pointerId); } catch(e){}
  });

  slider.addEventListener('pointercancel', ()=>{
    if (!dragging) return;
    dragging = false;
    moveToIndex(index, true);
  });

  // Автоплей (необязательно)
  let autoplayInterval = 4000;
  let autoplayId = null;
  function startAutoplay(){
    stopAutoplay();
    autoplayId = setInterval(()=>goTo(index+1), autoplayInterval);
  }
  function stopAutoplay(){ if (autoplayId) clearInterval(autoplayId); autoplayId = null; }
  slider.addEventListener('mouseenter', stopAutoplay);
  slider.addEventListener('mouseleave', startAutoplay);
  // запустить автоплей, если нужно:
  // startAutoplay();

  // keyboard
  window.addEventListener('keydown', (e)=>{
    if (e.key === 'ArrowLeft') prevBtn.click();
    if (e.key === 'ArrowRight') nextBtn.click();
  });

  // утилиты
  function debounce(fn, wait){
    let t;
    return function(...a){ clearTimeout(t); t = setTimeout(()=>fn.apply(this,a), wait); };
  }

})();
</script>

Комментарии / пояснения:
- Ключевая идея для бесконечного слайдера — клонировать первый и последний слайды и при достижении клона "прыгать" (без анимации) на соответствующий реальный слайд.
- Позиционирование — transform: translateX(-index * width) через flex-контейнер track.
- При ресайзе нужно пересчитать ширину слайда.
- Для поддержки перетаскивания (touch/mouse) удобно использовать pointer events (работает и на тачах, и на мыши).
- Можно добавить lazy-loading картинок, адаптивные колонки (несколько слайдов на экран) — тогда ширина слайда считается как slider.clientWidth / slidesVisible.
- Если хотите готовое и богатое решение, используйте библиотеку Swiper (маленький код, много функций).

Если хотите — могу:
- прислать версию, где видно картинки вместо цветных блоков,
- сделать вариант с несколькими видимыми слайдами (например 3 на десктопе, 1 на мобиле),
- или показать вариант с использованием CSS-only (radio + labels) или с библиотекой Swiper.
Grid модуль свайпера нужен. rows: 2 и обязательно fill: 'row': <br/> <br/> <pre><code>import { Grid } from 'swiper/modules';

new Swiper('.swiper', {
  modules: [Grid],
  slidesPerView: 4,
  grid: { rows: 2, fill: 'row' },
  spaceBetween: 16,
});</code></pre> <br/> <br/> p.s. без fill: 'row' слайды пойдут столбцами, это дефолт
Похожие вопросы