Marty
Marty

Reputation: 145

Touch image slider: next/prev keys interfere with code for current slide

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">&larr;</span>
    <span class="control next">&rarr;</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

Answers (1)

McLisak
McLisak

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

Related Questions