Слайдер-превью изображений при наведении мыши
На сайтах интернет-магазинов (или тому подобных), где на страницах рубрик представлены карточки товаров или услуг, можно встретить мини-слайдеры, в которых показаны превью этих товаров и услуг. При наведении курсора и перемещении его влево-вправо можно посмотреть несколько таких изображений. Чтобы было более понятно, о чем идет речь, зайдите на сайты ozon.ru и realty.yandex.ru (актуально на момент написания данной статьи).
Хочу поделиться моей реализацией функционала этого слайдера в двух вариантах: на jQuery и в нативном JavaScript.
Из особенностей: на десктопе изображения меняются при перемещении курсора мыши над ним горизонтально, а на мобильных устройствах — если проводить пальцем по изображению.
Исходная структура HTML-кода
<div class="images">
  <img src="image">
  <img src="image">
  <img src="image">
</div>
После выполнения скрипта она обретает следующий вид:
<div class="hvr">
  <div class="hvr__images">
    <div class="images">
      <img class="image">
      <img class="image">
      <img class="image">
    </div>
    <div class="hvr__sectors">
      <div class="hvr__sector"></div>
      <div class="hvr__sector"></div>
      <div class="hvr__sector"></div>
    </div>
  </div>
  <div class="hvr__dots">
    <div class="hvr__dot hvr__dot--active"></div>
    <div class="hvr__dot"></div>
    <div class="hvr__dot"></div>
  </div>
</div>
CSS-код
Есть обязательные стили:
.hvr__images {
  position: relative;
}
.hvr__sectors {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
}
.hvr__sector {
  flex-grow: 1;
}
Также можно добавить допонительные стили:
- для изображений, чтобы до срабатывания скрипта отображалось только первое изображение;
- для точек, показывающих общее количество изображений и активный слайд.
.images {
  display: flex;
  overflow: hidden;
}
.image {
  display: block;
}
.hvr__dots {
  display: flex;
  align-items: center;
  justify-content: center;
}
.hvr__dot {
  width: 5px;
  height: 5px;
  margin: 10px 2px 0;
  border-radius: 50%;
  background: #d6dbe0;
}
.hvr__dot--active {
  background: #000;
}
Решение на jQuery
(function ($) {
  $.fn.HvrSlider = function () {
    return this.each(function () {
      var el = $(this);
      if (el.find('img').length > 1) {
        var hvr = $('<div>', {
          class: 'hvr',
          append: [
            $('<div>', {
              class: 'hvr__images',
              append: $('<div>', {
                class: 'hvr__sectors',
              }),
            }),
            $('<div>', {
              class: 'hvr__dots',
            }),
          ],
          insertAfter: el,
          prepend: el,
        });
        var hvrImages = $('.hvr__images', hvr);
        var hvrImage = $('img', hvr);
        var hvrSectors = $('.hvr__sectors', hvr);
        var hvrDots = $('.hvr__dots', hvr);
        el.prependTo(hvrImages);
        hvrImage.each(function () {
          hvrSectors.prepend('<div class="hvr__sector"></div>');
          hvrDots.append('<div class="hvr__dot"></div>');
        });
        $('.hvr__dot:first', hvrDots).addClass('hvr__dot--active');
        var setActiveEl = function (el) {
          hvrImage.hide().eq(el.index()).show();
          $('.hvr__dot', hvrDots).removeClass('hvr__dot--active').eq(el.index()).addClass('hvr__dot--active');
        };
        $('.hvr__sector', hvrSectors).hover(function () {
          setActiveEl($(this));
        });
        hvrSectors.on('touchmove', function (e) {
          var position = e.originalEvent.changedTouches[0];
          var target = document.elementFromPoint(position.clientX, position.clientY);
          if ($(target).is('.hvr__sector')) {
            setActiveEl($(target));
          }
        });
      }
    });
  };
})(jQuery);
Код для инициализации скрипта:
$('.images').HvrSlider();
Результат выглядит следующим образом:
  See the Pen 
  Слайдер-превью изображений на jQuery by Dimox (@dimox)
  on CodePen.
Решение на JavaScript
class HvrSlider {
  constructor(selector) {
    const elements = document.querySelectorAll(selector);
    elements.forEach((el) => {
      if (el.querySelectorAll('img').length > 1) {
        const hvr = document.createElement('div');
        hvr.classList.add('hvr');
        const hvrImages = document.createElement('div');
        hvrImages.classList.add('hvr__images');
        hvr.appendChild(hvrImages);
        const hvrSectors = document.createElement('div');
        hvrSectors.classList.add('hvr__sectors');
        hvrImages.appendChild(hvrSectors);
        const hvrDots = document.createElement('div');
        hvrDots.classList.add('hvr__dots');
        hvr.appendChild(hvrDots);
        el.parentNode.insertBefore(hvr, el);
        hvrImages.prepend(el);
        const hvrImagesArray = hvr.querySelectorAll('img');
        hvrImagesArray.forEach(() => {
          hvrSectors.insertAdjacentHTML('afterbegin', '<div class="hvr__sector"></div>');
          hvrDots.insertAdjacentHTML('afterbegin', '<div class="hvr__dot"></div>');
        });
        hvrDots.firstChild.classList.add('hvr__dot--active');
        const setActiveEl = function (targetEl) {
          const index = [...hvrSectors.children].indexOf(targetEl);
          hvrImagesArray.forEach((img, idx) => {
            if (index == idx) {
              img.style.display = 'block';
            } else {
              img.style.display = 'none';
            }
          });
          hvr.querySelectorAll('.hvr__dot').forEach((dot, idx) => {
            if (index == idx) {
              dot.classList.add('hvr__dot--active');
            } else {
              dot.classList.remove('hvr__dot--active');
            }
          });
        };
        hvrSectors.addEventListener('mouseover', function (e) {
          if (e.target.matches('.hvr__sector')) {
            setActiveEl(e.target);
          }
        });
        hvrSectors.addEventListener('touchmove', function (e) {
          const position = e.changedTouches[0];
          const target = document.elementFromPoint(position.clientX, position.clientY);
          if (target.matches('.hvr__sector')) {
            setActiveEl(target);
          }
        });
      }
    });
  }
}
Код для инициализации скрипта:
new HvrSlider('.images');
Результат точно такой же, как и в примере выше:
  See the Pen 
  Слайдер-превью изображений на JavaScript by Dimox (@dimox)
  on CodePen.


Комментарии (18)
Добрый день. В данном слайдере есть один недостаток. При просмотре с телефона страницу можно скролить вверх и вниз только касаясь мимо превью которые используют данный слайдер. На самом превью можно только листать картинки влево, вправо. В общем создаются неудобства при прокрутке страницы вверх, вниз когда пытаешься прокручивать хватаясь за превьюшку. Можно как то поправить данную проблему, что бы скролить страницу вверх, вниз можно было как и раньше касаясь любого места экрана?
Да, есть такая проблема. Надо будет заняться поиском решения.
Обнаружил еще одну проблему. В Firefox все картинки показываются в одну строку. В слайдер они превращаются только после того как по ним поводить курсором мышки. После этого все работает как слайдер. После перезагрузки страницы картинки снова в строку.
Странно, у меня в Firefox всё работает, как положено.
Проблема решена. Достаточно было удалить строку:
Спасибо. Работает. В Firefox ваш оригинальный вариант и правда работает. Я в CSS вносил некоторые изменения что бы адаптировать к шаблону сайта, вот бяка и вылезла. В хром, опере, едж, везде нормально, а в Firefox бяка.
изменения вот такие
.image {
display: block; max-width: 450px; width: 100%; height: auto;
Это для того что бы блок слайдера был адаптивным. Как побороть это в Firefox не знаю.
Нужно добавить такие стили:
То есть по умолчанию показывать только первое изображение.
Спасибо. Сработало после небольшой доработки, так как на странице есть и другие картинки и в итоге они пропадали а в слайдере отображались. Вот это вариант работает в моем случае корректно:
.images img {
display: none;
}
.images img:first-child {
display: block;
}
Переключение слайдов тапом работает очень коряво.
В идеале должно работать так: https://codepen.io/i-did/pen/yLYRvVL
А как на JS сделать плавность сметы этих изображений?
Спасибо за код. Подскажите, как сделать, что бы автоматически показывался первый слайд, если убрать мышь?
Добрый день. А как такой превью сделать для категории товаров в woocomerce
Установил на сайт и возникла следующая проблема.
При загрузке страницы отображаются все фото товара, а уже после того как навожу курсор, фото скрываются и все отображается как и должно.
Как это исправить?
Скройте стилями все изображения, кроме первого. В статье есть пример.