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

Разметка
По HTML все достаточно просто. Мы имеем общий статичный родительский компонент, внутри которого лежит контейнер, который мы и будем прокручивать. Внутри динамичного контейнера размещаются слайды с вашим содержимым:
<div class="slider">
<div class="slider-track">
<div class="slide">
<img src="images/slide-1.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-2.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-3.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-4.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-1.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-2.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-3.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-4.jpeg" alt="">
</div>
</div>
</div>
Стоит сразу сказать, что если слайдов у вас мало, то на больших экранах могут образоваться большие пустые пространства. Поэтому, если требуется, можете продублировать существующие слайды.
Стилизация
По стилям тоже немного. Статичный контейнер должен скрывать содержимое за своими пределами, чтобы не появлялся горизонтальный скролл. Динамичный контейнер будет располагать элементы в одну линию. Также важно заметить, что не стоит добавлять отступы с помощью gap
, так как это может вызывать разрывы анимации во время дублирования контента. Вместо этого стоит использовать margin-right
у самих слайдов.
.slider {
overflow-x: hidden;
}
.slider-track {
display: flex;
align-items: center;
}
.slide {
margin-right: 40px;
/* Для избежания сжатия слайдов */
flex-shrink: 0;
}
.slide img {
height: 200px;
width: 350px;
object-fit: cover;
border-radius: 10px;
}
Логика
Самое интересное начинается здесь. Первым делом я создам пустую функцию, которая будет в будущем запускать анимацию:
const initSlider = () => {
// Реализация
}
initSlider();
Внутри функции запишем в переменные DOM‑элементы слайдера и динамичной полосы, а также объявим скорость слайдера по умолчанию:
const DEFAULT_SPEED = 2; // Количество пикселей для смещения
const slider = document.querySelector(".slider");
// Предотвращение ошибок в случае отсутсвия слайдера на странице
if (!slider) return;
const wrapper = document.querySelector(".slider-track");
wrapper.innerHTML += wrapper.innerHTML;
Теперь будем редактировать позицию ленты. Здесь логика немного сложнее:
Объявим переменную
position = 0
, указывающую на текущее положение ленты.Будем уменьшать позицию для движения ленты влево или увеличивать для движения вправо.
После уменьшения позиции сразу же передвинем динамичную ленту на
position
пикселей.Передвижение ленты вызывает перерисовку экрана, и после этой перерисовки мы снова должны выполнить все операции выше.
Напишем базовую функцию для первых 3 шагов внутри нашей же функции и сразу вызовем ее один раз:
let position = 0;
function animate() {
// Изменение позиции
position -= DEFAULT_SPEED;
// Если элемент ушел слишком далеко (независимо от направления),
// то возвращаем позицию в исходное положение
if (Math.abs(position) >= wrapper.scrollWidth / 2) {
position = 0;
}
// Сдвиг ленты на измененную позицию
wrapper.style.transform = `translateX(${position}px)`;
}
animate();
Теперь происходит главная магия. Для вызова этой же функции во время перерисовки экрана будем использовать встроенную функцию requestAnimationFrame()
, куда передадим эту же функцию animate
. Тем самым мы создадим бесконечный цикл, который будет передвигать ленту в нужную сторону. При этом этот цикл не сломает сайт, как это сделал бы цикл while
, так как наш цикл является асинхронным. По итогу получаем:
function animate() {
position -= speed;
if (Math.abs(position) >= wrapper.scrollWidth / 2) {
position = 0;
}
wrapper.style.transform = `translateX(${position}px)`;
requestAnimationFrame(animate);
}
Замедление ленты при наведении
Для остановки ленты достаточно изменять скорость по умолчанию и возвращать обратно при отведении мыши с элемента:
let speed = DEFAULT_SPEED;
slider.addEventListener("mouseenter", () => {
// Замедляем в 2 раза
speed = DEFAULT_SPEED / 2;
});
slider.addEventListener("mouseleave", () => {
speed = DEFAULT_SPEED;
});
Итог
В результате получаем следующий полный код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.slider {
overflow-x: hidden;
}
.slider-track {
display: flex;
align-items: center;
}
.slide {
margin-right: 64px;
flex-shrink: 0
}
</style>
</head>
<body>
<div class="slider">
<div class="slider-track">
<div class="slide">
<img src="images/slide-1.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-2.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-3.jpeg" alt="">
</div>
<div class="slide">
<img src="images/slide-4.jpeg" alt="">
</div>
</div>
</div>
</body>
<script>
const initSlider = () => {
const DEFAULT_SPEED = 2;
const slider = document.querySelector(".slider");
if (!slider) return;
const wrapper = document.querySelector(".slider-track");
wrapper.innerHTML += wrapper.innerHTML;
let speed = DEFAULT_SPEED;
let position = 0;
slider.addEventListener("mouseenter", () => {
speed = DEFAULT_SPEED / 2;
});
slider.addEventListener("mouseleave", () => {
speed = DEFAULT_SPEED;
});
function animate() {
position -= speed;
if (Math.abs(position) >= wrapper.scrollWidth / 2) {
position = 0;
}
wrapper.style.transform = `translateX(${position}px)`;
requestAnimationFrame(animate);
}
animate();
}
initSlider();
</script>
</html>
Надеюсь эта статья помогла вам разобраться с логикой работы слайдера. В комментариях обязательно указывайте свои решения, которые вы считаете более оптимальными для создания такого слайдера.
Буду очень рад, если вы посетите мой Телеграм канал с другими полезными материалами и видео — t.me/sanwed_it
krote
margin-right: 64px;
это замечательно но...
Визуально при скролле влево, когда слева скрывается последний элемент и за ним к левой границе подходит снова первый, то он совершает небольшой скачок влево...
Или это у меня одного так?
Sanwed Автор
Именно с
margin-right
у самих слайдов такого скачка быть должно. Стоит убедиться, что flex-контейнер не используетgap
. Если с этим порядок, то можно отправить получившийся кодkrote
https://jsfiddle.net/rga4/w4yuve16/24/
Andy_Francev
Мне одному кажется, что анимация не выдаёт даже 60 FPS?
krote
Короче забавно но без скачков движется у меня только в Firefox. Проверил Opera, Edge, Chrome - скачет плюс минус одинаково. В хроме проверил на двух других компах даже - эффект есть.