## This article utilizes React, but the techniques are frontend-agnostic.

# A Novel Algorithm for Stateless Navigation, Transitions and Buffering in Image Galleries

While working on a recent project, I needed to implement an image gallery with a navigable lightbox. I wanted the navigation to include swiping animations and opacity fades, and I wanted the previous and next images to pre-load for an optimal user experience. Initial brainstorms involved various bits of state management, Effect hooks and/or react-transition-group. I eschewed those paths in favor of number theory. The result is a single map function that handles all behavior declaratively via **modular arithmetic**.

## Initial Setup

To begin with, I created a lightbox to hold my images. A dissection of the lightbox itself is beyond the scope of this article, but a functional, simplified component follows.

- First, we’ll be using FontAwesome for our navigation arrows. Include the following in your
**index.html***:*

- Next, the
**Lightbox.js**component. For content, I selected 10 random images from Unsplash.com.

- Lastly,
**Lightbox.css**handles the styling. I intentionally used garish colors to help illustrate the various sections.

Though we’re a good distance from our goal, we now have a functional, responsive lightbox.

## The Idea

Let us imagine that we display 3 images at all times — previous, current and next. By intelligently varying their content and properties, we can ensure that only the *current* image is visible. Further, when navigating, we’re only required to swap one image which we are neither viewing nor transitioning from viewing.

This seems like a good use-case for linked lists, but digging deeper exposes patterns that can be exploited. A pencil, paper, coffee and 45 minutes produced this rough draft:

Let’s clean that up, fix the errors and make sense of it.

Note the patterns in the indexes (3, 3, 3, 6, 6, 6, etc) and in the offsets (0, +1, -1, 0, +1, -1, etc). These little bits of repetition and symmetry are no coincidence — we are looking at a **collection of interconnected modular equations**. If we can deduce algebraic formulae for this behavior, then we will have constructed a useful machine from modular arithmetic.

## Opacity

Recall that we have 3 images — previous, current and next — and that we only wish to see the *current* image. On the above graph, the “current” images (red squares, bold) are those whose *“index we want to display”* match the *navIndex.*

We can observe that these “current” images are rotating in a consistent pattern, returning to the same image every 3 steps; this yields a simple set of pseudocode equations for setting opacity:

## Calculating navIndex Offsets

How do we calculate the offsets needed to generate the correct values of indexWeWantToDisplay? First, we can simplify things by observing that the offsets repeat every 3 steps.

Thus, we are looking for three equations, such that inputs of 0, 1 and 2 modulo 3 yield various outputs of 0, -1 and+1 modulo 3. Trial and error yielded three such equations:

To calculate indexWeWantToDisplay, these offsets are added to the navIndex. To ensure that our navigation wraps around, we take the result modulo the length of arrayOfUrls.

## A Declarative Gallery

With our equations in hand, we can let mathematics worry about the content and opacity of our images. Let’s refactor *generateImageAndBuffers().*

Though not present in the above video, an issue does present itself. Occasionally, when clicking in rapid succession, an out-of-order image will flash momentarily. This is due to the GET request delay of the next-next image; before the request is fulfilled, a stale image is holding it’s place.

## Five Beats Three

A solution to the stale image problem is to generalize the algorithm to higher orders. Any odd number may be used, but 5 images appear to be adequate. This ensures that the next-next and previous-previous images are always preloaded. The patterns and mathematics are similar, but we are now operating modulo 5.

Again, we refactor *generateImageAndBuffers().*

## Translation

The last piece of the puzzle is translation — images should slide into and out of view. As the images are *position: absolute* in an *overflow: hidden* container, we are free to locate them at various points offscreen. Revisiting the above graph, we can add location identifiers Left1, Left2, Center, Right1 and Right2.

Here, again, a simple modular pattern emerges; our location identifiers are directly correlated with the offsets, which we are already calculating! We can simply multiply the offsets by a constant value and plug them into a *transform: translateX*. I found 50 view widths to be an acceptable constant.

## Final Refactor

Our *generateImageAndBuffers* function is the antithesis of DRY. We can refactor everything to a single map function. We’ll add a *pointerEvents* style to ensure that right-clicks target the current image. As well, we can abstract the buffer length to be dealer’s choice; though doing so requires generalizing the modular equations, which is outside the scope of this article. The end result brings us back to where we started:

In order to demonstrate what is happening under the hood, I made a version with modified styles; all of the mechanics are in plain view:

## Epilogue

A final note: if the gallery size does not evenly divide the buffer size, there will be no transition when navigation wraps between the last and first image; this is expected behavior. One solution is to exploit least-common-multiples by updating the navigation to operate modulo (arrayOfUrls.length * buffer). Keep in mind other components relying on navIndex (such as a thumbnail carriage) will need to account for the change.

A fully functioning repository of the code in this article may be found at

A demonstration of the originating project may be viewed here: