Карусель на JS — это лента карточек, которую скрипт прокручивает к нужному кадру. Рабочий минимум 2026 года выглядит так: ленту держит CSS scroll snap, а JavaScript только вызывает scrollTo и следит за активной карточкой через IntersectionObserver. Свайп, инерция и плавность при этом достаются от браузера, а не пишутся руками. Ниже — полный код на примере карусели отзывов, сравнение с классическим вариантом на transform и замер обоих в headless Chrome.

Коротко, что даёт этот подход:

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

Статья переписана в августе 2026 года: код прошлой версии заменён, поведение старого и нового вариантов измерено в headless Chromium 145, версии библиотек сверены с реестром npm.

Три способа сделать карусель на JS

Как устроена карусель на scroll-snap: что делает браузер, а что JavaScript
Как устроена карусель на scroll-snap: что делает браузер, а что JavaScript

Под словом «карусель» в вёрстке понимают разное, и от этого зависит выбор способа. Определение из паттерна W3C ARIA APG: «A carousel presents a set of items, referred to as slides, by sequentially displaying a subset of one or more slides». То есть кадров может быть видно и один, и три — это одна и та же сущность. Подробный разбор терминов и чисто CSS-варианты мы собрали в отдельном материале про карусели и слайдеры в вёрстке.

Способ Что двигает кадры Свайп и клавиатура Когда брать
Классический: transform + счётчик JS считает смещение ленты и пишет его в translateX Только своими обработчиками touchstart и keydown Нужен эффект, которого нет у прокрутки: 3D-поворот, наплыв, карточки-веером
Прокрутка + scroll snap (в этой статье) Браузер: scrollTo на ленте, прилипание кадров — CSS Из коробки: палец, колесо, стрелки клавиатуры Ленты отзывов, товаров, логотипов, фото — то есть почти всё
Готовая библиотека Код библиотеки Из коробки плюс режимы вроде бесконечной ленты Нужны миниатюры, зацикливание, синхронные ленты, вертикальный режим

Второй способ стал рабочим по умолчанию, потому что нужные свойства давно поддерживаются везде. По данным MDN, scroll-snap-type имеет статус Baseline Widely available и доступно в браузерах с апреля 2022 года, scroll-behavior — с марта 2022 года, IntersectionObserver — с марта 2019 года, медиазапрос prefers-reduced-motion — с января 2020 года.

Мини-вывод: если карусель нужна для показа контента, а не для спецэффекта, писать математику смещений в 2026 году не нужно — эту работу уже делает движок браузера.

ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
  • Где и как применять? Потестируем модель после установки на разных задачах
  • Как дообучить модель под себя?

Инструкция

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

Шаг 1: подготовка HTML-структуры

Нужны три уровня: секция-обёртка, лента со слайдами и панель управления. Атрибуты role, aria-roledescription и aria-label на обёртке — не украшение: по паттерну W3C ARIA APG контейнер карусели «has either role region or role group» и «has the aria-roledescription property set to carousel». Ленте даём tabindex="0", чтобы её можно было листать с клавиатуры.

<section class="reviews" role="group" aria-roledescription="carousel" aria-label="Отзывы клиентов"> <div class="reviews__track" tabindex="0" aria-label="Лента отзывов" aria-atomic="false" aria-live="off"> <div class="reviews__slide" role="group" aria-roledescription="slide" aria-label="1 из 3"> <img src="/img/user-1.webp" alt="" width="64" height="64"> <blockquote>Собрали лендинг за выходные, первые заявки пришли на третий день.</blockquote> <cite>Анна, студия ремонта</cite> </div> <div class="reviews__slide" role="group" aria-roledescription="slide" aria-label="2 из 3"> <img src="/img/user-2.webp" alt="" width="64" height="64"> <blockquote>Раньше отзывы лежали простынёй, теперь их читают до конца.</blockquote> <cite>Игорь, интернет-магазин</cite> </div> <div class="reviews__slide" role="group" aria-roledescription="slide" aria-label="3 из 3"> <img src="/img/user-3.webp" alt="" width="64" height="64"> <blockquote>Карусель собрал сам за вечер, разработчика не звал.</blockquote> <cite>Мария, онлайн-школа</cite> </div> </div> <div class="reviews__nav"> <button type="button" class="reviews__arrow" data-step="-1" aria-label="Предыдущий отзыв">&#8592;</button> <div class="reviews__dots"></div> <button type="button" class="reviews__arrow" data-step="1" aria-label="Следующий отзыв">&#8594;</button> <button type="button" class="reviews__toggle" aria-pressed="false">Пауза</button> </div> </section>

Точки-индикаторы в разметке пустые: их создаст JavaScript по числу слайдов. Так добавление четвёртого отзыва не требует правки HTML в двух местах.

Шаг 2: оформление с CSS

Здесь и живёт главное отличие от старых руководств. Лента — обычный горизонтальный скролл-контейнер, а прилипание кадров даёт CSS scroll snap: scroll-snap-type на контейнере и scroll-snap-align на карточках. Свойство scroll-behavior: smooth отвечает за плавность — по описанию MDN «the scrolling box scrolls in a smooth fashion using a user-agent-defined easing function».

.reviews { position: relative; } .reviews__track { position: relative; /* точка отсчёта для offsetLeft */ display: flex; gap: 16px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; scrollbar-width: none; /* полосу прячем, прокрутка остаётся */ } .reviews__track::-webkit-scrollbar { display: none; } .reviews__slide { flex: 0 0 100%; min-width: 0; /* иначе длинное слово раздувает карточку */ box-sizing: border-box; scroll-snap-align: start; padding: 24px; background: #fff; border-radius: 16px; } .reviews__slide img { border-radius: 50%; } @media (min-width: 768px) { .reviews__slide { flex: 0 0 calc((100% - 32px) / 3); } } @media (prefers-reduced-motion: reduce) { .reviews__track { scroll-behavior: auto; } }

Четыре момента, на которых спотыкаются чаще всего.

  • position: relative на ленте. От неё JavaScript считает offsetLeft карточки. Без этого отсчёт уедет к ближайшему позиционированному предку выше по дереву, и прокрутка будет промахиваться.
  • Гэп не входит в проценты. Три карточки по 33,333% при gap: 16px в кадр не влезают: промежутки добавляются между карточками сверх ширины. Поэтому calc((100% - 32px) / 3), где 32 px — два промежутка по 16 px.
  • min-width: 0 на карточке. У флекс-элемента минимальная ширина по умолчанию равна ширине самого длинного слова внутри. Пока карточка одна на кадр, это незаметно, но в раскладке на три карточки размеры разъезжаются — насколько именно, показывает замер ниже.
  • Медиазапрос prefers-reduced-motion. Одна строка отменяет плавную прокрутку для тех, кто отключил анимации в системе. JavaScript при этом не меняется: он вызывает прокрутку без указания режима, а режим берётся из CSS.

Мини-вывод: вся физика движения — ширина кадра, прилипание, плавность, реакция на настройки доступности — задана в CSS. JavaScript останется тонким слоем управления.

Шаг 3: создание карусели с JavaScript

JavaScript делает ровно три вещи: рисует точки, прокручивает ленту к нужной карточке и следит за тем, какая карточка сейчас в кадре. Никаких вычислений transform, никаких обработчиков касаний.

const carousel = document.querySelector('.reviews'); const track = carousel.querySelector('.reviews__track'); const dots = carousel.querySelector('.reviews__dots'); const slides = Array.from(track.children); let current = 0; slides.forEach((slide, i) => { const dot = document.createElement('button'); dot.type = 'button'; dot.className = 'reviews__dot'; dot.setAttribute('aria-label', `Отзыв ${i + 1} из ${slides.length}`); dot.addEventListener('click', () => goTo(i)); dots.append(dot); }); function goTo(index) { current = (index + slides.length) % slides.length; track.scrollTo({ left: slides[current].offsetLeft }); } carousel.querySelectorAll('.reviews__arrow').forEach((btn) => { btn.addEventListener('click', () => goTo(current + Number(btn.dataset.step))); }); const visible = new Set(); const io = new IntersectionObserver((entries) => { for (const entry of entries) { const i = slides.indexOf(entry.target); entry.isIntersecting ? visible.add(i) : visible.delete(i); } if (!visible.size) return; current = Math.min(...visible); dots.querySelectorAll('.reviews__dot').forEach((dot, i) => { dot.classList.toggle('is-active', i === current); dot.setAttribute('aria-current', String(i === current)); }); }, { root: track, threshold: 0.6 }); slides.forEach((slide) => io.observe(slide));

Разберём узлы.

  • goTo кольцует индекс через остаток от деления, поэтому со стрелки «назад» на первой карточке пользователь попадает на последнюю, а не упирается в стену. В старой версии этой статьи такой карусели не получалось: там переключение обрывалось на границах.
  • track.scrollTo({ left }) вызывается без параметра behavior. Режим прокрутки берётся из CSS-свойства scroll-behavior — то есть плавность и её отключение при prefers-reduced-motion настраиваются в одном месте.
  • IntersectionObserver с корнем-лентой держит подсветку точек в синхроне при любом способе прокрутки: стрелки, точки, свайп пальцем, колесо мыши, клавиатура. Множество visible нужно, чтобы на широком экране, где видно три карточки сразу, активной считалась первая из них.

Шаг 4: автопрокрутка, которая не мешает

Автопрокрутка — самая частая причина, по которой карусель раздражает. Паттерн W3C ARIA APG задаёт три правила: у карусели «has a button for stopping and restarting rotation», она «stops rotating whenever the mouse is hovering over the carousel» и «stops rotating when keyboard focus enters the carousel. It does not restart unless the user explicitly requests it to do so».

const toggle = carousel.querySelector('.reviews__toggle'); const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; let timer = null; let paused = reduced; function start() { if (timer || paused) return; timer = setInterval(() => goTo(current + 1), 5000); track.setAttribute('aria-live', 'off'); } function pause() { clearInterval(timer); timer = null; track.setAttribute('aria-live', 'polite'); } function syncToggle() { toggle.setAttribute('aria-pressed', String(paused)); toggle.textContent = paused ? 'Запустить' : 'Пауза'; } toggle.addEventListener('click', () => { paused = !paused; syncToggle(); paused ? pause() : start(); }); carousel.addEventListener('mouseenter', pause); carousel.addEventListener('mouseleave', start); carousel.addEventListener('focusin', () => { paused = true; syncToggle(); pause(); }); document.addEventListener('visibilitychange', () => document.hidden ? pause() : start()); syncToggle(); paused ? pause() : start();

Что здесь важнее самого таймера:

  • Флаг paused отделяет временную остановку (мышь навели — увели) от осознанной. После того как в карусель зашёл фокус клавиатуры, лента сама не поедет: пользователь читает, а не догоняет уезжающий текст.
  • Атрибут aria-live переключается между off и polite. По APG у обёртки слайдов aria-live стоит в «off: if the carousel is automatically rotating» и в «polite: if the carousel is NOT automatically rotating» — пока лента едет сама, скринридер молчит, а после паузы озвучивает смену карточки.
  • Событие visibilitychange гасит таймер, когда вкладку свернули или переключили. Оно «is fired at the document when its visibility status changes» и доступно во всех браузерах с апреля 2021 года по данным MDN.
  • При включённой в системе экономии движения paused стартует со значением true — автопрокрутки не будет вовсе, кнопка сразу показывает «Запустить».

Мини-вывод: карусель уважает три сигнала пользователя — мышь, фокус и вкладку. Это тот минимум, после которого автопрокрутка перестаёт быть проблемой доступности.

Шаг 5: использование jQuery и готовых библиотек (опционально)

Свой код или библиотека: как выбрать за три вопроса
Свой код или библиотека: как выбрать за три вопроса

Прошлая версия этой статьи предлагала подключить jQuery и плагин Slick Slider. Совет устарел, и это видно по реестру npm: последняя версия пакета slick-carousel — 1.8.1, опубликована 3 октября 2017 года, в её peerDependencies стоит jquery >=1.8.0. То есть ради карусели на страницу тянется библиотека, без которой современный проект обходится. Если Slick уже стоит в проекте, разбор его настроек у нас есть в руководстве по слайдерам на Slick Carousel.

Актуальная замена — Swiper: на 28 июля 2026 года в npm лежит версия 14.0.7, у неё пустые dependencies и peerDependencies, то есть внешних зависимостей нет вовсе. Подключение занимает несколько строк:

import Swiper from 'swiper'; import 'swiper/css'; new Swiper('.reviews', { slidesPerView: 1, spaceBetween: 16, breakpoints: { 768: { slidesPerView: 3 } }, });

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

ОБЗОРНЫЙ ПРАКТИКУМ ПО НАШУМЕВШИМ НЕЙРОСЕТЯМ
Нейросети DEEPSEEK И QWEN За 2 часа сделаем полный обзор новых мощных ИИ-моделей, которые бросают вызов нейросети ChatGPT
ТОП-подарки всем участникам лекции:
  • Возможность получить Доступ в Нейроклуб на целый месяц
  • Как ИИ ускоряет работу и приносит деньги
  • За 2 часа вы получите четкий план, как начать работать с ИИ прямо сейчас!

Что показал прогон кода в headless Chrome

Мы не сравнивали варианты на глаз. Скрипт замера выпилил HTML, CSS и JavaScript прямо из текста этой статьи и из прошлой её версии, собрал две страницы и прогнал их в headless Chromium 145 через Playwright. Сцена — контейнер шириной 600 px, отступ между карточками 16 px. Ниже цифры этого прогона.

Проверка (сцена 600 px) Код прошлой версии Код из этой статьи
Первый кадр: сколько видно активной карточки 600 px из 600 600 px из 600
Второй кадр: сколько видно активной карточки 0 px из 600 600 px из 600
Кнопка «Предыдущий» на первой карточке сдвига нет переход на последнюю
Колесо мыши над лентой (400 px вправо) scrollLeft остался 0 scrollLeft стал 616
Стрелка клавиатуры на ленте в фокусе нет фокусируемой ленты scrollLeft 0 → 616

Разберём главную строку. Старая формула translateX(${100 * (i - index)}%) двигала каждый слайд на его собственную ширину, а не ленту целиком. На первом кадре это незаметно: карточка стоит в нуле, видно все 600 px. После переключения на второй кадр отступы карточек стали −640 и +640 px, и в окне 600 px не осталось ни одной: видимая ширина обеих — 0 px. Пользователь получал пустой прямоугольник вместо отзыва.

Заодно замер объяснил, почему карточка была шириной 640 px при контейнере 600 px: в старом CSS у слайда стоял padding: 20px без box-sizing: border-box, и padding добавлялся к ширине снаружи.

Ещё одна строка про клавиатуру и колесо: у старой ленты стоял overflow: hidden. По описанию MDN, прилипание кадров вообще требует включённой прокрутки — «you need to enable scrolling on a scroll container». С overflow: hidden прокрутки нет, поэтому колесо над старой лентой не сдвинуло её ни на пиксель, а свайп на телефоне не работал в принципе.

Три карточки в кадре: цена одной строки CSS

Раскладку «три карточки рядом» замерили отдельно, на сцене 600 px с gap: 16px. С правилом min-width: 0 все три карточки получились ровно по 189,33 px, встали по отступам 0 / 205,33 / 410,66 px, и правый край третьей пришёлся на 599,98 px — то есть кадр заполнен целиком. Тот же код без строки min-width: 0 дал карточки разной ширины: 199,8 / 206,03 / 218,69 px, отступы 0 / 215,8 / 437,83 px, а правый край третьей уехал на 656,52 px и вылез за кадр на 56,52 px. Ширину карточке в этом случае задаёт самое длинное слово в отзыве, а не вёрстка.

Индикаторы, клавиатура и экономия движения

Активную точку считает IntersectionObserver, поэтому она реагирует на любой способ прокрутки. В прогоне мы трижды меняли положение ленты мимо стрелок: программно перевели scrollLeft на третью карточку — активной стала третья точка; вернули в ноль — первая; прокрутили колесом на 400 px — лента прилипла к следующему кадру (scrollLeft 616), точка переключилась на вторую.

Клавиатура работает без отдельного кода: в tab-последовательности оказались семь элементов подряд — лента отзывов, «Предыдущий отзыв», три точки с подписями «Отзыв N из 3», «Следующий отзыв» и кнопка паузы. Стрелка вправо на сфокусированной ленте сдвинула её ровно на один кадр: scrollLeft с 0 на 616 px, то есть 600 px карточки плюс 16 px промежутка.

Режим экономии движения проверили эмуляцией системной настройки. Без неё вычисленное значение scroll-behavior у ленты — smooth, кнопка показывает «Пауза», aria-pressed="false", aria-live="off". С включённой настройкой то же самое место даёт scroll-behavior: auto, кнопку «Запустить», aria-pressed="true" и aria-live="polite": анимации нет, автопрокрутки нет, смену карточки скринридер объявляет.

Автопрокрутку гоняли реальным временем, интервал 5 секунд, окна по 11 секунд. За первое окно лента ушла на две карточки вперёд. С наведённым курсором — ноль переходов за 11 секунд. После увода курсора движение возобновилось. После входа фокуса клавиатуры лента остановилась, кнопка переключилась на «Запустить», aria-live стал polite, и за следующие 11 секунд без мыши на карусели она не сдвинулась. Поехала снова только после нажатия кнопки — ровно то поведение, которого требует APG.

Доступность карусели: чек-лист W3C

Карусель — один из немногих блоков, у которого есть официальный эталон поведения. Паттерн Carousel в W3C ARIA Authoring Practices Guide перечисляет требования дословно, и почти все они закрываются разметкой из шага 1 и скриптом из шага 4.

  • Контейнер карусели «has either role region or role group» — в примере стоит role="group".
  • Контейнер «has the aria-roledescription property set to carousel», а название даёт aria-label: скринридер произносит «Отзывы клиентов, карусель», а не «группа».
  • Каждый слайд получает aria-roledescription="slide" и подпись вида «2 из 3», чтобы был понятен объём ленты.
  • У обёртки слайдов aria-atomic="false" и aria-live: «off: if the carousel is automatically rotating», «polite: if the carousel is NOT automatically rotating». Наш скрипт переключает это значение сам.
  • Если лента едет сама, у неё «has a button for stopping and restarting rotation» — отдельная кнопка с aria-pressed.
  • Лента «stops rotating whenever the mouse is hovering over the carousel».
  • Лента «stops rotating when keyboard focus enters the carousel. It does not restart unless the user explicitly requests it to do so» — поэтому в коде отдельный флаг, а не просто снятие таймера.
  • Стрелки и точки — настоящие <button type="button">: клавиатурное поведение кнопки браузер даёт сам, JavaScript для этого не нужен.

Отдельным пунктом — уважение к системной настройке движения. Медиазапрос prefers-reduced-motion, по описанию MDN, «is used to detect if a user has enabled a setting on their device to minimize the amount of non-essential motion». Для карусели это значит две вещи: отключить плавную прокрутку и не запускать автопрокрутку вовсе.

Мини-вывод: доступность здесь не отдельный этап работы, а десяток атрибутов и один флаг в скрипте. И это самое пустое место в теме: 5 августа 2026 года мы скачали все десять страниц первой десятки Яндекса по запросу «карусель js» и прогнали поиск по их тексту — слова «клавиатура», «доступность», «скринридер», атрибут aria-roledescription и медиазапрос prefers-reduced-motion не встретились ни на одной.

Частые ошибки в каруселях на JS

Список собран из разбора старой версии этой статьи и того, что чаще всего приходится чинить в чужих каруселях.

  • Смещение считают в процентах от карточки, а не от ленты. Классическая ошибка старого кода: пока карточка одна на кадр, глаз не ловит, а на втором кадре в окне не остаётся ничего. Двигать нужно ленту, а не каждый слайд.
  • Забыт box-sizing: border-box. Карточка с внутренними отступами становится шире кадра, и край съедается. В замере выше это дало 640 px карточки при контейнере 600 px.
  • Проценты не учитывают gap. Промежутки прибавляются между карточками сверх их ширины, поэтому в раскладке на три нужна формула calc((100% - 32px) / 3).
  • Нет min-width: 0 у флекс-карточек. Ширину начинает диктовать самое длинное слово, кадры разъезжаются, прилипание промахивается.
  • Индекс хранится только в переменной. Стоит пользователю прокрутить ленту пальцем, и счётчик расходится с картинкой. Наблюдатель пересечений снимает проблему целиком.
  • Автопрокрутка без паузы. Лента, которая уезжает из-под курсора и из-под фокуса, делает контент нечитаемым, а для клавиатурного пользователя — недоступным.
  • Таймер продолжает крутиться в фоновой вкладке. Обработчик visibilitychange занимает одну строку и экономит батарею на телефоне.
  • Изображения без width и height. Карточки прыгают при загрузке, а вместе с ними уезжает вся лента.
  • Карусель ради карусели. Если контент важный, его лучше показать списком: то, что уехало со второго кадра, читатель чаще всего не видит.

Если карточки в ленте товарные, интересный приём — добавить им отклик при наведении. Разбор такого эффекта есть в материале про карточки товара с 3D-поворотом: механика поворота живёт в CSS и с прокруткой ленты не конфликтует.

Вопросы и ответы

Как сделать карусель на JS без библиотек?

Нужны три части: лента с горизонтальной прокруткой и свойствами scroll-snap-type и scroll-snap-align, вызов scrollTo для перехода к карточке и IntersectionObserver для подсветки активной точки. Это 63 непустые строки JavaScript вместе с автопрокруткой, свайп и инерцию берёт на себя браузер.

Чем карусель отличается от слайдера?

Строгой границы нет. По определению W3C карусель показывает набор слайдов, выводя по очереди один или несколько сразу, а слайдером чаще называют вариант, где в кадре всегда одна карточка. Код у них общий, разница только в ширине карточки.

Нужен ли jQuery для карусели?

Нет. jQuery требуется старым плагинам вроде Slick, у которого последняя версия в npm вышла 3 октября 2017 года и указывает jquery в peerDependencies. Современные библиотеки, например Swiper версии 14.0.7, обходятся без внешних зависимостей, а карусель на scroll-snap не требует ничего вовсе.

Как показать три карточки в кадре?

Задать карточке ширину формулой calc((100% — 32px) / 3), где 32 px — это два промежутка по 16 px, и добавить min-width: 0. Без первого не хватит места из-за гэпов, без второго ширину карточки начнёт диктовать самое длинное слово внутри.

Как правильно останавливать автопрокрутку?

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

Заключение

Карусель на JavaScript в 2026 году — это разметка, десяток строк CSS и тонкий скрипт поверх штатной прокрутки браузера. Плавность, свайп, инерция и прилипание кадров уже реализованы в движке, поэтому вручную остаётся описать три вещи: куда прокрутить, чем подсветить активную карточку и когда остановить автопрокрутку.

Порядок работы, который экономит время: сначала разметка с атрибутами доступности, потом геометрия в CSS с box-sizing, min-width: 0 и формулой ширины через calc, и только затем скрипт. Проверять стоит не глазами, а замером: открыть карусель на узком и широком экране, прокрутить пальцем и колесом, пройти по ней клавишей Tab, включить в системе экономию движения. Ровно этот набор проверок поймал в старом коде пустой кадр, который жил в статье несколько лет.

Готовая библиотека остаётся разумным выбором, когда нужны бесконечная лента, связанные ленты или миниатюры. Для карусели отзывов, логотипов, фотографий и товарных карточек своего кода из этой статьи достаточно.

РОССИЙСКИЕ НЕЙРОСЕТИ ДЛЯ ЖИЗНИ И КАРЬЕРЫ В 2025
Присоединяйся к онлайн-вебинару.
В прямом эфире разберем и потестируем лучшие на сегодняшний день отечественные ИИ!
Вы узнаете о том:
  • Выполним базовые задачи на российских нейросетях и посмотрим на результаты!
  • Файл-инструкцию «Как сделать нейро-фотосессию из своего фото бесплатно, без иностранных карт и прочих сложностей»
  • Покажем 10+ способов улучшить свою жизнь с ИИ каждому — от ребенка и пенсионера до управленца и предпринимателя
Участвовать бесплатно
ОБЗОРНЫЙ ПРАКТИКУМ ПО НАШУМЕВШИМ НЕЙРОСЕТЯМ
Нейросети DEEPSEEK И QWEN
За 2 часа сделаем полный обзор новых мощных ИИ-моделей, которые бросают вызов нейросети ChatGPT
Вы узнаете:
  • Возможность получить Доступ в Нейроклуб на целый месяц
  • Как ИИ ускоряет работу и приносит деньги
  • За 2 часа вы получите четкий план, как начать работать с ИИ прямо сейчас!
Участвовать бесплатно

Большой практикум
ЗАМЕНИ ВСЕ НЕЙРОСЕТИ НА ОДНУ — PERPLEXITY
ПОКАЖЕМ НА КОНКРЕТНЫХ КЕЙСАХ
  • Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
  • УЧАСТВОВАТЬ ЗА 0 РУБ.
  • Расскажем, как получить подписку
Участвовать бесплатно
ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
Участвовать бесплатно