Готовый слайдер — отличное решение, пока перед вами типовая задача: три баннера, стрелки, точки. Но как только дизайн требует нестандартного поведения, анимации или логики переключения, а каждый килобайт на счету, собирать компонент вручную становится проще и надёжнее. Разберём, как сделать простой, понятный слайдер на чистом JavaScript — такой, который легко встроить в проект и развивать под свои нужды.

Когда имеет смысл писать слайдер самому

Самописный слайдер — не панацея. Для лендинга с двумя баннерами, где всё работает из коробки, готовый плагин сэкономит время. Но как только появляются специфические требования, свой компонент начинает выигрывать по всем фронтам:

  • нужен полный контроль над HTML-разметкой и стилями — никаких компромиссов с чужой структурой;
  • важно не тащить лишние зависимости — меньше кода, меньше уязвимостей;
  • требуется своя логика переключения, автопрокрутки, свайпа или синхронизации с другими блоками;
  • компонент должен легко адаптироваться под дизайн-систему проекта;
  • проект должен оставаться понятным для поддержки через несколько месяцев — свой код всегда прозрачнее.

У простого слайдера есть ещё одно важное преимущество: вы понимаете, как он устроен. Это особенно полезно, когда сайт собирается из шаблонов и готовых компонентов, а потом дорабатывается под продуктовые задачи. Вместо того чтобы копаться в документации плагина, вы правите знакомый код.

Из чего состоит простой слайдер

Минимальный слайдер — это не магия, а четыре части:

  • обёртка — видимая область, в которой показывается только один слайд;
  • трек — элемент, который двигается влево и вправо;
  • слайды — отдельные карточки, изображения или блоки контента;
  • управление — кнопки, точки, клавиатура, свайп.

Базовая схема почти всегда одна и та же: у трека меняется transform: translateX(...), а активный слайд определяется индексом. Это фундамент, на котором строятся все последующие улучшения.

Базовая структура HTML

 
Слайд 1
Слайд 2
Слайд 3

 
 

Такая разметка проста и читаема. Важно, что логика и внешний вид разделены: HTML отвечает за структуру, CSS — за отображение, JS — за поведение. Это позволяет менять дизайн, не трогая скрипты, и наоборот.

Стили: делаем слайдер без лишней сложности

Базовый CSS

.slider {
  position: relative;
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.slider__viewport {
  overflow: hidden;
  width: 100%;
}

.slider__track {
  display: flex;
  transition: transform 0.3s ease;
}

.slider__slide {
  flex: 0 0 100%;
  min-width: 0;
}

.slider__btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0,0,0,0.5);
  color: white;
  border: none;
  padding: 10px 15px;
  cursor: pointer;
}

.slider__btn--prev { left: 10px; }
.slider__btn--next { right: 10px; }

.slider__dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}

.slider__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: none;
  background: #ccc;
  cursor: pointer;
}

.slider__dot--active {
  background: #333;
}

Ключевая идея: каждый слайд занимает ровно 100% ширины контейнера. Тогда смещение трека вычисляется элементарно: -100% * индекс. Никаких сложных расчётов, всё прозрачно. flex: 0 0 100% гарантирует, что слайды не сжимаются и не растягиваются, а overflow: hidden отсекает всё лишнее.

JavaScript: логика переключения

Минимальная рабочая версия

const slider = document.querySelector('.slider');
const track = slider.querySelector('.slider__track');
const slides = slider.querySelectorAll('.slider__slide');
const prevBtn = slider.querySelector('.slider__btn--prev');
const nextBtn = slider.querySelector('.slider__btn--next');
const dotsContainer = slider.querySelector('.slider__dots');

let currentIndex = 0;

function updateSlider() {
  track.style.transform = `translateX(-${currentIndex * 100}%)`;

  const dots = dotsContainer.querySelectorAll('.slider__dot');
  dots.forEach((dot, i) => {
    dot.classList.toggle('slider__dot--active', i === currentIndex);
  });
}

function nextSlide() {
  currentIndex = (currentIndex + 1) % slides.length;
  updateSlider();
}

function prevSlide() {
  currentIndex = (currentIndex - 1 + slides.length) % slides.length;
  updateSlider();
}

// Создаём точки
slides.forEach((_, i) => {
  const dot = document.createElement('button');
  dot.classList.add('slider__dot');
  dot.setAttribute('type', 'button');
  dot.addEventListener('click', () => {
    currentIndex = i;
    updateSlider();
  });
  dotsContainer.appendChild(dot);
});

prevBtn.addEventListener('click', prevSlide);
nextBtn.addEventListener('click', nextSlide);

// Инициализация
updateSlider();

Перед вами уже работающий слайдер: кнопки переключают слайды, точки подсвечиваются, зацикливание работает. Никаких библиотек — только чистый JS. Обратите внимание: точки создаются динамически по количеству слайдов, а значит, разметка остаётся минимальной.

Как это работает по шагам

1. Находим элементы

Сначала скрипт получает обёртку, трек, слайды, кнопки и контейнер точек. Это стандартный подход: вся логика живёт внутри конкретного экземпляра слайдера. Благодаря этому на странице может быть несколько независимых слайдеров — они не будут конфликтовать.

2. Храним текущий индекс

currentIndex — это номер активного слайда. Простая переменная, вокруг которой строится вся логика. Именно она управляет положением трека и состоянием точек.

3. Сдвигаем трек

Формула простая:

track.style.transform = `translateX(-${currentIndex * 100}%)`;

Если активен второй слайд, трек смещается на -100%. Если третий — на -200%. Это работает именно потому, что каждый слайд занимает 100% ширины видимой области.

4. Обновляем точки

Когда слайд меняется, состояние кнопок должно меняться вместе с ним. Иначе пользователь не поймёт, где сейчас находится. Это критично для UX: визуальная обратная связь не должна отставать. Мы просто переключаем класс slider__dot--active у нужной точки.

5. Делаем зацикливание

Операция % slides.length позволяет после последнего слайда перейти к первому. Это удобно для баннеров и промоблоков. Решение простое, но для бесконечной прокрутки с плавным переходом (когда визуально слайды идут по кругу без рывка) потребуется чуть больше кода — но для большинства задач достаточно и такого зацикливания.

Улучшаем слайдер без усложнения кода

Базовый слайдер — это фундамент. Дальше можно добавлять улучшения по мере необходимости, не перегружая код. Вот что чаще всего пригождается в реальных проектах:

Улучшение Зачем нужно Сложность
Клавиатурная навигация Удобство для пользователей и доступность Низкая
Свайп на тач-устройствах Лучше работает на мобильных Средняя
Автопрокрутка Подходит для промо и баннеров Низкая
Пауза при наведении Не раздражает пользователя Низкая
Переход к конкретному слайду Удобно для точек и табов Низкая
Адаптивная высота Убирает лишние пустоты Средняя

Добавляем клавиатуру

Управление с клавиатуры — не просто приятное дополнение, а часть доступности. Если слайдер содержит важный контент, пользователь должен иметь возможность листать его стрелками. Это особенно полезно, когда слайдер участвует в сценарии просмотра, а не просто крутит картинки.

document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowLeft') prevSlide();
  if (e.key === 'ArrowRight') nextSlide();
});

Лучше вешать обработчик на сам слайдер или его обёртку, чтобы не реагировать на стрелки, когда фокус вне компонента. Но для простоты можно начать с документа, а потом уточнить.

Добавляем автопрокрутку

Автопрокрутка — палка о двух концах. Для баннеров и промо-блоков она работает отлично, но для контента, который пользователь изучает, лучше дать управление в руки человека. В любом случае реализация проста:

let autoplayInterval;

function startAutoplay() {
  autoplayInterval = setInterval(nextSlide, 3000);
}

function stopAutoplay() {
  clearInterval(autoplayInterval);
}

startAutoplay();

Чтобы не мешать пользователю, лучше ставить паузу при взаимодействии — например, при наведении или фокусе:

slider.addEventListener('mouseenter', stopAutoplay);
slider.addEventListener('mouseleave', startAutoplay);

Так слайдер не будет переключаться, когда человек хочет рассмотреть содержимое.

Что важно для удобства и доступности

Даже простой слайдер должен быть понятным для пользователя. Часто разработчики концентрируются на визуале, забывая о поведении, а потом удивляются, почему компонент неудобен. Вот минимальный чек-лист, который стоит держать в голове:

  • у кнопок должны быть понятные подписи (хотя бы aria-label), а не просто стрелки;
  • активный слайд должен быть логически выделен — не только точкой, но и, например, фокусом для скринридеров;
  • точки должны быть настоящими кнопками (<button>), а не div — иначе они не попадают в фокус и не кликабельны с клавиатуры;
  • фокус не должен теряться при переключении — если пользователь перешёл кнопкой, фокус может оставаться на ней или перемещаться на новый слайд;
  • переключение не должно происходить слишком быстро — анимация в 300–400 мс комфортна, а автопрокрутка быстрее 2–3 секунд раздражает;
  • автопрокрутку лучше можно остановить — и обязательно останавливать при любом ручном взаимодействии.

Типовая ошибка

Часто делают слайдер только визуально, забывая про поведение:

  • кнопки без type="button" внутри формы — при нажатии может отправиться форма;
  • точки без aria-label — скринридеры молчат;
  • переключение без синхронизации состояния — трек уже сдвинулся, а точки или индикатор остались в старом состоянии;
  • слишком резкая анимация — transition: 0s или мгновенный рывок;
  • одинаковая логика для десктопа и мобильного без проверки — на тач-устройствах может не хватать свайпа или, наоборот, кнопки слишком мелкие.

В результате компонент вроде бы работает, но пользоваться им неудобно. Проверяйте эти моменты сразу, а не после жалоб.

Свайп: когда он нужен и как не переусложнить

Свайп нужен в основном на мобильных. Но для простого слайдера его не обязательно делать сразу. Если задача — быстро собрать рабочий компонент, начните с кнопок и точек. Свайп можно добавить позже, когда базовая механика отлажена.

Практичный подход такой:

  • сначала делаете переключение по кнопкам — это основа;
  • потом добавляете свайп на pointer-событиях (touchstart, touchmove, touchend) — они универсальнее для мыши и тачпада;
  • тестируете на реальном телефоне и тачпаде — эмулятор не всегда показывает нюансы;
  • проверяете, не ломается ли кликабельность внутри слайда — если слайд содержит кнопки, ссылки или формы, свайп требует особой аккуратности, чтобы не перехватывать обычные клики.

Часто свайп переоценивают: на десктопе он не нужен, а на мобильных кнопки могут быть удобнее, если слайдов мало. Не усложняйте раньше времени.

Когда лучше не писать свой слайдер

Самописный компонент — не универсальный ответ. Иногда готовый плагин действительно лучше. Всё зависит от контекста проекта и команды.

Лучше взять готовое решение, если:

  • нужен сложный бесконечный слайдер с нестандартной физикой (например, parallax-эффекты или инерция);
  • требуется много жестов и анимаций, которые уже отлажены в библиотеке;
  • проект должен быть быстро запущен без разработки компонента — время важнее гибкости;
  • у команды нет времени поддерживать свой код — готовый плагин обновляется сообществом;
  • нужен большой набор edge-case’ов, уже обработанных библиотекой (например, работа в старых браузерах или нестандартные устройства).

Лучше писать свой слайдер, если:

  • нужен компактный и контролируемый код — никакого лишнего;
  • важна простота дальнейшей доработки — свой код всегда понятнее;
  • слайдер должен подчиняться дизайну проекта, а не наоборот;
  • вы хотите понимать каждую часть компонента — это особенно ценно, когда проект растёт;
  • проект развивается как продукт, а не как шаблон «поставил и забыл».

Пошаговый план разработки

Ниже — удобная схема, если делать слайдер с нуля. На каждом шаге я добавил типовые грабли, чтобы вы их обошли.

Шаг 1. Сверстать структуру

  • обёртка;
  • viewport (видимая область);
  • track (движущаяся лента);
  • slides (слайды);
  • buttons (кнопки «вперёд/назад»);
  • dots (точки-индикаторы).

Не забудьте type="button" у кнопок, чтобы они не сабмитили форму, если слайдер окажется внутри <form>.

Шаг 2. Написать CSS

  • скрыть лишнее через overflow: hidden у viewport;
  • расположить слайды в ряд (flexbox);
  • добавить transition для плавности;
  • задать адаптивные размеры — слайды должны тянуться по ширине контейнера.

Проверьте, чтобы у слайдов не было лишних отступов, которые ломают расчёт 100%.

Шаг 3. Подключить базовый JS

  • найти DOM-элементы;
  • завести currentIndex;
  • сделать функции nextSlide и prevSlide;
  • реализовать updateSlider.

На этом этапе слайдер уже должен переключаться.

Шаг 4. Добавить точки

  • сгенерировать кнопки по количеству слайдов;
  • подсвечивать активную;
  • сделать переход по клику на точку.

Точки должны быть кнопками, иначе они не работают с клавиатуры.

Шаг 5. Проверить поведение

  • переключение туда и обратно;
  • зацикливание (последний → первый и наоборот);
  • работа на мобильных (ресайз, тач);
  • отсутствие ошибок в консоли;
  • корректное поведение при одном слайде (кнопки можно скрыть, точки не нужны).

Если слайд один, слайдер вырождается в статичный блок — это нормально, но код не должен падать.

Как тестировать слайдер перед публикацией

Перед тем как ставить компонент в проект, проверьте его на реальных сценариях. Эмуляторы и десктопный браузер не покажут всех проблем. Вот чек-лист, который я использую:

  • слайдер открывается без ошибок в консоли;
  • кнопки работают в обе стороны, даже если быстро кликать;
  • точки совпадают с активным слайдом при любом переходе;
  • на мобильном ширина не «прыгает» при смене ориентации;
  • контент внутри слайда не ломает вёрстку (длинные слова, большие изображения);
  • автопрокрутка не мешает взаимодействию — останавливается при наведении и не запускается, пока пользователь листает вручную;
  • компонент не конфликтует с другими скриптами на странице (проверьте, не переопределяются ли глобальные переменные).

Практический вывод

Писать свой простой слайдер вместо готового плагина имеет смысл тогда, когда вам нужен понятный, лёгкий и управляемый компонент. В базовой версии он собирается из трека, слайдов, кнопок и нескольких функций JavaScript. Этого достаточно, чтобы закрыть большинство типовых задач: переключение, точки, зацикливание, автопрокрутку и клавиатурное управление.

Главная идея здесь не в том, чтобы изобрести сложный движок, а в том, чтобы сделать маленький, надёжный и удобный для развития компонент. Именно такой подход хорошо работает в проектах, где шаблон нужно постепенно превращать в живой веб-продукт. Вы не просто получаете слайдер — вы учитесь мыслить компонентами и понимать, как устроены даже более сложные интерфейсы.

FAQ

Можно ли сделать слайдер вообще без JavaScript?

Формально да, но это будет не интерактивный слайдер, а скорее вёрстка с CSS-переключением (например, через :target или radio-кнопки). Для нормального управления — кнопки, точки, клавиатура — JavaScript необходим. Без него вы теряете гибкость и доступность.

Что проще: translateX или scroll-snap?

Для базового слайдера проще translateX. Вы полностью контролируете смещение и анимацию. scroll-snap удобен для свайповых интерфейсов, но управлять логикой (точки, зацикливание, программный переход) там сложнее — приходится работать с положением прокрутки, а не с индексом.

Сколько слайдов можно добавить?

С технической точки зрения — сколько угодно, но на практике лучше держать слайдер коротким. Если контента слишком много, пользователь быстрее устаёт, а производительность может упасть (особенно если внутри слайдов тяжёлые изображения). Для длинных последовательностей лучше подумать о другом паттерне — например, сетке или ленивой подгрузке.

Нужна ли библиотека для простого слайдера?

Нет, если задача типовая. Для базового переключения библиотека часто не нужна — вы только добавите лишний вес и зависимость. Библиотеки вроде Swiper или Splide имеет смысл подключать, когда нужны сложные эффекты, виртуальные слайды или тонкая настройка жестов.

Какой самый частый баг в самописном слайдере?

Чаще всего забывают синхронизировать состояние: трек уже сдвинулся, а точки, кнопки или индикатор остались в старом состоянии. Это сбивает пользователя. Решается просто: всегда обновлять всё в одной функции (updateSlider), которая вызывается при любом изменении индекса.