The basic idea

The idea is to copy the first picture and insert it into the end of the rotation map according to the general practice of seamless rotation map, so as to smooth the transition from the last picture to the first picture. The process is shown in the following figure, and the principle of sliding forward from no. 1 to No. 4 is also the same.

transform

Transform was chosen because of its performance advantages. The properties of the Transform are calculated at the end of the browser rendering pipeline, the Composite phase, as shown in the figure, which means that the properties of the Transform do not affect the backflow and redraw of the page. The principle is that in the Composite phase, properties are calculated by linear mapping through the bitmap information cache of the original elements. During the animation process, the bitmap information cache of the original elements is not changed, thus avoiding triggering backflow and redrawing.

Implementation and rendering issues

The main code

/** * params: new_index target index * return: returns the current image position of the wheel map */

function update(new_index) {

        if (new_index < 0) {
            new_index = data.length - 1;
            _move(-width * data.length, false);
            _index = 0;
            dataBox.offsetWidth;     // Force the render queue to refresh
        }

        if (new_index > data.length) {
            new_index = 1;
            _move(0.false);
            _index = 0;
            dataBox.offsetWidth;
        }
        
        _move(-new_index * width, true);
        _index = new_index;

        return (_index % data.length);
    }
    
function _move(distance, animation) {
        dataBox.style.transition = animation ? 'transform .5s' : 'none';
        dataBox.style.transform = `translateX(${distance}px)`;
    }
Copy the code

OffsetWidth Refreshes the render queue

The reason why offsetWidth is used to forcibly refresh the rendering queue after the no. 1 image at the end of the queue is switched to the no. 1 image at the beginning of the queue is that the current browser will add some DOM attribute modification operations to the rendering queue and finally refresh the rendering queue for a backflow, so as to reduce the number of backflow of the page. However, this would result in two changes to the Transition Transform property, as shown in the code above, retaining only the last value during backflow. The following figure shows the effect of no refresh and refresh respectively. Refreshing the render queue is not limited to offsetWidth, such as offsetTop, offsetLeft, offsetWidth, offsetHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, ClientTop, clientLeft, clientWidth, clientHeight, etc. I have also seen requestAnimationFrame(()=>{requestAnimationFrame(callback)}) being used to make style changes in the callback to be regenerated in the next frame.

The complete code

function Slide(dom, data) {

    let app, width, height, dataBox, _index, timer, timeout;

    function _create() {

        if (dom == null || data == null || data.length < 2) {
            throw new Error('create failure');
        }

        app = document.getElementById(dom);
        app.style.cssText = 'overflow: hidden';
        appStyle = getComputedStyle(app);
        width = parseInt(appStyle.width);
        height = parseInt(appStyle.height);
        dataBox = document.createElement('div');
        _index = 0;

        dataBox.style.cssText = `
            position: relative;
            height: ${height}px;
            width: ${(data.length + 1) * width}px;
        `;

        imgCSS = `
            width: ${width}px;
            height: ${height}px;
            float: left;
        `

        data.forEach(img= > {
            let el = document.createElement('img');
            el.src = img;
            el.style.cssText = imgCSS;
            dataBox.appendChild(el);
        });

        let el = document.createElement('img');
        el.src = data[0];
        el.style.cssText = imgCSS;
        dataBox.appendChild(el);

        app.appendChild(dataBox);

    }

    function _move(distance, animation) {
        dataBox.style.transition = animation ? 'transform .5s' : 'none';
        dataBox.style.transform = `translateX(${distance}px)`;
    }

    function _autoPlay() {
        clearTimeout(timer);
        timer = setTimeout(() = > {
            next();
            _autoPlay();
        }, timeout);
    }

    function update(new_index) {

        if (new_index < 0) {
            new_index = data.length - 1;
            _move(-width * data.length, false);
            _index = 0;
            dataBox.offsetWidth;
        }

        if (new_index > data.length) {
            new_index = 1;
            _move(0.false);
            _index = 0;
            dataBox.offsetWidth;
        }
        
        _move(-new_index * width, true);
        _index = new_index;

        return (_index % data.length);
    }

    function next() {
        return update(_index + 1);
    }

    function pre() {
        return update(_index - 1);
    }

    function setTime(ms = 1000) {
        let start = () = > { _autoPlay() }
        let end = () = > { clearTimeout(timer) }

        if (ms == -1) {
            app.removeEventListener('mouseover', end);
            app.removeEventListener('mouseleave', start);
            end();
        } else {
            timeout = ms < 500 ? 1000 : ms;
            app.addEventListener('mouseover', end);
            app.addEventListener('mouseleave', start);
            start();
        }
    }

    _create();

    return { next, pre, update, setTime }; //setTime enter -1 to close the animation
}

Copy the code

Refer to the link

From the principle of understanding, how to use CSS3 transition to create infinite wheel map

High Performance Web Animation and Rendering Principles series 2 — Rendering pipelines and CPU Rendering

High Performance Web Animation and Rendering Principles series (3) — Transform and Opacity