原生JS实现无缝轮播

什么叫无缝轮播

无缝轮播是网页中比较常见的一种轮播方式,很多网站首页都应用了无缝轮播,这种效果使页面看起来更顺滑。

先来看一下我实现的效果图:
原生JS实现无缝轮播_第1张图片

无缝轮播的实现

下面用示意图清楚的展现了无缝轮播的原理:
原生JS实现无缝轮播_第2张图片
一共有3张图片轮播,但是为什么有5张图片呢?这是因为要想实现无缝轮播的效果,就要用“障眼法”。

每当图片滚动到最后一张或者第一张时,都会自动回到第一张或最后一张,加入这两张图片有一个过渡的效果。

原理分析

好了,在介绍了无缝轮播之后,小伙伴们是不是跃跃欲试了呢,别着急,在正式写代码之前让我们先把逻辑理清楚。

假设当前正在播放的图片是1,令currentIndex = 1,当前left = 实际运动过程中距离左边的长度,目标left = 要切换的图片距离左边的长度,且这两个均为负数。

原生JS实现无缝轮播_第3张图片

如果当前图片移动方向是向左,当目标left < 当前left时,假设是从0移动到2:

//目标left,index是要切换的图片的索引,imgWidth是图片宽度
var newLeft = index * imgWidth;
//计算需要移动的总距离
var distance = newLeft - marginLeft;

目标left > 当前left时,假设是从2移动到0:

//3张图片总宽
var totalWidth = 3 * igmWidth;
distance = -(totalWidth - Math.abs(newLeft - marginLeft));

接下来分析图片向右移动的情况
原生JS实现无缝轮播_第4张图片
目标left > 当前left时,这时候移动的就是正数了

distance = newLeft - marginLeft;

目标left < 当前left

distance = totalWidth - Math.abs(newLeft - marginLeft);

设置动画效果

好的,既然逻辑清楚了,接下来就是愉快的写代码环节。

/**
 * 切换到某一个图片的索引
 * @param {*} index 要切换到目标的图片的索引
 * @param {*} direction 图片移动的方向 "left" "right"
 */
function switchTo(index, direction) {
    if (index == config.currentIndex) {
        return;
    }
    if (!direction) {
        direction = "left";
    }
    //最终的marginLeft
    var newLeft = (-index - 1) * config.imgWidth;
    animateSwitch();
    //重新设置当前索引
    config.currentIndex = index;
    //设置小圆点的选中状态
    setDotsActiveStatus();

    /**
     * 逐步改变marginLeft
     */
    function animateSwitch() {
        //先停止之前的动画
        stopAnimate();
        //1、计算运动的次数
        var number = Math.ceil(config.timer.total / config.timer.duration);
        //当前的运动次数
        var currentNum = 0;

        //2、计算需要移动总距离
        var distance;
        //获取当前的left值
        var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
        var totalWidth = config.imgsNum * config.imgWidth;
        if (direction == "left") {
            if (newLeft < marginLeft) {
                distance = newLeft - marginLeft;
            } else {
                distance = -(totalWidth - Math.abs(newLeft - marginLeft));
            }
        } else {
            if (newLeft > marginLeft) {
                distance = newLeft - marginLeft;
            } else {
                distance = totalWidth - Math.abs(newLeft - marginLeft);
            }
        }

        //3、计算每次运动的距离
        var everyDistance = distance / number;

        //将函数注册为config.timer.duration秒后调用,之后每隔config.timer.duration之后重复调用
        config.timer.id = setInterval(function () {
            //改变div的marginLeft
            marginLeft += everyDistance;
            if (direction == "left" && Math.abs(marginLeft) > totalWidth) {
                marginLeft += totalWidth;
            } else if (direction == "right" && Math.abs(marginLeft) < config.imgWidth) {
                marginLeft -= totalWidth;
            }

            config.doms.divImgs.style.marginLeft = marginLeft + "px";

            currentNum++;
            if (currentNum == number) {
                stopAnimate();
            }
        }, config.timer.duration)

        function stopAnimate() {
            clearInterval(config.timer.id);
            config.timer.id = null;
        }
    }
}

这里我定义了一个函数去处理切换图片的逻辑,里面的animateSwitch函数就是处理切换动画的,注意首先要停止当前动画,要不然一直点击的时候,就会一直执行这个函数,导致速度越来越快。
之后就是我们前面分析过的逻辑,计算出移动的总距离,再算出每次运动的距离,通过这个距离再调用setInterval计时器,需要判断的是当图片移动到最后一张时怎么重新回到第一张,这里我们把marginLeft的绝对值减去总宽度。
原生JS实现无缝轮播_第5张图片
当到达marginLeft = totalWidth这个临界值时,说明图片到了最后一张或者第一张,这个时候判断一下图片的运动方向,如果图片向左运动,下一次刷新假如到了下图位置:
原生JS实现无缝轮播_第6张图片
这个时候令marginLeft = marfinLeft + totalWidth就会运动到如下图位置,当然这个在浏览器中刷新是非常快的,人眼是感觉不到已经变化的了
原生JS实现无缝轮播_第7张图片
图片向右运动同理,只需要将marginLeft = marfinLeft - totalWidth即可

最后要取消动画的时候,只需要把计时器清除,并将id设为null。

以上就是无缝轮播的核心代码以及逻辑分析,仅供参考,各位小伙伴们如果看懂了一定要自己动手练习,我会把整个js代码和整个项目都放在末尾,有需要的可以看一下,也可以私信我发给你。前端知识多而乱,只有掌握好基础后期才能游刃有余。

附JS代码

//轮播配置对象
var config = {
    imgWidth: 640, //图片宽度
    dotsWidth: 8, //圆点宽度
    doms: { //dom对象
        divImgs: document.querySelector('.img'),
        divDots: document.querySelector('.dots'),
        divArrow: document.querySelector('.arrow'),
        divLeft: document.querySelector('.left'),
        divRight: document.querySelector('.right'),
    },
    currentIndex: 0, //当前显示的图片索引 0 ~ imgsNum-1
    timer: { //运动计数器配置
        duration: 16, //运动间隔的时间
        total: 500, //运动的总时间,单位毫秒
        id: null //计时器的id
    },
    autoTimer: null //自动移动的计时器
};

//图片数量
config.imgsNum = config.doms.divImgs.children.length;

/**
 * 初始化函数
 */
function init() {
    //初始化元素尺寸
    initSize();
    //初始化元素
    initElements();
    //初始化显示图片的位置
    initImgPosition();
    //设置小圆点的选中状态
    setDotsActiveStatus();
}
init();

/**
 * 初始化元素尺寸
 */
function initSize() {
    //包裹圆点的尺寸,+6是margin相加的值
    config.doms.divDots.style.width = (config.dotsWidth + 6) * config.imgsNum + "px";
    //轮播图整体尺寸
    config.doms.divImgs.style.width = config.imgWidth * (config.imgsNum + 2) + "px";
}

/**
 * 初始化元素
 */
function initElements() {
    //创建小圆点
    for (var i = 0; i < config.imgsNum; i++) {
        var span = document.createElement('span');
        config.doms.divDots.appendChild(span);
    }
    //复制图片,实现无缝轮播
    var children = config.doms.divImgs.children;
    //第一张图片
    var first = children[0];
    //最后一张图片
    var last = children[children.length - 1];
    var newImg = first.cloneNode(true); //深度克隆
    config.doms.divImgs.appendChild(newImg);
    newImg = last.cloneNode(true); //深度克隆
    config.doms.divImgs.insertBefore(newImg, first);
}

/**
 * 初始化显示图片的位置
 */
function initImgPosition() {
    //设置的currentIndex为几就显示第几张图片
    var left = (-config.currentIndex - 1) * config.imgWidth;
    config.doms.divImgs.style.marginLeft = left + "px";
}

/**
 * 设置小圆点的选中状态
 */
function setDotsActiveStatus() {
    //小圆点外边框
    var children = config.doms.divDots.children;
    for (var i = 0; i < children.length; i++) {
        var dot = children[i];
        if (i == config.currentIndex) {
            dot.className = "active";
        } else {
            dot.className = "";
        }
    }
}


/**
 * 切换到某一个图片的索引
 * @param {*} index 要切换到目标的图片的索引
 * @param {*} direction 图片移动的方向 "left" "right"
 */
function switchTo(index, direction) {
    if (index == config.currentIndex) {
        return;
    }
    if (!direction) {
        direction = "left";
    }
    //最终的marginLeft
    var newLeft = (-index - 1) * config.imgWidth;
    animateSwitch();
    //重新设置当前索引
    config.currentIndex = index;
    setDotsActiveStatus();

    /**
     * 逐步改变marginLeft
     */
    function animateSwitch() {
        //先停止之前的动画
        stopAnimate();
        //1、计算运动的次数
        var number = Math.ceil(config.timer.total / config.timer.duration);
        //当前的运动次数
        var currentNum = 0;

        //2、计算需要移动总距离
        var distance;
        //当前的left值
        var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
        var totalWidth = config.imgsNum * config.imgWidth;
        if (direction == "left") {
            if (newLeft < marginLeft) {
                distance = newLeft - marginLeft;
            } else {
                distance = -(totalWidth - Math.abs(newLeft - marginLeft));
            }
        } else {
            if (newLeft > marginLeft) {
                distance = newLeft - marginLeft;
            } else {
                distance = totalWidth - Math.abs(newLeft - marginLeft);
            }
        }

        //3、计算每次运动的距离
        var everyDistance = distance / number;

        //将函数注册为config.timer.duration秒后调用,之后每隔config.timer.duration之后重复调用
        config.timer.id = setInterval(function () {
            //改变div的marginLeft
            marginLeft += everyDistance;
            if (direction == "left" && Math.abs(marginLeft) > totalWidth) {
                marginLeft += totalWidth;
            } else if (direction == "right" && Math.abs(marginLeft) < config.imgWidth) {
                marginLeft -= totalWidth;
            }

            config.doms.divImgs.style.marginLeft = marginLeft + "px";

            currentNum++;
            if (currentNum == number) {
                stopAnimate();
            }
        }, config.timer.duration)

        function stopAnimate() {
            clearInterval(config.timer.id);
            config.timer.id = null;
        }
    }
}

/**
 * 点击左右按钮和小圆点切换图片
 */
function changeImg() {
    //给按钮注册点击事件
    config.doms.divArrow.onclick = function (e) {
        if (e.target.classList.contains("left")) {
            var index = config.currentIndex - 1;
            if (index < 0) {
                index = config.imgsNum - 1;
            }
            switchTo(index, "right");
        } else {
            var index = (config.currentIndex + 1) % config.imgsNum;
            switchTo(index, "left");
        }
    }

    //给小圆点注册点击事件
    config.doms.divDots.onclick = function (e) {
        if (e.target.tagName == "SPAN") {
            var index = Array.from(this.children).indexOf(e.target);
            switchTo(index, index > config.currentIndex ? "left" : "right");
        }
    }
}

//点击左右按钮切换图片
changeImg();

/**
 * 自动轮播
 */
config.autoTimer = setInterval(function() {
    var index = (config.currentIndex + 1) % config.imgsNum;
    switchTo(index, "left");
}, 3000);

整个项目zip

百度网盘链接:https://pan.baidu.com/s/1vQB8t_dI1zZeeXa-TA9rzw
提取码:6ie9

你可能感兴趣的:(前端开发人员,JS,无缝轮播)