Как сделать вот такой слайдер?

Отличный вопрос! Создание слайдера — одна из классических задач в веб-разработке. Я подробно разберу, как создать адаптивный, интерактивный и доступный слайдер изображений с нуля на чистом JavaScript, HTML и CSS.

Мы создадим слайдер со следующими функциями:

  • Автопрокрутка
  • Кнопки "Вперед/Назад"
  • Индикаторные точки (пагинация)
  • Бесконечная петля
  • Пауза при наведении
  • Плавные переходы
  • Адаптивность

---

План реализации

  1. HTML-структура: Каркас слайдера.
  2. CSS-стилизация: Внешний вид, позиционирование, анимации.
  3. JavaScript-логика:
  • Инициализация переменных.
  • Функции для переключения слайдов.
  • Обработчики событий для кнопок и точек.
  • Логика автопрокрутки.
  • Реализация бесконечной петли.

---

1. HTML-разметка

Создадим базовую структуру. Мы поместим все слайды в общий контейнер. Для бесконечной петли мы добавим клоны первого и последнего слайда.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Кастомный слайдер</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="slider-container">
        <!-- Контейнер для слайдов -->
        <div class="slider-track" id="sliderTrack">
            <!-- Клон последнего слайда (для бесконечности) -->
            <div class="slide"><img src="image3.jpg" alt="Описание изображения 3"></div>
            <!-- Оригинальные слайды -->
            <div class="slide"><img src="image1.jpg" alt="Описание изображения 1"></div>
            <div class="slide"><img src="image2.jpg" alt="Описание изображения 2"></div>
            <div class="slide"><img src="image3.jpg" alt="Описание изображения 3"></div>
            <!-- Клон первого слайда (для бесконечности) -->
            <div class="slide"><img src="image1.jpg" alt="Описание изображения 1"></div>
        </div>

        <!-- Кнопки навигации -->
        <button class="slider-btn prev-btn" id="prevBtn" aria-label="Предыдущий слайд">‹</button>
        <button class="slider-btn next-btn" id="nextBtn" aria-label="Следующий слайд">›</button>

        <!-- Индикаторные точки (пагинация) -->
        <div class="slider-dots" id="sliderDots">
            <!-- Точки будут сгенерированы через JavaScript -->
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>

2. CSS-стили

Стили обеспечат внешний вид, плавные переходы и скрытие неактивных слайдов. Ключевой момент — использование transform: translateX() для горизонтальной прокрутки.

/* style.css */
body {
    margin: 0;
    font-family: sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #f0f0f0;
}

.slider-container {
    position: relative;
    width: 80%;
    max-width: 800px;
    overflow: hidden; /* Скрываем всё, что выходит за границы */
    border-radius: 10px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}

.slider-track {
    display: flex;
    transition: transform 0.5s ease-in-out; /* Плавная анимация сдвига */
    will-change: transform; /* Подсказка браузеру для оптимизации */
}

.slide {
    flex: 0 0 100%; /* Каждый слайд занимает 100% ширины контейнера */
    box-sizing: border-box;
}

.slide img {
    width: 100%;
    height: auto;
    display: block;
}

/* Стили для кнопок навигации */
.slider-btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(0, 0, 0, 0.5);
    color: white;
    border: none;
    font-size: 2rem;
    padding: 10px 15px;
    cursor: pointer;
    border-radius: 5px;
    z-index: 10;
    transition: background-color 0.3s ease;
}

.slider-btn:hover {
    background: rgba(0, 0, 0, 0.8);
}

.prev-btn {
    left: 10px;
}

.next-btn {
    right: 10px;
}

/* Стили для индикаторных точек */
.slider-dots {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 10px;
    z-index: 10;
}

.dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.5);
    border: none;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

.dot.active {
    background-color: white;
}

.dot:hover {
    background-color: rgba(255, 255, 255, 0.8);
}

/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
    .slider-container {
        width: 95%;
    }
    .slider-btn {
        font-size: 1.5rem;
        padding: 8px 12px;
    }
}

3. JavaScript-логика

Это самая важная часть. Мы реализуем всю интерактивность.

// script.js
document.addEventListener('DOMContentLoaded', function() {
    // Элементы DOM
    const track = document.getElementById('sliderTrack');
    const slides = document.querySelectorAll('.slide');
    const prevBtn = document.getElementById('prevBtn');
    const nextBtn = document.getElementById('nextBtn');
    const dotsContainer = document.getElementById('sliderDots');

    // Переменные состояния
    let currentSlideIndex = 1; // Начинаем с первого оригинального слайда (индекс 1, т.к. 0 - это клон последнего)
    let slideWidth = slides[0].clientWidth; // Ширина одного слайда
    let autoScrollInterval;
    const autoScrollDelay = 4000; // 4 секунды

    // Инициализация: создание точек-индикаторов
    function createDots() {
        // Количество оригинальных слайдов (минус 2 клона)
        const originalSlidesCount = slides.length - 2;

        for (let i = 0; i < originalSlidesCount; i++) {
            const dot = document.createElement('button');
            dot.classList.add('dot');
            dot.setAttribute('aria-label', `Перейти к слайду ${i + 1}`);
            dot.addEventListener('click', () => goToSlide(i + 1)); // +1 потому что первый слайд у нас под индексом 1
            dotsContainer.appendChild(dot);
        }
        updateDots();
    }

    // Обновление активной точки
    function updateDots() {
        const dots = document.querySelectorAll('.dot');
        const originalIndex = getOriginalSlideIndex();

        dots.forEach((dot, index) => {
            dot.classList.toggle('active', index === originalIndex);
        });
    }

    // Получить индекс слайда в оригинальной последовательности (без клонов)
    function getOriginalSlideIndex() {
        const totalOriginalSlides = slides.length - 2;

        if (currentSlideIndex === 0) {
            // Если мы на клоне последнего слайда, показываем последний оригинальный
            return totalOriginalSlides - 1;
        } else if (currentSlideIndex === slides.length - 1) {
            // Если мы на клоне первого слайда, показываем первый оригинальный
            return 0;
        } else {
            // Для оригинальных слайдов
            return currentSlideIndex - 1;
        }
    }

    // Функция перехода к конкретному слайду
    function goToSlide(index) {
        // Отключаем переход, если мы уже на нужном слайде
        if (index === currentSlideIndex) return;

        currentSlideIndex = index;
        updateSliderPosition();
        updateDots();
        resetAutoScroll();
    }

    // Обновление позиции слайдера
    function updateSliderPosition() {
        const offset = -currentSlideIndex * slideWidth;
        track.style.transform = `translateX(${offset}px)`;
    }

    // Функция для бесшовного перехода в начале/конце
    function checkSlideIndex() {
        const totalSlides = slides.length;

        // Если мы на клоне последнего слайда (индекс 0), после анимации мгновенно переходим к настоящему последнему слайду
        if (currentSlideIndex === 0) {
            // Временно отключаем анимацию для мгновенного перехода
            track.style.transition = 'none';
            currentSlideIndex = totalSlides - 2; // Последний оригинальный слайд
            updateSliderPosition();

            // Включаем анимацию обратно после рефлоу
            setTimeout(() => {
                track.style.transition = 'transform 0.5s ease-in-out';
            }, 0);
        }
        // Если мы на клоне первого слайда (последний индекс), после анимации мгновенно переходим к настоящему первому слайду
        else if (currentSlideIndex === totalSlides - 1) {
            track.style.transition = 'none';
            currentSlideIndex = 1; // Первый оригинальный слайд
            updateSliderPosition();

            setTimeout(() => {
                track.style.transition = 'transform 0.5s ease-in-out';
            }, 0);
        }
    }

    // Переход к следующему слайду
    function nextSlide() {
        currentSlideIndex++;
        updateSliderPosition();
        updateDots();

        // После завершения CSS-перехода проверяем, не нужно ли сделать бесшовный переход
        track.addEventListener('transitionend', checkSlideIndex, { once: true });
        resetAutoScroll();
    }

    // Переход к предыдущему слайду
    function prevSlide() {
        currentSlideIndex--;
        updateSliderPosition();
        updateDots();
        track.addEventListener('transitionend', checkSlideIndex, { once: true });
        resetAutoScroll();
    }

    // Автопрокрутка
    function startAutoScroll() {
        autoScrollInterval = setInterval(nextSlide, autoScrollDelay);
    }

    function stopAutoScroll() {
        clearInterval(autoScrollInterval);
    }

    function resetAutoScroll() {
        stopAutoScroll();
        startAutoScroll();
    }

    // Обработчики событий
    prevBtn.addEventListener('click', prevSlide);
    nextBtn.addEventListener('click', nextSlide);

    // Пауза при наведении
    track.addEventListener('mouseenter', stopAutoScroll);
    track.addEventListener('mouseleave', startAutoScroll);

    // Обработка изменения размера окна (для адаптивности)
    window.addEventListener('resize', () => {
        slideWidth = slides[0].clientWidth;
        updateSliderPosition(); // Пересчитываем позицию при изменении размера
    });

    // Инициализация слайдера
    function initSlider() {
        slideWidth = slides[0].clientWidth;
        createDots();
        updateSliderPosition(); // Устанавливаем начальную позицию
        startAutoScroll(); // Запускаем автопрокрутку
    }

    // Запускаем инициализацию после загрузки DOM
    initSlider();
});

---

Ключевые моменты и объяснения

  1. Бесконечная петля: Мы добавили клоны первого и последнего слайда в начало и конец. Когда слайдер доходит до клона, он мгновенно (без анимации) перескакивает на соответствующий оригинальный слайд, создавая иллюзию бесконечности.
  1. transform: translateX(): Это самый производительный способ анимировать перемещение слайдов, так как он задействует аппаратное ускорение GPU.
  1. Событие transitionend: Мы используем его для проверки индекса слайда после завершения анимации, чтобы бесшовный переход работал корректно.
  1. flex: 0 0 100%: Это правило CSS гарантирует, что каждый слайд будет занимать ровно 100% ширины родительского контейнера.
  1. Доступность (a11y): Мы добавили атрибуты aria-label для кнопок, чтобы скринридеры могли их правильно озвучить.
  1. Оптимизация: will-change: transform подсказывает браузеру, что элемент будет анимирован, что может улучшить производительность.

---

Альтернативные подходы и улучшения

  1. Библиотеки: Для сложных проектов рассмотрите использование готовых библиотек:
  • Swiper.js: Очень мощная, с множеством опций.
  • Slick: Популярная, но устаревшая.
  • Glide.js: Легкая и простая.
  1. Ленивая загрузка (Lazy Loading): Для слайдеров с большим количеством тяжелых изображений можно реализовать загрузку изображений только когда они вот-вот станут видимыми.
  1. Анимации появления: Добавьте различные эффекты перехода ( fade, zoom, flip) вместо простого сдвига.
  1. Предзагрузка изображений: Чтобы избежать мигания при первом показе, можно предзагрузить все изображения.
  1. Touch-события: Для мобильных устройств добавьте обработку свайпов с помощью touchstart, touchmove и touchend.

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