skip to Main Content

How can I implement a solution that allows users to navigate through slides using the mouse on desktop devices and using touch gestures (finger swipe) on mobile devices, without relying on third-party libraries? I need the navigation to be intuitive and smooth, with support for both mouse drag and touch events. The goal is to create a responsive slider that works seamlessly across different screen sizes and input methods

const paginationCirclesContainerEl = document.querySelector('.pagination_circles_container');
const btnPrevPositionEl = document.querySelector('.btn_prev_position');
const btnNextPositionEl = document.querySelector('.btn_next_position');
const sliderRobotsTrackEl = document.querySelector('.slider_robots_track');
const sliderRobotWrapperEls = [...document.querySelectorAll('.slider_robot_wrapper')];

// Variables for working with slides
const visibleSlidesCount = 3;
let currentSlideIndex = visibleSlidesCount;
let isAnimating = false;

// Creating pagination circles equal to the number of slides
const paginationCircles = [];

const createPaginationCircles = () => {
  const li = document.createElement('li');
  li.className = 'pagination_circle';
  paginationCirclesContainerEl.appendChild(li);
  paginationCircles.push(li);
};

const addPagination = () => {
  sliderRobotWrapperEls.forEach(createPaginationCircles);
  paginationCircles[0].classList.add('circle_active');
};
addPagination();

// Base slide width
let slideWidth = sliderRobotWrapperEls[0].offsetWidth;

// Cloning the first three slides
const lastThreeSlides = sliderRobotWrapperEls.slice(0, visibleSlidesCount);
lastThreeSlides.forEach(slideEl => {
  const clone = slideEl.cloneNode(true);
  sliderRobotsTrackEl.append(clone);
});

// Cloning the last three slides
const firstThreeSlides = sliderRobotWrapperEls.slice(-visibleSlidesCount);
firstThreeSlides.forEach(slideEl => {
  const clone = slideEl.cloneNode(true);
  sliderRobotsTrackEl.insertBefore(clone, sliderRobotWrapperEls[0]);
});

// New list of slides (12)
const allSlides = [...document.querySelectorAll('.slider_robot_wrapper')];

let isDragging = false;
let startX = 0;
let currentTranslate = 0;
let prevTranslate = 0;

sliderRobotsTrackEl.addEventListener('mousedown', e => {
  isDragging = true;
  startX = e.clientX;
});

sliderRobotsTrackEl.addEventListener('mousemove', e => {
  if (!isDragging) return;

  const currentX = e.clientX;
  const diffX = currentX - startX;
  currentTranslate = prevTranslate + diffX;

  updateSliderPosition();
});

sliderRobotsTrackEl.addEventListener('mouseup', e => {
  if (!isDragging) return;

  isDragging = false;
  prevTranslate = currentTranslate;
});

// Dynamically update the slide width on resize
const updateSlideWidth = () => {
  slideWidth = allSlides[0].offsetWidth;
  updateSliderPosition();
};
window.addEventListener('resize', updateSlideWidth);

// Move the track with slides
const updateSliderPosition = (withTransition = true, index) => {

  if (index) {
    removeActiveClass(currentSlideIndex);
    currentSlideIndex = index;
    addActiveClass(currentSlideIndex);
  }

  const offset = -currentSlideIndex * slideWidth;

  sliderRobotsTrackEl.style.transition = withTransition ? 'transform .5s' : 'none';
  sliderRobotsTrackEl.style.transform = `translate3d(${offset}px, 0px, 0px)`;
};

// Navigation through pagination circles
paginationCircles.forEach((circleEl, index) => {
  circleEl.addEventListener('click', () => {
    updateSliderPosition(true, index + visibleSlidesCount);
  });
});

// Add active circle class to the current slide
const addActiveClass = (currentSlideIndexCircle) => {
  let normalizedIndex;

  if (currentSlideIndexCircle) {
    normalizedIndex = (currentSlideIndexCircle - visibleSlidesCount + paginationCircles.length) % paginationCircles.length;
  } else {
    normalizedIndex = (currentSlideIndex - visibleSlidesCount + paginationCircles.length) % paginationCircles.length;
  }

  paginationCircles[normalizedIndex].classList.add('circle_active');
};

// Remove active circle class from the previous slide
const removeActiveClass = (currentSlideIndexCircle) => {
  let normalizedIndex;

  if (currentSlideIndexCircle) {
    normalizedIndex = (currentSlideIndexCircle - visibleSlidesCount + paginationCircles.length) % paginationCircles.length;
  } else {
    normalizedIndex = (currentSlideIndex - visibleSlidesCount + paginationCircles.length) % paginationCircles.length;
  }
  paginationCircles[normalizedIndex].classList.remove('circle_active');
};

// Show the next slide
const nextSlide = () => {
  // Block click until animation is finished
  if (isAnimating) return;
  isAnimating = true;
  setTimeout(() => {
    isAnimating = false;
  }, 500);

  removeActiveClass();

  currentSlideIndex++;
  updateSliderPosition();

  addActiveClass();

  // Quick rewind when reaching the last clone
  if (currentSlideIndex === allSlides.length - visibleSlidesCount) {
    setTimeout(() => {
      currentSlideIndex = visibleSlidesCount;
      updateSliderPosition(false);

    }, 500);
  }
};

// Show the previous slide
const prevSlide = () => {
  // Block click until animation is finished
  if (isAnimating) return;
  isAnimating = true;
  setTimeout(() => {
    isAnimating = false;
  }, 500);

  removeActiveClass();

  currentSlideIndex--;
  updateSliderPosition();

  addActiveClass();

  // Quick rewind when reaching the first clone
  if (currentSlideIndex === 0) {
    setTimeout(() => {
      currentSlideIndex = allSlides.length - visibleSlidesCount * 2;
      updateSliderPosition(false);

    }, 500);
  }
};

// Event handlers for the buttons
btnNextPositionEl.addEventListener('click', nextSlide);
btnPrevPositionEl.addEventListener('click', prevSlide);

// Initialize the initial position of the slider
updateSliderPosition(false);
ol {
  list-style: none;
}

.slider_robots {
  display: flex;
  flex-direction: column;
  align-items: center;

  padding: 80px 0 0 20px;
  margin: 0 20px 80px 0;
}

.slider_robots_container {
  position: relative;

  width: clamp(414px, 100%, 1282px);

  margin-bottom: 45px;

  overflow: hidden;
}

.slider_btn_container {
  position: absolute;
  z-index: 1000;

  display: flex;
  align-items: center;
  justify-content: center;

  background-color: #f7f7f7;
  border-radius: 100%;
  width: 54px;
  height: 54px;

  cursor: pointer;
}

.btn_prev_position {
  left: 0;
  top: 380px;
}

.btn_next_position {
  right: 0;
  top: 380px;
}

.slider_btn {
  width: 11px;
  height: 11px;
  border-top: 2px solid #6a768c;
  border-right: 2px solid #6a768c;
}

.btn_prev {
  transform: rotate(-135deg);
  margin-left: 3px;
}

.btn_next {
  transform: rotate(45deg);
  margin-right: 3px;
}


.slider_robots_track {
  display: flex;

  padding: 0 15px;

  transition: transform .5s;
}

.slider_robot_wrapper {
  display: flex;
  flex-direction: column;
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: calc((100% - 2 * 20px) / 3);

  gap: 25px;
  padding-right: 20px;

  pointer-events: none;
}

.slide_robot {
  width: 414px;
  height: 414px;
  border-radius: 12px;

  pointer-events: auto;
}

.pagination_circles_container {
  display: flex;
  justify-content: center;
  width: 100%;
  gap: 24px;
}

.pagination_circle {
  background-color: #b8edb7;
  border-radius: 100%;
  width: 10px;
  height: 10px;

  cursor: pointer;
}

.circle_active {
  background-color: #b8edb7;
  width: 10px;
  height: 10px;
  border-radius: 100%;
  box-shadow: 0 0 0 7px #447355;
}

@media screen and (max-width: 1024px) {

  .slider_robots_container {
    width: clamp(462px, 100%, 942px);
  }

  .slider_robot_wrapper {
    flex-basis: calc((100% - 20px) / 2);
  }

}

@media screen and (max-width: 768px) {

  .slider_robots_container {
    width: clamp(100px, 100%, 687px);
  }

  .slider_robot_wrapper {
    flex-basis: 100%;
  }

}
<div class="slider_robots">
  <div class="slider_robots_container">
    <div class="slider_btn_container btn_prev_position">
      <button class="slider_btn btn_prev"></button>
    </div>

    <div class="slider_robots_track">
      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #A8DADC;"></div>
      </div>

      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #457B9D;"></div>
      </div>

      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #F4A261;"></div>
      </div>

      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #2A9D8F;"></div>

      </div>

      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #E76F51;"></div>
      </div>

      <div class="slider_robot_wrapper">
        <div class="slide_robot" style="background-color: #264653;"></div>
      </div>
    </div>

    <div class="slider_btn_container btn_next_position">
      <button class="slider_btn btn_next"></button>
    </div>
  </div>

  <ol class="pagination_circles_container"></ol>
</div>

2

Answers


  1. Horizontal scrolling should work just like vertical scrolling. No pagination dots, no navigation arrows — just a scrollbar. Just use an overflowing, scrollable flexbox with scroll snapping. It works automatically on both desktops with a mouse, and mobile devices with touch screens.

    The key design consideration is to make it obvious to the user that the content overflows and that horizontal scrolling is necessary. My preferred solution is to ensure that each slide is narrower than the viewport, so that portions of the adjacent slides are visible.

    body {
      margin: 0;
    }
    
    .slides {
      background: pink;
      display: flex;
      gap: 1em;
      padding: 1em;
      overflow: auto;
      scroll-snap-type: x mandatory;
      font-size: 2em;
      font-weight: bold;
    }
    
    .slides > * {
      background: dodgerblue;
      color: white;
      flex-shrink: 0;
      width: 60vw;
      padding: 1em;
      scroll-snap-align: center;
      text-align: center;
    }
    <div class="slides">
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
      <div>7</div>
      <div>8</div>
      <div>9</div>
      <div>10</div>
    </div>
    Login or Signup to reply.
  2. Hi you can try this three ways to make slider that allow scrolling (navigate) by touch, mouse or mouse wheel in desktop and mobile

    first way :

    const slider = document.getElementById("slider");
    //        slider.scrollBy(124, 0);
            var slidertouch=false
            var lastpos=0;
            var immpos=0;
            var moveby=5;//speed of touch
            var wheelmoveby = 50;//speed of wheel
            slider.onmousedown=()=>{
                slidertouch=true
            }
            slider.onmouseup=()=>{
                slidertouch=false
            }
            slider.onblur=()=>{
                slidertouch=false
            }
            slider.onmouseleave =()=>{
                slidertouch=false
            }
            slider.onmousemove=(e)=>{
                if(slidertouch){
                    lastpos=immpos;
                    immpos=e.x;
    
                    if((lastpos-immpos)<0){ // change < to > (reversed)
                        slider.scrollBy(-moveby, 0);
                    }else{
                        slider.scrollBy(moveby, 0);
                    }
    
                    console.log("touch",lastpos,immpos,"/",e.x)
                }
            }
            slider.onmousewheel=(e)=>{
    
                if(e.wheelDelta>0){ // change < to > (reversed)
                    slider.scrollBy(-wheelmoveby, 0);
                }else{
                    slider.scrollBy(wheelmoveby, 0);
                }
    
                console.log("wheel",e.wheelDelta,"/",e.x)
    
            }
    .slides {
                scrollbar-color: transparent transparent;/*hide scroll*/
                overflow-y: hidden;
                overflow-x: scroll;
                width: 400px;/*try other width ex: 530 */
                padding: 30px 0px;
              background: #f2f2f2;
                display: flex;
    /*          scroll-snap-type: x mandatory;*/
    /*          scroll-snap-type: x proximity;*/
            }
    
            .slides > div {
                flex-shrink: 0;
                margin: 5px;
                height: 100px;
              background: black;
              color: white;
              width: 200px;
              scroll-snap-align: center;
              text-align: center;
            }
    <html>
        <head></head>
        <body>
        <div id="slider" class="slides">
          <div>slide number 1</div>
          <div>slide number 2</div>
          <div>slide number 3</div>
          <div>slide number 4</div>
    <!--increase the slides-->
        </div>
        </body>
    </html>

    second way :

    const slider = document.getElementById("slider");
            var slidertouch = false;
            var wheelmoveby =50;//speed of wheel
            var sliderscrollXlenght =(slider.children.length)*parseFloat(slider.children[0].getBoundingClientRect().width);
            var slidertouchrange = parseFloat(slider.getBoundingClientRect().width);
            var mousepos=0,scrollpos=0;
            slider.onmousedown=()=>{
                slidertouch=true
            }
            slider.onmouseup=()=>{
                slidertouch=false
            }
            slider.onblur=()=>{
                slidertouch=false
            }
            slider.onmouseleave =()=>{
                slidertouch=false
            }
            slider.onmousemove=(e)=>{
                if(slidertouch){
                    mousepos = (Math.abs(slidertouchrange-e.x)/slidertouchrange)*100; 
                    //scrollpos = Math.abs(((mousepos/100)*sliderscrollXlenght)-sliderscrollXlenght); //get scroll postion equevelant to mouse postion (reversed)
                     scrollpos = (mousepos/100)*sliderscrollXlenght; //get scroll postion equevelant to mouse postion
                    slider.scrollTo(scrollpos, 0);
    
                    console.log("touch",mousepos,"/",scrollpos,"/")
                }
            }
            slider.onmousewheel=(e)=>{
    
                if(e.wheelDelta>0){ // change < to > (reversed)
                    slider.scrollBy(-wheelmoveby, 0);
                }else{
                    slider.scrollBy(wheelmoveby, 0);
                }
    
                console.log("wheel",e.wheelDelta,"/",e.x)
    
            }
    .slides {
                scrollbar-color: transparent transparent;/*hide scroll*/
                overflow-y: hidden;
                overflow-x: scroll;
                width: 330px; /*try other width ex: 530 */
                padding: 30px 0px;
              background: #f2f2f2;
                display: flex;
    /*          scroll-snap-type: x mandatory; */
    /*          scroll-snap-type: x proximity;*/
            }
    
            .slides > div {
                flex-shrink: 0;
                margin: 5px;
                height: 100px;
              background: black;
              color: white;
              width: 200px;
              scroll-snap-align: center;
              text-align: center;
            }
    <html>
        <head></head>
        <body>
        <div id="slider" class="slides">
          <div>slide number 1</div>
          <div>slide number 2</div>
          <div>slide number 3</div>
          <div>slide number 4</div>
        <!--increase the slides-->
        </div>
        </body>
    </html>

    third way (using mouse wheel only on desktop):

            const slider = document.getElementById("slider");
    //        slider.scrollBy(124, 0);
            var slidertouch=false
            var lastpos=0;
            var immpos=0;
            var wheelmoveby=50;//speed of wheel
            slider.onmousewheel=(e)=>{
    
                if(e.wheelDelta>0){ // change < to > (reversed)
                    slider.scrollBy(-wheelmoveby, 0);
                }else{
                    slider.scrollBy(wheelmoveby, 0);
                }
    
                console.log("wheel",e.wheelDelta,"/",e.x)
    
            }
     .slides {
                scrollbar-color: transparent transparent; /*hide scroll*/
                overflow-y: hidden;
                overflow-x: scroll;
                width: 330px;/*try other width ex: 530 */
                padding: 30px 0px;
              background: #f2f2f2;
                display: flex;
    /*          scroll-snap-type: x mandatory;*/
    /*          scroll-snap-type: x proximity;*/
            }
    
            .slides > div {
                flex-shrink: 0;
                margin: 5px;
                height: 100px;
              background: black;
              color: white;
              width: 200px;
              scroll-snap-align: center;
              text-align: center;
            }
    <html>
        <head></head>
        <body>
        <div id="slider" class="slides">
          <div>slide number 1</div>
          <div>slide number 2</div>
          <div>slide number 3</div>
          <div>slide number 4</div>
    <!--increase the slides-->
        </div>
        </body>
    </html>

    when you apply any event to any slide I recommend to use event.stopPropagation()

    you can set selection Pseudo element to slides to remove selection

    .slides>div::selection {
      color: white;
      background: transparent;
    }

    I hope this help you 😊

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search