How to Build a 5‑Image Infinite Swiper with Custom Gestures and Dynamic Layout

This article walks through designing and implementing a five‑image Swiper component that supports left‑right swipe gestures, infinite looping, and any number of images, detailing gesture handling, circular‑list data structures, rendering logic, performance optimizations, and edge‑case fixes with complete JavaScript code examples.

Aotu Lab
Aotu Lab
Aotu Lab
How to Build a 5‑Image Infinite Swiper with Custom Gestures and Dynamic Layout

Left‑right swipe

The swiper listens for touchstart and touchmove events. Gesture detection is preferred over raw finger dragging for performance. The following pseudocode shows how to capture the initial touch point, compute the offset on move, and decide swipe direction based on a threshold (±50 px).

swiper.on("touchstart", startHandle);
swiper.on("touchmove", moveHandle);
function startHandle(e) {
    var x0 = e.touch.pageX, y0 = e.touch.pageY;
}
function moveHandle(e) {
    var x = e.touch.pageX, y = e.touch.pageY;
    var offsetX = x0 - x, offsetY = y0 - y;
    if (offsetX <= -50) {
        // swipe right
    } else if (offsetX >= 50) {
        // swipe left
    }
}

Infinite loop

JavaScript lacks native pointers, so a circular linked list is simulated with an array. The head and tail are linked by using pop / unshift and shift / push operations. Navigation updates the array as follows:

if (left) {
    queue.push(queue.shift());   // move head to tail
    swap("left");               // render
} else {
    queue.unshift(queue.pop()); // move tail to head
    swap("right");              // render
}

Rendering treats the five visible items as a pyramid of layers, each with a specific z-index and other CSS. The swap function applies the style array to the DOM nodes.

function swap() {
    // queue is the circular list, nodelist is the collection of <li> elements
    for (var i = 0; i < 5; ++i) {
        nodelist[queue[i]].style.cssText = css[i];
    }
}
var css = [
    "z-index: 3; /* other css */",
    "z-index: 2; /* other css */",
    "z-index: 1; /* other css */",
    "z-index: 1; /* other css */",
    "z-index: 2; /* other css */"
];

Arbitrary number of images

The implementation distinguishes three cases: count == 5 (ideal), count > 5, and count < 5.

count > 5

When more than five images are present, the circular list is expanded. A snapshot array maps each node to its current style; each mutation creates a new snapshot ( nextSnap) that is diff‑checked against the previous snapshot to update only changed DOM nodes.

// initialization
function init() {
    nodelist = document.querySelectorAll("li");
    n = nodelist.length;
    queue = [];
    for (var i = 0; i < n; ++i) queue.push(i); // circular list
    snapshot = new Array(n);
    for (var i = 0; i < n; ++i) {
        nodelist[i].style.cssText = defaultCssText;
    }
}
var defaultCssText = "visibility: hidden";
var css = [
    "z-index: 3; /* other css */",
    "z-index: 2; /* other css */",
    "z-index: 1; /* other css */",
    "z-index: 1; /* other css */",
    "z-index: 2; /* other css */"
];
function swap() {
    var nextSnap = new Array(n);
    // only the first, second, third, last‑1 and last items are visible
    var visibleIdx = [0,1,2,n-2,n-1];
    for (var i in visibleIdx) {
        nextSnap[queue[visibleIdx[i]]] = css[i];
    }
    for (var i = 0; i < n; ++i) {
        if (snapshot[i] !== nextSnap[i]) {
            snapshot[i] = nextSnap[i];
            nodelist[i].style.cssText = snapshot[i] || defaultCssText;
        }
    }
}

count < 5

For fewer than five images the visible pyramid varies. Separate CSS arrays are defined for each possible count (1‑4) and a corresponding render order is selected via a switch. The render list is then built by alternating shift and pop operations to keep the visual order symmetric.

// CSS definitions for each count
var css1 = ["z-index: 1; /* other css */"]; // 1 image
var css2 = ["z-index: 2; /* other css */", "z-index: 1; /* other css */"]; // 2 images
var css3 = ["z-index: 2; /* other css */", "z-index: 1; /* other css */", "z-index: 1; /* other css */"]; // 3 images
var css4 = ["z-index: 3; /* other css */", "z-index: 2; /* other css */", "z-index: 2; /* other css */", "z-index: 1; /* other css */"]; // 4 images

switch (count) {
    case 4: css = css4; renderList = [1,4,2,3]; break;
    case 3: css = css3; renderList = [1,3,2]; break;
    case 2: css = css2; renderList = [1,2]; break;
    default: css = css1; renderList = [1]; break;
}

function swap() {
    // build a symmetric render list (max 5 items)
    var odd = true;
    while (queue.length && renderList.length < 5) {
        renderList.push(odd ? queue.pop() : queue.shift());
        odd = !odd;
    }
    for (var i = 0; i < renderList.length; ++i) {
        nodelist[renderList[i]].style.cssText = css[i];
    }
}

Detail optimizations

Identical z-index values caused rendering order to depend on DOM insertion order, producing visual glitches. Adding a 3‑D transform forces the browser to respect the explicit stacking order.

// enable 3D perspective
swiper.style["-webkit-transform-style"] = "preserve-3d";
var css = [
    "z-index: 3; transform: translateZ(10px)",
    "z-index: 2; transform: translateZ(6px)",
    "z-index: 2; transform: translateZ(6px)",
    "z-index: 1; transform: translateZ(2px)",
    "z-index: 1; transform: translateZ(2px)"
];

When the number of images is even, the pyramid becomes asymmetrical during right‑ward navigation. A simple orientation flag and parity check correct the position of the last visible node.

function swap(orientation) {
    var odd = true;               // parity flag
    var renderList = [];
    while (queue.length && renderList.length < 5) {
        renderList.push(odd ? queue.pop() : queue.shift());
        odd = !odd;
    }
    var last = renderList.length - 1;
    for (var i = 0; i < renderList.length; ++i) {
        var style = (orientation === "right" && !odd && i === last) ? css[i+1] : css[i];
        nodelist[renderList[i]].style.cssText = style;
    }
}

Source code and demo pages are hosted at the following URLs (plain text):

https://github.com/leeenx/mobile-swiper

Demo v1: https://leeenx.github.io/mobile-swiper/v1.html

Demo v2 (with 3‑D fix): https://leeenx.github.io/mobile-swiper/v2.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

swipergestureCarouselinfinite loop
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.