В этой статье разберём как можно очень просто с помощью CSS Flexbox и CSS трансформаций создать адаптивный слайдер для сайта.

Исходные коды и демо слайдера

Проект слайдера под названием chiefSlider расположен на GitHub. Перейти на него можно по этой ссылке.

Слайдер с одним активным слайдом (без зацикливания):

Слайдер с одним активным слайдом

Слайдер с тремя активными слайдами (без зацикливания):

Слайдер с тремя активными слайдами

Адаптивный слайдер с зацикливанием:

Адаптивный слайдер с зацикливанием

Слайдер с зацикливанием и автоматической сменой слайдов:

Слайдер с зацикливанием и автоматической сменой слайдов

Cлайдер, изменяющий своё состояние при изменении размеров окна браузера:

Cлайдер, изменяющий своё состояние при изменении размеров окна браузера

Пример, в котором показан как можно применить слайдер для ротации статей:

Пример слайдера, выполняющий ротацию статей

Преимущества слайдера chiefSlider

Перечислим основные преимущества данного слайдера:

  • во-первых, он не создаёт клоны элементов (item) для организации зацикливания, как это реализовано, например, в плагинах OwlCarousel и slick;
  • во-вторых, он не зависит от библиотеки jQuery; это не только убирает дополнительные требования, но и делает его более лёгким;
  • в-третьих, он практически не вносит никакие изменения в DOM документа; единственное, что он делает - это добавляет или изменяет значения CSS трансформаций у элементов слайдера;
  • в-четвертых, он содержит только минимальный набор функций; дополнительный функционал можно добавить в зависимости от задачи;
  • в-пятых, он является адаптивным, т.е. его можно использовать на любых сайтах; адаптивность слайдера настраивается с помощью CSS;
  • в-шестых, количество активных элементов настраивается с помощью CSS; это означает, что его можно использовать для создания карусели как с одним активным слайдом, так и с любым другим их количеством.

Установка слайдера chiefSlider

Установка слайдера выполняется за 3 шага:

  • добавить CSS слайдера chiefSlider на страницу или в CSS-файл, подключённый к странице;
  • поместить HTML код слайдера в необходимое место страницы;
  • вставить JavaScript код на страницу или в js-файл, подключённый к странице.

CSS и JavaScript код желательно минимизировать, это действие обеспечит более быструю загрузку страницы.

Как разработать простой слайдер для сайта (без зацикливания)

Создание слайдера chiefSlider будет состоять из создания HTML кода, CSS и JavaScript (без jQuery).

HTML код слайдера chiefSlider:

<div class="slider">
  <div class="slider__wrapper">
    <div class="slider__item">
      <!-- SLIDE #1 -->
    </div>
    <div class="slider__item">
      <!-- SLIDE #2 -->
    </div>
    <div class="slider__item">
      <!-- SLIDE #3 -->
    </div>      
    <div class="slider__item">
      <!-- SLIDE #4 -->
    </div>
  </div>
  <!-- КНОПКИ "НАЗАД" И "ВПЕРЁД" -->
  <a class="slider__control slider__control_left" href="#" role="button"></a>
  <a class="slider__control slider__control_right slider__control_show" href="#" role="button"></a>
</div>

Как видно, слайдер имеет очень простую HTML архитектуру. Начинается она с основного блока, который имеет класс slider. Данный блок состоит из 3 элементов.

Первый элемент - это slider__wrapper. Он выступает в качестве обёртки для элементов slider__item (слайдов).

Остальные два элемента (slider__control) визуально представляют собой кнопки. С их помощью будет совершаться навигация по слайду, т.е. переход к предыдущим и следующим элементам.

CSS код слайдера chiefSlider:

/* ОСНОВНЫЕ СТИЛИ */
.slider {
  position: relative;
  overflow: hidden;
}
.slider__wrapper {
  display: flex;
  transition: transform 0.6s ease; /* 0.6 длительность смены слайда в секундах */
}
.slider__item {
  flex: 0 0 50%; /* определяет количество активных слайдов (в данном случае 2 */
  max-width: 50%; /* определяет количество активных слайдов (в данном случае 2 */
}
/* СТИЛИ ДЛЯ КНОПОК "НАЗАД" И "ВПЕРЁД" */
.slider__control {
  position: absolute;
  display: none;  
  top: 50%;
  transform: translateY(-50%);
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 40px; /* ширина кнопки */
  height: 50px; /* высота кнопки */ 
  opacity: .5; /* прозрачность */
  background: #000; /* цвет фона */
}
.slider__control_show {
  display: flex;
}
.slider__control:hover,
.slider__control:focus {
  text-decoration: none;
  outline: 0;
  opacity: .9; /* прозрачность */
}
.slider__control_left {
  left: 0;
}
.slider__control_right {
  right: 0;
}
.slider__control::before {
  content: '';
  display: inline-block;
  width: 20px; /* ширина иконки (стрелочки) */
  height: 20px; /* высота иконки (стрелочки) */
  background: transparent no-repeat center center;
  background-size: 100% 100%;
}
.slider__control_left::before {
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
}
.slider__control_right::before {
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
}

Как видно, CSS код слайдера тоже является не очень сложным. Основные определения, с помощью которых можно настроить внешний вид слайдера, снабжены комментариями.

CSS код, который определяет количество активных элементов:

/* определяет количество активных слайдов (в данном случае 2) */
flex: 0 0 50%; 
max-width: 50%;

Этот код устанавливает слайдеру число активных элементов, равное 2.

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

/* определяет количество активных слайдов (в данном случае 1) */  
flex: 0 0 100%; 
max-width: 100%;

Создание адаптивного слайдера осуществляется посредством медиа запросов.

Например, слайдер, который на устройствах с крохотным экраном должен иметь один активный слайд, а на больших - четыре:

.slider__item {
  flex: 0 0 100%;
  max-width: 100%;
}
@media (min-width: 980px) {
  .slider__item {
    flex: 0 0 25%;
    max-width: 25%;
  }
}

JavaScript код слайдера chiefSlider:

'use strict';
var multiItemSlider = (function () {
  return function (selector) {
    var
      _mainElement = document.querySelector(selector), // основный элемент блока
      _sliderWrapper = _mainElement.querySelector('.slider__wrapper'), // обертка для .slider-item
      _sliderItems = _mainElement.querySelectorAll('.slider__item'), // элементы (.slider-item)
      _sliderControls = _mainElement.querySelectorAll('.slider__control'), // элементы управления
      _sliderControlLeft = _mainElement.querySelector('.slider__control_left'), // кнопка "LEFT"
      _sliderControlRight = _mainElement.querySelector('.slider__control_right'), // кнопка "RIGHT"
      _wrapperWidth = parseFloat(getComputedStyle(_sliderWrapper).width), // ширина обёртки
      _itemWidth = parseFloat(getComputedStyle(_sliderItems[0]).width), // ширина одного элемента    
      _positionLeftItem = 0, // позиция левого активного элемента
      _transform = 0, // значение трансформации .slider_wrapper
      _step = _itemWidth / _wrapperWidth * 100, // величина шага (для трансформации)
      _items = []; // массив элементов
      
    // наполнение массива _items
    _sliderItems.forEach(function (item, index) {
      _items.push({ item: item, position: index, transform: 0 });
    });

    var position = {
      getMin: 0,
      getMax: _items.length - 1,
    }

    var _transformItem = function (direction) {
      if (direction === 'right') {
        if ((_positionLeftItem + _wrapperWidth / _itemWidth - 1) >= position.getMax) {
          return;
        }
        if (!_sliderControlLeft.classList.contains('slider__control_show')) {
          _sliderControlLeft.classList.add('slider__control_show');
        }
        if (_sliderControlRight.classList.contains('slider__control_show') && (_positionLeftItem + _wrapperWidth / _itemWidth) >= position.getMax) {
          _sliderControlRight.classList.remove('slider__control_show');
        }
        _positionLeftItem++;
        _transform -= _step;
      }
      if (direction === 'left') {
        if (_positionLeftItem <= position.getMin) {
          return;
        }
        if (!_sliderControlRight.classList.contains('slider__control_show')) {
          _sliderControlRight.classList.add('slider__control_show');
        }
        if (_sliderControlLeft.classList.contains('slider__control_show') && _positionLeftItem - 1 <= position.getMin) {
          _sliderControlLeft.classList.remove('slider__control_show');
        }
        _positionLeftItem--;
        _transform += _step;
      }
      _sliderWrapper.style.transform = 'translateX(' + _transform + '%)';
    }

    // обработчик события click для кнопок "назад" и "вперед"
    var _controlClick = function () {
      var direction = this.classList.contains('slider__control_right') ? 'right' : 'left';
      _transformItem(direction);
    };

    var _setUpListeners = function () {
      // добавление к кнопкам "назад" и "вперед" обработчика _controlClick для события click
      _sliderControls.forEach(function (item) {
        item.addEventListener('click', _controlClick);
      });
    }

    // инициализация
    _setUpListeners();

    return {
      right: function () { // метод right
        _transformItem('right');
      },
      left: function () { // метод left
        _transformItem('left');
      }
    }

  }
}());

Основное действие в коде JavaScript выполняет функция _transformItem. Эта функция в зависимости от переданного ей направления выполняет трансформирование элемента .slider__wrapper.

Инициализация слайдера осуществляется следующим образом:

var slider = multiItemSlider('.slider')
Демо слайдера

Как создать слайдер с зацикливанием?

Зацикливание слайдов можно выполнить посредством трансформирования элементов .slider__item.

Для этого необходимо к каждому элементу .slider__item привязать значения его текущей позиции и трансформации.

Наиболее оптимально эти действия можно выполнить с помощью массива items:

var _items = [];

// наполнение массива элементами .slider__item
_sliderItems.forEach(function (item, index) {
  _items.push({ item: item, position: index, transform: 0 });
});

Но связать данные с элементами можно выполнить не только посредством массива, а например, с помощью data-атрибутов. Но операции с DOM самые медленные и ресурсоёмкие, их не рекомендуется использовать, когда это можно выполнить как-то по-другому.

Следующий шаг - это создать функции для вычисления элементов .slider__item с минимальной и максимальной позицией.

var position = {
  getItemMin: function () {
    var indexItem = 0;
    _items.forEach(function (item, index) {
      if (item.position < _items[indexItem].position) {
        indexItem = index;
      }
    });
    return indexItem;
  },
  getItemMax: function () {
    var indexItem = 0;
      _items.forEach(function (item, index) {
        if (item.position > _items[indexItem].position) {
          indexItem = index;
        }
      });
    return indexItem;
  },
  getMin: function () {
    return _items[position.getItemMin()].position;
  },
  getMax: function () {
    return _items[position.getItemMax()].position;
  }
}

Последний основной шаг, который предстоит выполнить - это доработать функцию _transformItem. А именно добавить к ней код, который будет изменять позицию элемента .slider__item и выполнять его трансформацию.

var _transformItem = function (direction) {
  var nextItem;
  if (direction === 'right') {
    _positionLeftItem++;
    if ((_positionLeftItem + _wrapperWidth / _itemWidth - 1) > position.getMax()) {
      nextItem = position.getItemMin();
      _items[nextItem].position = position.getMax() + 1;
      _items[nextItem].transform += _items.length * 100;
      _items[nextItem].item.style.transform = 'translateX(' + _items[nextItem].transform + '%)';
    }
    _transform -= _step;
  }
  if (direction === 'left') {
    _positionLeftItem--;
    if (_positionLeftItem < position.getMin()) {
      nextItem = position.getItemMax();
      _items[nextItem].position = position.getMin() - 1;
      _items[nextItem].transform -= _items.length * 100;
      _items[nextItem].item.style.transform = 'translateX(' + _items[nextItem].transform + '%)';
    }
    _transform += _step;
  }
  _sliderWrapper.style.transform = 'translateX(' + _transform + '%)';
}

На самом деле здесь всё просто.

Например, для того чтобы осуществить переход к следующему слайду сначала в массиве items ищется элемент с позицией большей, чем у текущего крайнего правого элемента .slider__item.

Если такой элемент в массиве есть, то выполняется трансформация элемента .slider__wrapper (т.е. действия, как и в алгоритме без зацикливания).

А вот если такого элемента нет, то кроме трансформации .slider__wrapper, выполняется ещё ряд действий. Во-первых, в массиве items ищется элемент с минимальной позицией. После получения этого элемента, ему устанавливается позиция, значение которой будет равно значению текущего правого элемента + 1. Ну и конечно выполняется его трансформация, на такое количество процентов, чтобы он оказался в конце, т.е. после последнего элемента.

Слайдер с зацикливанием - Как выполняется переход к следующему слайду

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

Слайдер с зацикливанием - Как выполняется переход к предыдущему слайду

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

Чтобы это выполнить необходимо:

  • удалить класс slider__control_show у элемента управления "Вправо";
  • в CSS для селектора .slider__control изменить значение свойства display на flex.
Демо слайдера

Как создать слайдер с зацикливанием и автоматической сменой слайдов?

Запрограммировать автоматическую смену слайдов через определённые промежутки времени можно с помощью функции setInterval.

var _cycle = function (direction) {
  if (!_config.isCycling) {
    return;
  }
  _interval = setInterval(function () {
    _transformItem(direction);
  }, _config.interval);
}  

Функция setInterval в этом примере будет запускать функцию _transformItem через определённые интервалы времени, равные значению переменой _config.interval.

Кроме этого остановку автоматической смены слайдов желательно ещё добавить при поднесении курсора к слайдеру.

Осуществить этот функционал можно следующим образом:

if (_config.pause && _config.isCycling) {
  _mainElement.addEventListener('mouseenter', function () {
    clearInterval(_interval);
  });
  _mainElement.addEventListener('mouseleave', function () {
    clearInterval(_interval);
    _cycle(_config.direction);
  });
}
Посмотреть

Как остановить автоматическую смену слайдов, если элемент не виден пользователю?

Отключить автоматическую смену слайдов целесообразно в двух случаях:

  • когда страница (на которой расположен данный слайдер) является не активной;
  • когда слайдер находится за пределами области видимости страницы.

Обработку первого случая можно осуществить с помощью события visibilitychange.

document.addEventListener('visibilitychange', _handleVisibilityChange, false);

Функция для обработчика события visibilitychange:

// обработка события "Изменения видимости документа"
var _handleVisibilityChange = function () {
  if (document.visibilityState === "hidden") {
    clearInterval(_interval);
  } else {
    clearInterval(_interval);
    _cycle(_config.direction);
  }
}

Вычисление видимости элемента можно организовать с помощью функции _isElementVisible:

function _isElementVisible(element) {
  var 
    rect = element.getBoundingClientRect(),
    vWidth = window.innerWidth || doc.documentElement.clientWidth,
    vHeight = window.innerHeight || doc.documentElement.clientHeight,
    elemFromPoint = function (x, y) { 
      return document.elementFromPoint(x, y);
    };

  if (rect.right < 0 || rect.bottom < 0 || rect.left > vWidth || rect.top > vHeight) {
    return false;
  }

  return (
    element.contains(elemFromPoint(rect.left, rect.top))
    || element.contains(elemFromPoint(rect.right, rect.top))
    || element.contains(elemFromPoint(rect.right, rect.bottom))
    || element.contains(elemFromPoint(rect.left, rect.bottom))
  );
}

Поместить вызов _isElementVisible можно, например, в начало функции _transformItem. Это действие позволит отменить автоматическую смену слайдов, если слайдер в данный момент находится вне viewport.

var _transformItem = function (direction) {
  var nextItem;

  if (!_isElementVisible(_mainElement)) {
    return;
  }
  
  //...
Посмотреть

Слайдер, реагирующий на изменение размеров окна браузера

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

Реализовано это с помощью использования события resize и массива _states. Массив используется для вычислений. Его применение позволит не выполнять переинициализацию слайдера, когда это не требуется.

Ознакомиться с кодом слайдера и его демкой можно в лаборатории:

Посмотреть