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
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.
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 :
second way :
third way (using mouse wheel only on desktop):
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
I hope this help you 😊