Задача
СкопированоСлайдер или слайд-шоу — распространённый дизайнерский паттерн на сайтах, особенно на лендингах. Слайдер состоит из нескольких слайдов, обычно с изображениями или с видео-контентом. Между слайдами можно переключаться, также бывают варианты с автоматической прокруткой и кнопками для управления анимацией.
Слайдер — неоднозначный элемент. С одной стороны, он экономит место на странице, вмещает больше контента за счёт прокрутки, а ещё привлекает внимание. С другой — его сложно сделать удобным и доступным для всех пользователей.
В этом рецепте расскажем, как создать доступный слайдер, в котором подумаем про дизайн и учтём семантику.
Готовое решение
СкопированоДля начала создадим HTML-разметку со всеми нужными элементами — общим контейнером, кнопками для переключения слайдов и самими слайдами с картинками и заголовками.
<div class="slider" role="region" aria-label="Паттерны"> <div class="controls" role="group" aria-label="Управление слайдами" > <button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 2 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 3 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 4 из 4" > </button> <button aria-label="Предыдущий" type="button" class="button button-prev" > </button> <button aria-label="Следующий" type="button" class="button button-next" > </button> </div> <div class="slides" aria-live="polite"> <div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1" > <img class="slide-img" src="./images/summer.jpg" alt="Абстрактные цветы розовых, синих, малиновых и оранжевых оттенков на зелёном фоне." > <h2 id="item-1-label">Паттерн «Лето»</h2> </div> <div class="slide" role="group" aria-labelledby="item-2-label" id="carousel-item-2" > <img class="slide-img" src="./images/flowers.jpg" alt="Цветы с расплывчатыми контурами, похожие на маки. Преобладает розовый, тёмно-зелёный, красный и фиолетовый цвет." > <h2 id="item-2-label">Паттерн «Цветочное поле»</h2> </div> <div class="slide" role="group" aria-labelledby="item-3-label" id="carousel-item-3" > <img class="slide-img" src="./images/lilac.jpg" alt="Несколько пятен розовых оттенков в форме цветов. На их фоне салатовые, тёмно-зелёные и фисташковые брызги." > <h2 id="item-3-label">Паттерн «Лиловый»</h2> </div> <div class="slide" role="group" aria-labelledby="item-4-label" id="carousel-item-4" > <img class="slide-img" src="./images/scarlet.jpg" alt="Несколько абстрактных роз в виде пятен алого цвета." > <h2 id="item-4-label">Паттерн «Алый»</h2> </div> </div></div>
<div class="slider" role="region" aria-label="Паттерны" > <div class="controls" role="group" aria-label="Управление слайдами" > <button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 2 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 3 из 4" > </button> <button class="button button-radio" type="button" aria-label="Показать 4 из 4" > </button> <button aria-label="Предыдущий" type="button" class="button button-prev" > </button> <button aria-label="Следующий" type="button" class="button button-next" > </button> </div> <div class="slides" aria-live="polite"> <div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1" > <img class="slide-img" src="./images/summer.jpg" alt="Абстрактные цветы розовых, синих, малиновых и оранжевых оттенков на зелёном фоне." > <h2 id="item-1-label">Паттерн «Лето»</h2> </div> <div class="slide" role="group" aria-labelledby="item-2-label" id="carousel-item-2" > <img class="slide-img" src="./images/flowers.jpg" alt="Цветы с расплывчатыми контурами, похожие на маки. Преобладает розовый, тёмно-зелёный, красный и фиолетовый цвет." > <h2 id="item-2-label">Паттерн «Цветочное поле»</h2> </div> <div class="slide" role="group" aria-labelledby="item-3-label" id="carousel-item-3" > <img class="slide-img" src="./images/lilac.jpg" alt="Несколько пятен розовых оттенков в форме цветов. На их фоне салатовые, тёмно-зелёные и фисташковые брызги." > <h2 id="item-3-label">Паттерн «Лиловый»</h2> </div> <div class="slide" role="group" aria-labelledby="item-4-label" id="carousel-item-4" > <img class="slide-img" src="./images/scarlet.jpg" alt="Несколько абстрактных роз в виде пятен алого цвета." > <h2 id="item-4-label">Паттерн «Алый»</h2> </div> </div> </div>
Для стилизации слайдера используем такие CSS-правила:
.controls { margin-block-end: 20px;}.button { cursor: pointer; user-select: none;}.button-radio { background-color: transparent; margin: 0; padding: 0; inline-size: 15px; block-size: 15px; border-radius: 50%; border: 1px solid #FFFFFF;}.button-radio + .button-radio { margin-inline-start: 12px;}.button-radio.active { background-color: #C56FFF; pointer-events: none;}.button-radio:focus-visible { outline: 3px solid white; outline-offset: -1px;}.button-prev,.button-next { position: absolute; inset-block-start: 50%; transform: translateY(-50%); border: none; inline-size: 30px; block-size: 42px; background-color: transparent; background-image: url(./images/arrow.svg); background-repeat: no-repeat; background-size: contain;}.button-prev[aria-disabled="true"],.button-next[aria-disabled="true"] { opacity: 0.5; pointer-events: none;}.button-prev { inset-inline-start: -50px;}.button-next { inset-inline-end: -50px; transform: translateY(-50%) rotateY(180deg); transform-origin: center;}.slide-img { display: block; inline-size: 100%; block-size: 225px; max-block-size: 225px; object-fit: cover;}.slider { display: flex; flex-direction: column; align-items: center; max-inline-size: 600px; inline-size: 100%; position: relative;}.slides { inline-size: 100%;}.slide { display: none; text-align: center;}.slide--active { display: block;}@media (max-width: 768px) { .slider { max-inline-size: 260px; } .slide-img { block-size: 400px; } .button-prev { inset-inline-start: -40px; } .button-next { inset-inline-end: -40px; }}
.controls { margin-block-end: 20px; } .button { cursor: pointer; user-select: none; } .button-radio { background-color: transparent; margin: 0; padding: 0; inline-size: 15px; block-size: 15px; border-radius: 50%; border: 1px solid #FFFFFF; } .button-radio + .button-radio { margin-inline-start: 12px; } .button-radio.active { background-color: #C56FFF; pointer-events: none; } .button-radio:focus-visible { outline: 3px solid white; outline-offset: -1px; } .button-prev, .button-next { position: absolute; inset-block-start: 50%; transform: translateY(-50%); border: none; inline-size: 30px; block-size: 42px; background-color: transparent; background-image: url(./images/arrow.svg); background-repeat: no-repeat; background-size: contain; } .button-prev[aria-disabled="true"], .button-next[aria-disabled="true"] { opacity: 0.5; pointer-events: none; } .button-prev { inset-inline-start: -50px; } .button-next { inset-inline-end: -50px; transform: translateY(-50%) rotateY(180deg); transform-origin: center; } .slide-img { display: block; inline-size: 100%; block-size: 225px; max-block-size: 225px; object-fit: cover; } .slider { display: flex; flex-direction: column; align-items: center; max-inline-size: 600px; inline-size: 100%; position: relative; } .slides { inline-size: 100%; } .slide { display: none; text-align: center; } .slide--active { display: block; } @media (max-width: 768px) { .slider { max-inline-size: 260px; } .slide-img { block-size: 400px; } .button-prev { inset-inline-start: -40px; } .button-next { inset-inline-end: -40px; } }
Для прокрутки и перемещения по слайдам с помощью кнопок и клавиатурных клавиш со стрелками используем JavaScript:
document.addEventListener('DOMContentLoaded', function () { const slider = document.querySelector('.slider') const slides = slider.querySelectorAll('.slide') const activeSlides = 'slide--active' const slideCount = slides.length const controlButtons = slider.querySelectorAll('.button-radio') const prevButton = slider.querySelector('.button-prev') const nextButton = slider.querySelector('.button-next') const activeButton = 'active' const inactiveButton = 'aria-disabled' const currentButton = 'aria-current' let currentSlide = 0 function updateSlider() { slides.forEach((slide, index) => { if(index === currentSlide) { slide.classList.add(activeSlides) } else { slide.classList.remove(activeSlides) } }) controlButtons.forEach((button, index) => { if (index === currentSlide) { button.classList.add(activeButton) button.setAttribute(currentButton, true) } else { button.classList.remove(activeButton) button.removeAttribute(currentButton, true) } prevButton.setAttribute(inactiveButton, currentSlide === 0) nextButton.setAttribute(inactiveButton, currentSlide === slideCount - 1) }) } controlButtons.forEach((button, index) => { button.addEventListener('click', () => { if (index < slideCount) { currentSlide = index updateSlider() } }) }) prevButton.addEventListener('click', () => { if (currentSlide > 0) { currentSlide-- updateSlider() } }) nextButton.addEventListener('click', () => { if (currentSlide < slideCount - 1) { currentSlide++ updateSlider() } }) slider.addEventListener('keydown', function (event) { if (event.key === 'ArrowLeft' && currentSlide > 0) { currentSlide-- updateSlider() } else if (event.key === 'ArrowRight' && currentSlide < slideCount - 1) { currentSlide++ updateSlider() } }) updateSlider()})
document.addEventListener('DOMContentLoaded', function () { const slider = document.querySelector('.slider') const slides = slider.querySelectorAll('.slide') const activeSlides = 'slide--active' const slideCount = slides.length const controlButtons = slider.querySelectorAll('.button-radio') const prevButton = slider.querySelector('.button-prev') const nextButton = slider.querySelector('.button-next') const activeButton = 'active' const inactiveButton = 'aria-disabled' const currentButton = 'aria-current' let currentSlide = 0 function updateSlider() { slides.forEach((slide, index) => { if(index === currentSlide) { slide.classList.add(activeSlides) } else { slide.classList.remove(activeSlides) } }) controlButtons.forEach((button, index) => { if (index === currentSlide) { button.classList.add(activeButton) button.setAttribute(currentButton, true) } else { button.classList.remove(activeButton) button.removeAttribute(currentButton, true) } prevButton.setAttribute(inactiveButton, currentSlide === 0) nextButton.setAttribute(inactiveButton, currentSlide === slideCount - 1) }) } controlButtons.forEach((button, index) => { button.addEventListener('click', () => { if (index < slideCount) { currentSlide = index updateSlider() } }) }) prevButton.addEventListener('click', () => { if (currentSlide > 0) { currentSlide-- updateSlider() } }) nextButton.addEventListener('click', () => { if (currentSlide < slideCount - 1) { currentSlide++ updateSlider() } }) slider.addEventListener('keydown', function (event) { if (event.key === 'ArrowLeft' && currentSlide > 0) { currentSlide-- updateSlider() } else if (event.key === 'ArrowRight' && currentSlide < slideCount - 1) { currentSlide++ updateSlider() } }) updateSlider() })
Разбор решения
СкопированоДизайн
СкопированоС точки зрения дизайна важно учесть особенности восприятия пользователей, которые чувствительны к движущемуся контенту. Это проблема для людей с когнитивными, моторными или зрительными особенностями.
Слайдер должен быть под полным контролем пользователей, поэтому мы не будем добавлять автопрокрутку.
HTML
СкопированоВместо автоматической прокрутки добавляем кнопки для последовательного переключения слайдов. Важно, чтобы интерактивные элементы контрастировали с фоном слайдера. Так как тип кнопок по умолчанию submit
, установим руками другой тип button
. Мы не отправляем данные на сервер, и нам не нужна перезагрузка страницы.
<button aria-label="Предыдущий" class="button button-prev" type="button"></button>
<button aria-label="Предыдущий" class="button button-prev" type="button" > </button>
Не ограничивайтесь одними стрелками для предыдущего и следующего слайда и предоставляйте пользователю альтернативный способ навигации. С помощью специальных кнопок, которые называют точками прогресса (progress dots), также показываем пользователю текущее положение в группе слайдов.
<button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4"></button><!-- Остальные кнопки -->
<button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4" > </button> <!-- Остальные кнопки -->
Расположение элементов в DOM (Document Object Model) влияет на порядок, в котором пользователи клавиатуры перемещаются по странице. По этой причине располагаем элементы навигации перед слайдером, а не после него. В этом случае пользователю не нужно будет возвращаться назад, чтобы прокрутить слайды:
<div class="controls" role="group" aria-label="Управление слайдами"> <!-- Сначала кнопки-точки, потом вперёд и назад --></div><div class="slides" aria-live="polite"> <!-- Слайды --></div>
<div class="controls" role="group" aria-label="Управление слайдами" > <!-- Сначала кнопки-точки, потом вперёд и назад --> </div> <div class="slides" aria-live="polite"> <!-- Слайды --> </div>
Содержимое слайдов группируем в одном <div>
.
<div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1"> <!-- Содержимое --></div>
<div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1" > <!-- Содержимое --> </div>
Семантика и ARIA-разметка
СкопированоВ первую очередь используем семантические теги <button>
, <h2>
для заголовков слайдов и <img>
для картинок с их кратким описанием в alt
.
Чтобы улучшить опыт пользователей скринридеров, голосового управления и других вспомогательных технологий, добавим в слайдер дополнительные ARIA-атрибуты и ARIA-роли.
С помощью роли region
и названия элемента в aria
, создаём отдельную область слайдера на странице. Пользователи скринридеров смогут отслеживать, находятся ли они в этой области или вышли из неё. Код будет зачитываться скринридером как область или группа элементов слайдера с названием «Паттерны».
<div class="slider" role="region" aria-label="Паттерны"> <!-- Элементы слайдера --></div>
<div class="slider" role="region" aria-label="Паттерны" > <!-- Элементы слайдера --> </div>
Благодаря role
группируем элементы навигации, и даём им общее название в aria
— «Управление слайдами». Это упрощает навигацию по слайдеру. Так пользователи скринридеров смогут быстро найти часть слайдера со всеми кнопками для управления им.
<div class="controls" role="group" aria-label="Управление слайдами"> <!-- Кнопки --></div>
<div class="controls" role="group" aria-label="Управление слайдами" > <!-- Кнопки --> </div>
Контейнеру со всеми слайдами задаём aria
. Этот атрибут делает эту часть страницы изменяющейся область (live region). Это значит, что скринридер автоматически зачитает содержимое нового слайда при пролистывании. Мы используем значение polite
, чтобы скринридер не спешил сразу рассказать об изменениях.
<div class="slides" aria-live="polite"> <!-- Слайды --></div>
<div class="slides" aria-live="polite"> <!-- Слайды --> </div>
Таким же образом поступаем и с содержимым слайдов. Группируем слайды через роль group
, а вот название задаём через атрибут aria
, связанный с заголовком через id
с уникальным значением. Это лучшее решение, так как в слайде уже есть видимый заголовок.
<div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1"> <!-- Картинка --> <h2 id="item-1-label">Паттерн «Лето»</h2></div>
<div class="slide" role="group" aria-labelledby="item-1-label" id="carousel-item-1" > <!-- Картинка --> <h2 id="item-1-label">Паттерн «Лето»</h2> </div>
Слайды, которые не видны, скрываем не только визуально, но и для пользователей вспомогательных технологий. Для этого используем CSS-свойство display
для всех слайдов по умолчанию. Для активного слайда добавим класс .slide
с другим значением у display
— none
.
Это активный слайд:
<div class="slide slide--active" role="group" aria-labelledby="item-1-label" id="carousel-item-1"> <!-- Содержимое слайда --></div>
<div class="slide slide--active" role="group" aria-labelledby="item-1-label" id="carousel-item-1" > <!-- Содержимое слайда --> </div>
А это все скрытые слайды:
<div class="slide" role="group" aria-labelledby="item-2-label" id="carousel-item-2"> <!-- Содержимое слайда --></div>
<div class="slide" role="group" aria-labelledby="item-2-label" id="carousel-item-2" > <!-- Содержимое слайда --> </div>
Для навигации по слайдеру важно использовать настоящую кнопку <button>
. Она работает с клавиатуры по умолчанию, а ещё у неё уже есть нужная семантика. Так как в кнопках нет видимого текста, называем их через aria
. Имена должны чётко передавать функциональность элементов. Кнопки-точки для переключения между слайдами назовём «Показать 1/2/3/4 из 4». Хорошо рассказать пользователям не только к какому слайду они перейдут, но и сколько их всего.
<button class="button button-radio" type="button" aria-label="Показать 2 из 4"></button>
<button class="button button-radio" type="button" aria-label="Показать 2 из 4" > </button>
С кнопками для пролистывания слайдов в одном направлении всё ещё проще: «Следующий» для кнопки вперёд и «Предыдущий» для кнопки назад.
<button aria-label="Предыдущий" class="button button-prev" type="button"></button><button aria-label="Следующий" class="button button-next" type="button"></button>
<button aria-label="Предыдущий" class="button button-prev" type="button" > </button> <button aria-label="Следующий" class="button button-next" type="button" > </button>
Чтобы не делать неактивными кнопки-точки и передать, что пользователь вспомогательной технологии выбрал конкретный по счёту слайд, навесим атрибут aria
. Он сообщает, в каком месте слайдера сейчас находится пользователь скринридера. Так как по умолчанию слайдер показывает первый слайд, на старте добавим атрибут к первой кнопке.
<button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4"></button>
<button class="button button-radio" type="button" aria-current="true" aria-label="Показать 1 из 4" > </button>
Теперь сложный и дискуссионный момент. Кнопка для перемещения к предыдущему слайду неактивна, когда мы на первом слайде. Кнопка «Следующий» перестаёт работать, когда выбран последний слайд. Чтобы передать это не только визуально через CSS, но и в HTML, используем атрибут aria
. Почему? ARIA-атрибут для неактивных кнопок остаётся доступен для вспомогательных технологий и, при этом, на них всё ещё можно установить клавиатурный фокус. HTML-атрибут disabled
полностью недоступен для всех. Это иногда приводит к тому, что пользователи скринридеров неожиданно «теряют» кнопки на странице. Чтобы кнопки не были неактивными по умолчанию, добавляем атрибут через JavaScript. Если что-то пошло не так со скриптом, интерактивные элементы всегда будут активными.
Не забудем описать картинки в слайдах. В этом случае они не декоративные и важны для понимания. Без этого сложно представить, как именно выглядят наши паттерны.
<img class="slide-img" src="./images/summer.jpg" alt="Абстрактные цветы розовых, синих, малиновых и оранжевых оттенков на зелёном фоне.">
<img class="slide-img" src="./images/summer.jpg" alt="Абстрактные цветы розовых, синих, малиновых и оранжевых оттенков на зелёном фоне." >
CSS
СкопированоСначала задаём стили для всего слайдера. Делаем его флекс-контейнером, выстраиваем элементы в колонку, центрируем через align
и задаём максимальную ширину max
. Так как у нас есть кнопки со стрелками в право и левой части слайдера, добавляем всему контейнеру position
.
.slider { display: flex; flex-direction: column; align-items: center; max-inline-size: 600px; inline-size: 100%; position: relative;}
.slider { display: flex; flex-direction: column; align-items: center; max-inline-size: 600px; inline-size: 100%; position: relative; }
Сами слайды и картинки в них растягиваем на всю доступную ширину, а картинке задаём object
, чтобы пропорции картинки не искажались.
.slides { inline-size: 100%;}.slide { text-align: center;}.slide-img { width: 100%; height: 225px; object-fit: cover;}
.slides { inline-size: 100%; } .slide { text-align: center; } .slide-img { width: 100%; height: 225px; object-fit: cover; }
Кнопки-точки скругляем с помощью border
, а с помощью изменения цвета фона показываем активную кнопку. Также не забываем и про стили для клавиатурного фокуса :focus
.
.button_type_radio { background-color: transparent; margin: 0; padding: 0; inline-size: 15px; block-size: 15px; border-radius: 50%; border: 1px solid #868A8F;}.button_type_radio.active { background-color: #53d67b;}.button_type_radio:focus-visible { outline: 3px solid white; outline-offset: -1px;}
.button_type_radio { background-color: transparent; margin: 0; padding: 0; inline-size: 15px; block-size: 15px; border-radius: 50%; border: 1px solid #868A8F; } .button_type_radio.active { background-color: #53d67b; } .button_type_radio:focus-visible { outline: 3px solid white; outline-offset: -1px; }
Кнопки для предыдущего и следующего слайда позиционируем с помощью position
и отрицательных значений для inset
и inset
соответственно. Стрелки добавляем через свойство background
.
.prev-button,.next-button { position: absolute; inset-block-start: 50%; transform: translateY(-50%); border: none; inline-size: 30px; block-size: 42px; background-color: transparent; background-image: url(arrow.svg); background-repeat: no-repeat; background-size: contain; cursor: pointer;}.button-prev { inset-inline-start: -40px;}.button-next { inset-inline-end: -40px; transform: translateY(-50%) rotateY(180deg); transform-origin: center;}
.prev-button, .next-button { position: absolute; inset-block-start: 50%; transform: translateY(-50%); border: none; inline-size: 30px; block-size: 42px; background-color: transparent; background-image: url(arrow.svg); background-repeat: no-repeat; background-size: contain; cursor: pointer; } .button-prev { inset-inline-start: -40px; } .button-next { inset-inline-end: -40px; transform: translateY(-50%) rotateY(180deg); transform-origin: center; }
Находим неактивные кнопки со стрелками по атрибуту aria
, добавляем для них небольшую прозрачность через opacity
и добавляем для пользователей мышки свойство pointer
.
.button-prev[aria-disabled="true"],.button-next[aria-disabled="true"] { opacity: 0.5; pointer-events: none;}
.button-prev[aria-disabled="true"], .button-next[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }
Слайдер можно анимировать на свой вкус. Если используете анимацию, оберните соответствующие правила в директиву @media
со значением prefers
. Так пользователи, которые отключили анимацию у себя в системе, не столкнутся с анимацией в слайдере.
JavaScript
СкопированоДля начала будем слушать событие окончания парсинга страницы DOM
, прежде чем добавлять все нужные атрибуты в слайдер.
document.addEventListener('DOMContentLoaded', function () { // Тело скрипта})
document.addEventListener('DOMContentLoaded', function () { // Тело скрипта })
Теперь скрываем все слайды, кроме текущего. По умолчанию это первый слайд.
const slider = document.querySelector('.slider')const slides = slider.querySelectorAll('.slide')const activeSlides = 'slide--active'let currentSlide = 0slides.forEach((slide, index) => { if(index === currentSlide) { slide.classList.add(activeSlides) } else { slide.classList.remove(activeSlides) }})
const slider = document.querySelector('.slider') const slides = slider.querySelectorAll('.slide') const activeSlides = 'slide--active' let currentSlide = 0 slides.forEach((slide, index) => { if(index === currentSlide) { slide.classList.add(activeSlides) } else { slide.classList.remove(activeSlides) } })
После делаем активными и показываем только те элементы, которые соответствуют текущему слайду. На этом шаге переключаем значение атрибута aria
с true
на false
и добавляем и удаляем с кнопок aria
.
const slideCount = slides.lengthconst controlButtons = slider.querySelectorAll('.button-radio')const prevButton = slider.querySelector('.button-prev')const nextButton = slider.querySelector('.button-next')const activeButton = 'active'const inactiveButton = 'aria-disabled'const currentButton = 'aria-current'let currentSlide = 0function updateSlider() { controlButtons.forEach((button, index) => { if (index === currentSlide) { button.classList.add(activeButton) button.setAttribute(currentButton, true) } else { button.classList.remove(activeButton) button.removeAttribute(currentButton, true) } prevButton.setAttribute(inactiveButton, currentSlide === 0) nextButton.setAttribute(inactiveButton, currentSlide === slideCount - 1) })}
const slideCount = slides.length const controlButtons = slider.querySelectorAll('.button-radio') const prevButton = slider.querySelector('.button-prev') const nextButton = slider.querySelector('.button-next') const activeButton = 'active' const inactiveButton = 'aria-disabled' const currentButton = 'aria-current' let currentSlide = 0 function updateSlider() { controlButtons.forEach((button, index) => { if (index === currentSlide) { button.classList.add(activeButton) button.setAttribute(currentButton, true) } else { button.classList.remove(activeButton) button.removeAttribute(currentButton, true) } prevButton.setAttribute(inactiveButton, currentSlide === 0) nextButton.setAttribute(inactiveButton, currentSlide === slideCount - 1) }) }
Чтобы кнопки начали переключать слайды, будем слушать событие click
на них. Пройдёмся по массиву кнопок-точек. Проверим, что кнопке соответствует нужный слайд, а потом перейдём к слайду с тем же порядковым номером, что у кнопки.
const slideCount = slides.lengthconst controlButtons = slider.querySelectorAll('.button-radio')controlButtons.forEach((button, index) => { button.addEventListener('click', () => { if (index < slideCount) { currentSlide = index updateSlider() } })})
const slideCount = slides.length const controlButtons = slider.querySelectorAll('.button-radio') controlButtons.forEach((button, index) => { button.addEventListener('click', () => { if (index < slideCount) { currentSlide = index updateSlider() } }) })
А так поступим с кнопками для переключения слайдов в одном направлении:
const prevButton = slider.querySelector('.button-prev')const nextButton = slider.querySelector('.button-next')prevButton.addEventListener('click', () => { if (currentSlide > 0) { currentSlide-- updateSlider() }})nextButton.addEventListener('click', () => { if (currentSlide < slideCount - 1) { currentSlide++ updateSlider() }})
const prevButton = slider.querySelector('.button-prev') const nextButton = slider.querySelector('.button-next') prevButton.addEventListener('click', () => { if (currentSlide > 0) { currentSlide-- updateSlider() } }) nextButton.addEventListener('click', () => { if (currentSlide < slideCount - 1) { currentSlide++ updateSlider() } })
Наконец, дополнительно предусмотрим перелистывание слайдов клавиатурными стрелками. Для этого слушаем событие keydown
для клавиш Arrow
и Arrow
.
slider.addEventListener('keydown', function (event) { if (event.key === 'ArrowLeft' && currentSlide > 0) { currentSlide-- updateSlider() } else if (event.key === 'ArrowRight' && currentSlide < slideCount - 1) { currentSlide++ updateSlider() }})
slider.addEventListener('keydown', function (event) { if (event.key === 'ArrowLeft' && currentSlide > 0) { currentSlide-- updateSlider() } else if (event.key === 'ArrowRight' && currentSlide < slideCount - 1) { currentSlide++ updateSlider() } })