Make a Number Go Back and Forth in JavaScript

September 2025

Small Steps and Giant Leaps

I'm working on a project1 that randomly changes CSS variables. The values are numbers in various ranges. For example:

  • Lightness: 0-100
  • Chroma: 0-2002
  • Hue: 0-360

The random changes fall into two categories: small changes and big jumps. In both cases, I'm adjusting a starting number by some amount. The part I'm interested in is the edges.

A simple approach is to use the % mod operator. If the random number goes over the max the remainder gets used instead.

For example, given a random number of 90 with a max of 100 results in 90:

90 % 100 == 90

If the random number is 110, the result is the remainder of 110 divided by 100 (i.e. 10) which looks like this in the JavaScript:

110 % 100 == 10

The effect is that when you reach the end, you start over at the beginning. But, you're always counting up.

That means if I currently have a Lightness value of 90 and try to do a small step of +15 the result would be 5. That's more of a giant leap.

Bouncing Around

Instead, I want the numbers to bounce off the edges. If adding a number to the value hits the max it should start counting back down. Same thing, but in reverse, at the bottom.

So, given a starting value of 95 and a random change of +15 I want the result to be 90. Step by step, it would look like this:

95, 96, 97, 98, 99, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90

I'm not familiar with any built-in ways to do that. So, I wrote this function to handle it for me:

JavaScript
function shiftNumber(position, min, max, move) {
  let step = (move > 0) ? 1 : -1;
  for (let count = 0; count < Math.abs(move); count += 1) {
    position += step;
    if (position >= max) {
      step = -1;
    } else if (position <= min) {
      step = 1;
    }
  }
  return position;
}
Head's Up

This code works without issue on Integers. Floating point numbers (i.e. numbers that have a decimal in them) have an off by one error. It's because the effectively sampling rate doesn't pull off the floating point parts.

See the example:

start: 25.5 | 
min: 0 | 
max: 100 | 
move: -45 | 
expected: 18.5 | 
got: 18.5

Which should really be 19.5. That's addressable, but I don't need it at this point so I'm leaving it as is for now.

Kicking The Tires

Here's the test suite I'm running against the function to make sure things are working as expected:

Onward To The Code

I've never needed that function before. But, it feels like one of things where now that I've got it, I'll find more uses for it.

Always good to add another tool to the mix.

-a

end of line

Footnotes

Playing around with styling each letter of the alphabet individually... and then using messing the values.

Got the initial idea from this blog post by Terence Eden and nerd-snipped myself with it.

There's a prototype up at the time I'm writing this. It may or may not be done by the time your reading these word.

The Chroma value doesn't really have at bound on it. I just use 200 as

an arbitrary stopping point based on what I've seen.