Слайдер на чистом JS (конкурс)

Рейтинг: 1Ответов: 0Опубликовано: 13.04.2023

Я реализовал слайдер на JavaScript. Работает управление с телефона, работает перетаскивание слайдов, только не реализовано прокручивание до определённого слайда в зависимости от скорости перетаскивания слайдов, что важно для скролла слайдов с телефона, например.
У меня получилась очень сложная логика авто слайда при перетаскивании (как мне кажется)

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

Каким должен быть слайдер:

  • Кнопки влево/вправо плавно переключают слайды
  • Работает перетаскивание слайдов, авто докручивание до слайда происходит плавно
  • Перетаскивание слайдов работает также от скорости перетаскивания. Если быстро запустили перетаскивание - докрутится до слайда подальше. (я это не реализовал у себя)
  • Слайдер правильно работает на мобильных устройствах
  • Должно корректно работать в браузере Chrome последней версии

Имеет смысл какие то ещё свои доп плюшки реализовать, если у вас будут какие-то мысли. Мне было бы очень интересно понять, как мыслят другие разработчики.



class Slider {

  nodes = {
    sliderNode: null,
    sliderItemsNode: null,
    sliderItemNodes: [],
    sliderArrowLeftNode: null,
    sliderArrowRightNode: null
  };

  cssSelectors = {
    items: '.slider__items',
    item: '.slider__item',
    wrapper: '.slider__wrapper',
    arrowLeft: '.slider__arrow_left',
    arrowRight: '.slider__arrow_right'
  };

  moveSlideShiftX = 0;
  shiftX = 0;
  lastShifX = 0;
  objShiftX = {};

  startMovePos = {
    x: 0,
    y: 0
  };

  activeItemIndex = 0;

  isDragging = false;
  countDragging = 0;

  teamsTitleNode = document.querySelector('.teams__title');


  constructor(sliderSelector) {
    this.initNodes(sliderSelector);
    this.initEventListeners();
    this.setObjShiftX();
    this.addTransition();
    this.setShiftX();
  }

  initNodes(sliderSelector) {
    this.nodes.sliderNode = document.querySelector(sliderSelector);
    if (this.nodes.sliderNode === null) {
      throw new Error(`Slider: по селектору ${sliderSelector} не найден элемент в DOM дереве`);
    }

    this.nodes.sliderItemsNode = this.nodes.sliderNode.querySelector(this.cssSelectors.items);
    if (this.nodes.sliderItemsNode === null) {
      throw new Error(`Slider: по селектору ${this.cssSelectors.items} не найден элемент в DOM дереве`);
    }

    this.nodes.sliderItemNodes = Array.from(this.nodes.sliderNode.querySelectorAll(this.cssSelectors.item));
    if (this.nodes.sliderItemNodes.length === 0) {
      throw new Error(`Slider: по селектору ${this.cssSelectors.item} не найдены элементы слайдера в DOM дереве`);
    }

    this.nodes.sliderArrowLeftNode = this.nodes.sliderNode.querySelector(this.cssSelectors.arrowLeft);
    if (this.nodes.sliderArrowLeftNode === null) {
      throw new Error(`Slider: по селектору ${this.cssSelectors.arrowLeft} не найден элемент в DOM дереве`);
    }

    this.nodes.sliderArrowRightNode = this.nodes.sliderNode.querySelector(this.cssSelectors.arrowRight);
    if (this.nodes.sliderArrowLeftNode === null) {
      throw new Error(`Slider: по селектору ${this.cssSelectors.arrowRight} не найден элемент в DOM дереве`);
    }

  }

  initEventListeners() {
    window.addEventListener('resize', this.debounce(this.resizeEvent, 50));

    this.nodes.sliderArrowLeftNode.addEventListener('click', () => {
      const nextIndex = this.getNextIndex(-1);
      this.changeSlide(nextIndex);
    });

    this.nodes.sliderArrowRightNode.addEventListener('click', () => {
      const nextIndex = this.getNextIndex(1);
      this.changeSlide(nextIndex);
    });

    for (const sliderNode of this.nodes.sliderItemNodes) {
      sliderNode.addEventListener('pointerdown', this.dragStart);
    }

    document.addEventListener('pointermove', this.dragging);
    document.addEventListener('pointerout', (event) => {
      if (event.relatedTarget === null) {
        console.log("Курсор мыши ушёл из браузера");
        this.dragStop(event);
      }
    });

    document.addEventListener('pointerup', this.dragStop);
    document.addEventListener('pointerleave', this.dragStop);
  }

  resizeEvent = () => {
    this.setObjShiftX();
    this.removeTransition();
    this.setShiftX();
    this.addTransition();
  }

  dragStart = (e) => {
    const x = e.clientX;
    const y = e.clientY;
    if (e.button === 2 || e.button === 1) return false; // Если это правая или средняя кнопка мыши, это не тот клик
    this.isDragging = true;
    this.startMovePos = {
      x: x,
      y: y
    };
    this.lastShifX = this.shiftX;
    this.removeTransition();
    console.log('dragStart');
  }

  dragStop = (e) => {
    this.isDragging = false;

    this.addTransition();
    if (this.moveSlideShiftX !== 0) this.autoSlide();

    this.moveSlideShiftX = 0;
    console.log('dragStop');
  }

  dragging = (e) => {
    const x = e.clientX;
    const y = e.clientY;

    if (this.isDragging === false) return;

    const nextMovePos = {
      x: x,
      y: y
    };
    const diffMovePos = {
      x: nextMovePos.x - this.startMovePos.x,
      y: nextMovePos.y - this.startMovePos.y
    }

    this.moveSlideShiftX = diffMovePos.x;
    this.calcMoveSlideShiftX();
    this.countDragging++;
    this.teamsTitleNode.innerText = `${this.countDragging} dragging`;

    console.log('dragging');
  }

  autoSlide() {
    const dir = this.moveSlideShiftX < 0 ? 'left' : 'right';

    let nextIndex = this.activeItemIndex + (dir === 'left' ? 1 : -1);
    let remainingShiftX = Math.abs(this.moveSlideShiftX);
    const loopBool = dir === 'left' ? nextIndex < this.nodes.sliderItemNodes.length : nextIndex >= 0;

    for (let step = 0; loopBool; step++) {
      const lastIndex = nextIndex + (dir === 'left' ? -1 : 1);
      const currentShiftX = step === 0 ? this.lastShifX : this.objShiftX[lastIndex];
      const nextShiftX = this.objShiftX[nextIndex];
      const distanceBetweenSlides = Math.abs(currentShiftX - nextShiftX);
      const percentMoved = remainingShiftX / distanceBetweenSlides;

      if (percentMoved >= 0.5 && percentMoved <= 1) {
        this.activeItemIndex = nextIndex;
        break;
      } else if (percentMoved < 0.5 && step === 0) {
        break;
      } else if (percentMoved < 0.5 && step !== 0) {
        this.activeItemIndex = lastIndex;
        break;
      } else if (percentMoved > 1) {
        const isLastSlide = dir === 'left' ? nextIndex + 1 >= this.nodes.sliderItemNodes.length : nextIndex - 1 < 0;

        if (isLastSlide) {
          this.activeItemIndex = nextIndex;
          break;
        } else {
          nextIndex += dir === 'left' ? 1 : -1;
          remainingShiftX -= distanceBetweenSlides;
          continue;
        }
      }
    }
    this.setShiftX(this.activeItemIndex);
  }

  addTransition() {
    const noParseTransitionDuration = parseFloat(getComputedStyle(this.nodes.sliderItemsNode).getPropertyValue('--transitionDurationSeconds'));
    const transitionDuration = isNaN(noParseTransitionDuration) ? 0.3 : noParseTransitionDuration;
    this.nodes.sliderItemsNode.style.transitionDuration = `${transitionDuration}s`;
  }

  removeTransition() {
    this.nodes.sliderItemsNode.style.transitionDuration = '0s';
  }

  setObjShiftX() {
    this.objShiftX = {};
    for (let i = 0; i < this.nodes.sliderItemNodes.length; i++) {
      this.objShiftX[i] = this.getShiftX(i);
    }
    console.log(this.objShiftX);
  }

  getNextIndex(dir) {
    const quantityItems = this.nodes.sliderItemNodes.length;
    return (this.activeItemIndex + dir + quantityItems) % quantityItems;
  }

  getCenteringShiftX = (activeItemIndex = this.activeItemIndex) => {
    const firstSliderItemNode = this.nodes.sliderItemNodes[activeItemIndex];
    const sliderItemsNode = this.nodes.sliderItemsNode;

    const widthSlider = sliderItemsNode.offsetWidth;
    const widthFirstSliderItem = firstSliderItemNode.offsetWidth;

    const centeringShiftX = Math.max((widthSlider - widthFirstSliderItem) / 2, 0);
    // this.centeringShiftX = Math.max((widthSlider - widthFirstSliderItem) / 2, 0);
    return centeringShiftX;
  }

  getChangeSlideShiftX(activeItemIndex = this.activeItemIndex) {
    const firstRect = this.nodes.sliderItemNodes[0].getBoundingClientRect();
    const nextRect = this.nodes.sliderItemNodes[activeItemIndex].getBoundingClientRect();
    const diffLeftNextSlide = nextRect.left - firstRect.left;

    return diffLeftNextSlide;
    // this.changeSlideShiftX = diffLeftNextSlide;
  }

  getShiftX(activeItemIndex = this.activeItemIndex) {
    return this.getCenteringShiftX(activeItemIndex) - this.getChangeSlideShiftX(activeItemIndex);
  }

  setShiftX = (activeItemIndex = this.activeItemIndex) => {
    this.shiftX = this.objShiftX[activeItemIndex];
    this.nodes.sliderItemsNode.style.transform = `translateX(${this.shiftX}px)`;
  }

  calcMoveSlideShiftX() {
    this.shiftX = this.lastShifX + this.moveSlideShiftX;

    this.nodes.sliderItemsNode.style.transform = `translateX(${this.shiftX}px)`;
  }

  changeSlide(nextIndex) {
    this.activeItemIndex = nextIndex;
    this.setShiftX();
  }

  debounce(func, delay) {
    let timeoutId;
    return function(...args) {
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
      timeoutId = setTimeout(() => {
        func.apply(this, args);
      }, delay);
    };
  }

}

const slider = new Slider('.teams__slider');
.slider {
  display: flex;
  flex-direction: column;
  gap: 20px;
  user-select: none;
}

.slider__arrows {
  display: flex;
  gap: 30px;
  margin: 0 auto;
  user-select: none;
}

.slider__items {
  --transitionDurationSeconds: 0.3;
  display: flex;
  align-items: center;
  gap: 50px;
  user-select: none;
  transition-property: transform;
  transition-timing-function: linear;
  transition-duration: 0s;
  touch-action: pan-y;
}

.slider__wrapper img {
  pointer-events: none;
}

.slider__item {
  cursor: grab;
}

.slider__arrow {
  cursor: pointer;
  padding: 5px 12px;
  transition: background-color 0.2s ease;
  font-size: 2em;
  color: white;
}

.slider__arrow:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

html {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-size: 16px;
  font-family: 'Roboto', sans-serif;
}

img {
  display: block;
  max-width: 100%;
  max-height: 100%;
}

.container {
  max-width: 1094px;
  display: block;
  margin: 0 auto;
  padding: 0 1em;
}

.link {
  white-space: nowrap;
  text-decoration: none;
  color: inherit;
  transition-property: color;
  transition-timing-function: ease;
  transition-duration: 0.2s;
}

.link:hover {
  color: #90ee90;
}

.btn {
  display: inline-block;
  font-size: inherit;
  border: none;
  outline: none;
  color: white;
  padding: 1em 1.625em;
  background-color: inherit;
  transition-property: color, background-color;
  transition-timing-function: ease;
  transition-duration: 0.2s;
  font-weight: bold;
}

.btn_green {
  background-color: rgb(66, 174, 96);
  border-radius: 6px;
}

.btn_green:hover {
  background-color: rgb(135, 189, 29);
  color: white;
}

.teams {
  background-color: rgb(39, 55, 32);
  padding: 120px 0;
  overflow-x: hidden;
}

.teams__title {
  color: white;
  margin: 0;
  font-size: 2em;
  text-align: center;
  margin: 0;
}

.teams__wrapper {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.teams__slider-item {
  padding: 2.75em 5.125em;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
  border-radius: 30px;
  --width: max(75%, 500px);
  min-width: var(--width);
  max-width: var(--width);
}

.teams__slider-item:nth-child(2n + 1) {
  /* нечётные */
  background-color: white;
}

.teams__slider-item:nth-child(2n) {
  /* чётные */
  background-color: #D2EBD5;
}

.teams__slider-item-img {
  --width: 128px;
  min-width: var(--width);
  max-width: var(--width);
}

.teams__slider-text {
  margin: 0;
  font-size: 2em;
}

.teams__slider-review {
  display: flex;
  align-items: center;
  gap: 24px;
}

.teams__slider-review-img {
  --width: 64px;
  min-width: var(--width);
  max-width: var(--width);
}

.teams__slider-review-text {
  font-size: 1.125em;
}

@media (max-width: 720px) {
  .teams__slider-item {
    --width: max(75%, 300px);
    padding: 1em 2em;
  }
  .teams__slider-text {
    font-size: 1em;
  }
  .teams__slider-review-text {
    font-size: 0.8em;
  }
}

.as-console {
  display: none;
  visibility: hidden;
  opacity: 0;
  position: fixed;
  z-index: -1;
}
<section class="teams">
  <div class="container">
    <div class="teams__wrapper">
      <h2 class="teams__title">Trusted by top teams</h2>
      <div class="teams__slider slider">
        <div class="slider__items teams__slider-items">

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              1 “HyperComply has enabled us to complete questionnaires quickly and accurately, and has had a hugely positive effect on speeding up our sales cycle”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>
            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              2 “With HyperComply, we can now complete questionnaires with ease and accuracy, resulting in a faster sales cycle. This innovative tool has truly transformed the way we do business”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>

            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              3 “Thanks to HyperComply, we are able to breeze through questionnaires and speed up our sales cycle. This game-changing technology has made a significant impact on our business operations.”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>

            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              4 “Thanks to HyperComply, we are able to breeze through questionnaires and speed up our sales cycle. This game-changing technology has made a significant impact on our business operations.”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>

            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              5 “Thanks to HyperComply, we are able to breeze through questionnaires and speed up our sales cycle. This game-changing technology has made a significant impact on our business operations.”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>

            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

          <div class="slider__item teams__slider-item">

            <div class="teams__slider-item-img-wrapper">
              <img src="./img/teams-slider-logo.png" alt="" class="teams__slider-item-img">
            </div>
            <p class="teams__slider-text">
              6 “Thanks to HyperComply, we are able to breeze through questionnaires and speed up our sales cycle. This game-changing technology has made a significant impact on our business operations.”
            </p>
            <div class="teams__slider-review">
              <div class="teams__slider-review-img-wrapper">
                <img src="./img/teams-slider-review.jpg" alt="" class="teams__slider-review-img">
              </div>
              <div class="teams__slider-review-text">
                Desiree R, Sr. Director of Information Security
              </div>

            </div>

          </div>
          <!-- .slider__item.teams__slider-item -->

        </div>
        <!-- .slider__items teams__slider-items -->

        <div class="slider__arrows">
          <div class="slider__arrow slider__arrow_left">
            &lt;

          </div>
          <div class="slider__arrow slider__arrow_right">
            &gt;

          </div>
        </div>

      </div>
      <!-- .teams__slider slider -->
    </div>
    <!-- .teams__wrapper -->
</section>

Ответы

Ответов пока нет.