Reputation: 145
I have an image slider that moves to the next or previous image via drag & drop with the mouse, or a swipe with the finger on touch screens. So far, so good. But since swiping is rather tricky with, say, a laptop touchpad, I want to add next/previous buttons after all. Now my problem is that they mess with the code for touch and mouse gestures.
The errors caused are:
I've used console.log
and the dev tools to determine and examine these errors. Since I find my problems with this slider difficult to explain, I've made a code sample.
// set --n (used for calc in CSS) via JS, after getting
// .container and the number of child images it holds:
const _C = document.querySelector(".slider-container"),
N = _C.children.length;
_C.style.setProperty("--n", N);
// detect the direction of the motion between "touchstart" (or "mousedown") event
// and the "touched" (or "mouseup") event
// and then update --i (current slide) accordingly
// and move the container so that the next image in the desired direction moves into the viewport
// on "mousedown"/"touchstart", lock x-coordiate
// and store it into an initial coordinate variable x0:
let x0 = null;
let locked = false;
function lock(e) {
x0 = unify(e).clientX;
// remove .smooth class
_C.classList.toggle("smooth", !(locked = true));
}
// next, make the images move when the user swipes:
// was the lock action performed aka is x0 set?
// if so, read current x coordiante and compare it to x0
// from the difference between these two determine what to do next
let i = 0; // counter
let w; //image width
// update image width w on resive
function size() {
w = window.innerWidth;
}
function move(e) {
if (locked) {
// set threshold of 20% (if less, do not drag to the next image)
// dx = number of pixels the user dragged
let dx = unify(e).clientX - x0,
s = Math.sign(dx),
f = +(s * dx / w).toFixed(2);
// Math.sign(dx) returns 1 or -1
// depending on this, the slider goes backwards or forwards
if ((i > 0 || s < 0) && (i < N - 1 || s > 0) && f > 0.2) {
_C.style.setProperty("--i", (i -= s));
f = 1 - f;
}
_C.style.setProperty("--tx", "0px");
_C.style.setProperty("--f", f);
_C.classList.toggle("smooth", !(locked = false));
x0 = null;
}
}
size();
addEventListener("resize", size, false);
// ===============
// drag-animation for the slider when it reaches the end
// ===============
function drag(e) {
e.preventDefault();
if (locked) {
_C.style.setProperty("--tx", `${Math.round(unify(e).clientX - x0)}px`);
}
}
// ===============
// prev, next
// ===============
let prev = document.querySelector(".prev");
let next = document.querySelector(".next");
prev.addEventListener("click", () => {
if (i == 0) {
console.log("start reached");
} else if (i > 0) {
// decrease i as long as it is bigger than the number of slides
_C.style.setProperty("--i", i--);
}
});
next.addEventListener("click", () => {
if (i < N) {
// increase i as long as it's smaller than the number of slides
_C.style.setProperty("--i", i++);
}
});
// ===============
// slider event listeners for mouse and touch (start, move, end)
// ===============
_C.addEventListener("mousemove", drag, false);
_C.addEventListener("touchmove", drag, false);
_C.addEventListener("mousedown", lock, false);
_C.addEventListener("touchstart", lock, false);
_C.addEventListener("mouseup", move, false);
_C.addEventListener("touchend", move, false);
// override Edge swipe behaviour
_C.addEventListener(
"touchmove",
e => {
e.preventDefault();
},
false
);
// unify touch and click cases:
function unify(e) {
return e.changedTouches ? e.changedTouches[0] : e;
}
/* parent of book-container & container (slider) */
main {
overflow: hidden;
display: flex;
justify-content: space-between;
}
/* wraps entire slider */
.slider-wrapper {
overflow: hidden;
width: 100%;
position: relative;
}
.slider-nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0;
padding: 1%;
background: rgba(0,0,0,0.6);
color: #fff;
}
/* slider controls*/
.control {
position: absolute;
top: 50%;
width: 40px;
height: 10px;
color: #fff;
font-size: 3rem;
padding: 0;
margin: 0;
line-height: 0;
}
.prev,
.next {
cursor: pointer;
transition: all 0.2s ease;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: rgba(0,0,0,0.3);
padding: 1rem;
}
.prev {
left: 1.1rem;
}
.next {
right: 1.1rem;
}
.prev:hover,
.next:hover {
transform: scale(1.5,1.5);
}
.slider-container {
/*
n variable holds number of images to make .container wide enough
to hold all its image children
that still have the same width as its parent
*/
display: flex;
align-items: center;
overflow-y: hidden;
width: 100%; /* fallback */
width: calc(var(--n)*100%);
height: 31vw;
max-height: 100vh;
transform: translate(calc(var(--i, 0)/var(--n)*-100% + var(--tx, 0px)));
}
/* transition animation for the slider */
.smooth {
/* f computes actual animation duration via JS */
transition: transform calc(var(--f, 1)*.5s) ease-out;
}
/* images for the slider */
img {
width: 100%; /* can't take this out either as it breaks Chrome */
width: calc(100%/var(--n));
pointer-events: none;
}
<div class="slider-wrapper">
<div class="slider-container">
<img src="https://source.unsplash.com/featured?technology">
<img src="https://source.unsplash.com/featured?dogs">
<img src="https://source.unsplash.com/featured?cats">
<img src="https://source.unsplash.com/featured?cake">
<img src="https://source.unsplash.com/featured?birds">
<img src="https://source.unsplash.com/featured?cities">
</div>
<div class="slider-controls">
<span class="control prev">←</span>
<span class="control next">→</span>
</div>
</div>
<!-- END slider-wrapper -->
Would be great if anyone could anyone help me with this, so that the code for swiping slides via mouse or touch doesn't mess with the code for the next/pre button.
Upvotes: 0
Views: 1026
Reputation: 61
I think I've found quick way to fix the code for you.
prev.addEventListener("click", () => {
if (i == 0) {
console.log("start reached");
} else if (i > 0) {
// decrease i as long as it is bigger than the number of slides
_C.style.setProperty("--i", --i);
}
});
next.addEventListener("click", () => {
if (i+1 < N) {
// increase i as long as it's smaller than the number of slides
_C.style.setProperty("--i", ++i);
console.warn(i);
}
});
You had a bug in increasing your counter's logic. It was incremented and decremented 'post-factum'. That was causing issues with your buttons.
The thing with slider sticking can be reproduces by making your browser window smaller, click-dragging your mouse outside of the window and releasing it outside.
Hint: You should implement a listener for 'mouseleave'
event.
P.S.
The whole code could be a lot simpler, feel free hit me if you need a help to write it 'better' :) I'll try to help when I'll find some free time :)
Upvotes: 1