Search code examples
javascripthtmlcss

How do I move a slide with a mouse and on my phone?


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>


Solution

  • 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");
    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)
    
    }
    
    
    const dragcon = document.getElementById("dragcon");
    let drag=null;
    slider.ondrop = (e) => {
            e.preventDefault();
            slider.appendChild(drag)
    }
    slider.ondragover = (e) => {
            e.preventDefault();
    }
    dragcon.ondrop = (e) => {
            e.preventDefault();
            dragcon.style.backgroundColor = "#00fa00";
            dragcon.appendChild(drag)
    }
    dragcon.ondragover = (e) => {
            e.preventDefault();
    }
    for (let slide of slider.children) {
            slide.ondragstart = (e) => {
                    e.target.style.color = "#00fa00";
                    //drag=e.target.cloneNode(true)//to get copy of the slide
                    drag = e.target;
                    // e.dataTransfer.setData("slide", e.target.id);
            }
            slide.ondrag = () => {
            }
            slide.onmousedown = (e) => {
                    setTimeout(() => { e.target.setAttribute("draggable", "true"); }, 100);
            }
            slide.onmouseup = (e) => {
                    e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff"; 
            }
            slide.onblur = (e) => {
                   e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff"; 
            }
            slide.onmouseleave = (e) => {
                    e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff"; 
            }
    }
    .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;
            }
            .dragcon{
                width: 100px;
                height: 100px;
                background-color: #f0f0f0;
            }
            .dragcon>div{
                width: 100px;
                height: 50px;
                background-color: #000000;
            }
    <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>
         <br>
          <div id="dragcon" class="dragcon">Drag here !</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)
    
            }
            
            const dragcon = document.getElementById("dragcon");
    let drag=null;
    slider.ondrop = (e) => {
            e.preventDefault();
            slider.appendChild(drag)
    }
    slider.ondragover = (e) => {
            e.preventDefault();
    }
    dragcon.ondrop = (e) => {
            e.preventDefault();
            dragcon.style.backgroundColor = "#00fa00";
            dragcon.appendChild(drag)
    }
    dragcon.ondragover = (e) => {
            e.preventDefault();
    }
    for (let slide of slider.children) {
            slide.ondragstart = (e) => {
                    e.target.style.color = "#00fa00";
                    //drag=e.target.cloneNode(true)//to get copy of the slide
                    drag = e.target;
                    // e.dataTransfer.setData("slide", e.target.id);
            }
            slide.ondrag = () => {
            }
            slide.onmousedown = (e) => {
                    setTimeout(()=>{e.target.setAttribute("draggable", "true"); }, 100);
            }
            slide.onmouseup = (e) => {
                   e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
            slide.onblur = (e) => {
                     e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
            slide.onmouseleave = (e) => {
                    e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
    }
    .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;
            }
            .dragcon{
                width: 100px;
                height: 100px;
                background-color: #f0f0f0;
            }
            .dragcon>div{
                width: 100px;
                height: 50px;
                background-color: #000000;
            }
    <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>
        <br>
         <div id="dragcon" class="dragcon">Drag here !</div>
        </body>
    </html>

    third way (using mouse wheel only on desktop):

    const slider = document.getElementById("slider");
    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)
    
    }
    
    const dragcon = document.getElementById("dragcon");
    let drag=null;
    slider.ondrop = (e) => {
            e.preventDefault();
            slider.appendChild(drag)
    }
    slider.ondragover = (e) => {
            e.preventDefault();
    }
    dragcon.ondrop = (e) => {
            e.preventDefault();
            dragcon.style.backgroundColor = "#00fa00";
            dragcon.appendChild(drag)
    }
    dragcon.ondragover = (e) => {
            e.preventDefault();
    }
    for (let slide of slider.children) {
            slide.ondragstart = (e) => {
                    e.target.style.color = "#00fa00";
                    //drag=e.target.cloneNode(true)//to get copy of the slide
                    drag = e.target;
                    // e.dataTransfer.setData("slide", e.target.id);
            }
            slide.ondrag = () => {
            }
            slide.onmousedown = (e) => {
                    e.target.setAttribute("draggable", "true"); 
            }
            slide.onmouseup = (e) => {
                    e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
            slide.onblur = (e) => {
                    e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
            slide.onmouseleave = (e) => {
                     e.target.setAttribute("draggable", "false"); e.target.style.color = "#ffffff";
            }
    }
    .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;
            }
            .slides>div::selection {
                color: white;
                background: transparent;
            }
            .dragcon{
                width: 100px;
                height: 100px;
                background-color: #f0f0f0;
            }
            .dragcon>div{
                width: 100px;
                height: 50px;
                background-color: #000000;
            }
    <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>
            <br>
            <div id="dragcon" class="dragcon">Drag here !</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 😊