Слайдер-превью изображений при наведении мыши

На сайтах интернет-магазинов (или тому подобных), где на страницах рубрик представлены карточки товаров или услуг, можно встретить мини-слайдеры, в которых показаны превью этих товаров и услуг. При наведении курсора и перемещении его влево-вправо можно посмотреть несколько таких изображений. Чтобы было более понятно, о чем идет речь, зайдите на сайты 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.

Комментарии (11)

  1. Добрый день. В данном слайдере есть один недостаток. При просмотре с телефона страницу можно скролить вверх и вниз только касаясь мимо превью которые используют данный слайдер. На самом превью можно только листать картинки влево, вправо. В общем создаются неудобства при прокрутке страницы вверх, вниз когда пытаешься прокручивать хватаясь за превьюшку. Можно как то поправить данную проблему, что бы скролить страницу вверх, вниз можно было как и раньше касаясь любого места экрана?

  2. Спасибо. Сработало после небольшой доработки, так как на странице есть и другие картинки и в итоге они пропадали а в слайдере отображались. Вот это вариант работает в моем случае корректно:
    .images img {
    display: none;
    }

    .images img:first-child {
    display: block;
    }

  3. Переключение слайдов тапом работает очень коряво.
    В идеале должно работать так: https://codepen.io/i-did/pen/yLYRvVL

Ваш комментарий