DEV Community

Cover image for This simple math hack lets you create an image carousel without any if statements

This simple math hack lets you create an image carousel without any if statements

Rane Wallin on September 25, 2019

If you are a web developer or a web developer student, you have probably made at least one image carousel in your career. In fact, you've probably ...
Collapse
 
andrearaujo profile image
André Araújo (Andrev) • Edited

Amazing!

You could remove the "if" splitting the handler into 2 functions or using a hashmap;

const imageData = [ 'image1.png', 'img2.png', 'img3.png' ];
let currentImage = 0;

const nextImage = () => currentImage = (currentImage + 1) % imageData.length;

const prevImage = () => currentImage = (currentImage - 1 + imageData.length) % imageData.length;
Collapse
 
ranewallin profile image
Rane Wallin

Yes, though if the we are still keeping the code DRY we would just be offsetting the if somewhere else. We wouldn't want two separate buttons that only different by which handler they call, so the button would need some logic in it to decide which handler is the right handler. That's the cool thing about programming, though. So many different ways to solve every problem.

Collapse
 
andrearaujo profile image
André Araújo (Andrev) • Edited

Yeah, I wasn't saying it's wrong, and I know that the point of this post is to show the math, I just showed one of the possible solutions, but you confused me a little bit now...

What would be the difference in the pseudo-code below and why the second one wouldn't follow the DRY principle?

// some logics in the button...
carousel.handleImageChange('forward');
...
// some logics in the button...
carousel.handleImageChange('other direction');
...
// some logics in the button...
carousel.nextImage()
...
// some logics in the button...
carousel.prevImage()
...
Thread Thread
 
ranewallin profile image
Rane Wallin

Nothing wrong with it at all. My point was that you still have an if statement, it's just in a different place. The only way to eliminate it would be if there were two completely different button components, one for forward and one for back, that called different code, which would then be less dry. I wasn't saying there was anything wrong with what you posted :).

Collapse
 
jeancarl profile image
JeanCarl

Once you figure out the modulus operator, it's a pretty cool operator! A ternary operator can get rid of that last if:

const imageData = [ 'image1.png', 'img2.png', 'img3.png' ];
let currentImage = 0;

const handleImageChange = (direction) => {
    currentImage = (direction == 'forward') ? 
      (currentImage + 1) % imageData.length : 
      (currentImage - 1 + imageData.length) % imageData.length;
}

(also changed currentImage to let (otherwise "Assignment to constant variable." error)

Collapse
 
skhmt profile image
Mike

I wouldn't say a ternary is better than an if, especially for multi-line things like this.

Collapse
 
ranewallin profile image
Rane Wallin

Thanks. I fixed it. I do that all the time.

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited
const imageData = [ 'image1.png', 'img2.png', 'img3.png' ];
let currentImage = 0;

const handleImageChange = (direction) => {
    currentImage = (currentImage + imageData.length + ((direction == 'forward') ? 1 : -1)) % imageData.length;
}
Collapse
 
mrwensveen profile image
Matthijs Wensveen

If you create a direction enum like so:

const direction = { FORWARD: 1, BACKWARD: -1 },

const handleImageChange = offset => {
  currentImage = (currentImage + imageData.length + offset) % imageData.length;
}

// call with enum value:
handleImageChange(direction.BACKWARD);

And you're golden. Well, except when you call handleImageChange(-7)

Solution: currentImage = (currentImage + imageData.length + (offset % imageData.length)) % imageData.length
😊

Collapse
 
ranewallin profile image
Rane Wallin

I don't think this will work as written. You only add the number of elements to the first part if you are going backwards. This looks like it's adding it either way. If I am at the last element (2) in this example then this would return in the next index being 3 instead of 0.

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

Wrong. Adding either way works. 3 modulo 3 is zero, as is 6 modulo 3, 9 modulo 3 etc.

Collapse
 
rkichenama profile image
Richard Kichenama

What if instead of passing a string to the function, you gave it an integer number of images to move?

const imageData = [ 'image1.png', 'img2.png', 'img3.png', ... ];
let currentImage = 0;
____
const handleImageChange = (imageShift) => {
  currentImage = Math.max(
    0,
    Math.min(
      imageData.length - 1,
      (currentImage + imageShift) % imageData.length
    )
  );
}
____
const firstImage = () => handleImageChange(-imageData.length);
const prevImage = () => handleImageChange(-1);
const nextImage = () => handleImageChange(1);
const lastImage = () => handleImageChange(imageData.length);
Collapse
 
cecilelebleu profile image
Cécile Lebleu

Great tip!
I think you mistyped css in the tags and added cs instead

Collapse
 
ranewallin profile image
Rane Wallin

Thanks. I meant cs, like computer science. :)

Collapse
 
cecilelebleu profile image
Cécile Lebleu

Oh! I thought of C sharp, but that makes more sense 😅

Collapse
 
felipperegazio profile image
Felippe Regazio

Pretty cool :)

Collapse
 
daveskull81 profile image
dAVE Inden

This is really cool. It is always fun to see the power of simple math operations applied to the behavior of things like this and what it can drive code to do. Great post!

Collapse
 
drmandible profile image
DrMandible

Pretty nifty. Previewing the next element after the modulo would be a big challenge to try without ifs.

Collapse
 
ranewallin profile image
Rane Wallin

You could just do the same thing. I.e. nextImg = (currentImage + 1) % imageData.length;