js 实现淘宝无缝轮播图效果,可更改配置参数 带完整版解析代码[slider.js]

2023-04-27,,

前言:

        本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽。

        本篇文章为您分析一下原生JS写淘宝无缝轮播图效果

需求分析:

HTML需求
 1. 首先要有一个可视区域(banner)

 2. 在可视区域(banner)下有一个存放图片的区域(imgs)

 3. 在可视区域(banner)下还要有一个存放小圆点的区域(dots)

 4. 在可视区域(banner)下还要有一个存放按钮的区域 (arrow)
CSS需求
 1. 可视区域(banner)设置定宽,超出区域需要隐藏

 2. 在可视区域(imgs)下的所有图片需要在一行内显示[imgs宽度会在JS中动态生成]

 3. 小圆点的区域(dots)下的所有子元素设置为小圆点样式

 4. 按钮的区域 (arrow)下的两个图标设置定位。[本文采用的是 ≶ <请自行替换为图标]
JS需求
 1. 可以根据用户的配置信息更改轮播图等信息

 2. 要求能够无缝轮播

 3. ----小圆点的区域(dots)下的所有子元素设置为小圆点样式

 4. ----按钮的区域 (arrow)下的两个图标设置定位。[本文采用的是 ≶ <请自行替换为图标]

HTML结构:


    <div class="banner">
        <div class="imgs" style="width:2600px">
            <!-- 下面的结构需要JS动态传入 -->
            <a href=""><img src="img/1.jpg" alt=""></a>
            <a href=""><img src="img/2.webp" alt=""></a>
            <a href=""><img src="img/3.jpg" alt=""></a>
            <a href=""><img src="img/4.jpg" alt=""></a>
            <a href=""><img src="img/5.webp" alt=""></a>
        </div>
        <div class="dots" style="width:60px">
            <!-- 下面的结构需要JS动态传入 -->
            <span></span>
            <span></span>
            <span></span>
            <span></span>
            <span></span>
        </div>
        <div class="arrow">
            <div class="left"></div>
            <div class="right"></div>
        </div>
    </div>

看看效果

  

CSS样式:

* {
    margin: 0;
    padding: 0;
}

.banner {
    position: relative;
    width: 520px;
    height: 280px;
    border: 2px solid #000000;
    margin: 100px auto;
    /* overflow: hidden; */
}

.banner .imgs img {
    display: block;
    width: 520px;
    height: 280px;
}

.banner .imgs a {
    float: left;
}

.banner .dots {
    position: absolute;
    bottom: 12px;
    left: 0;
    right: 0;
    margin: 0 auto;
    background-color: rgba(255, 255, 255, .3);
    border-radius: 10px;
    padding: 2px 4px;
}

.banner .dots span {
    float: left;
    width: 8px;
    height: 8px;
    margin: 2px;
    background-color: seashell;
    border-radius: 50%;
    cursor: pointer;
}

.banner .dots span.active {
    background-color: skyblue;
}
.banner .arrow {
    /* display: none; */
}

.banner:hover .arrow {
    display: block;
}

.banner .arrow .item {
    cursor: pointer;
    width: 20px;
    height: 30px;
    line-height: 30px;
    position: absolute;
    top: 125px;
    background-color: rgba(0, 0, 0, .3);
    padding-left: 3px;
    box-sizing: border-box;
}

.banner .arrow .item.left {
    border-radius: 0 17px 17px 0;
}

.banner .arrow .item.right {
    right: 0;
    border-radius: 17px 0 0 17px;
}

看看效果

  

效果显然不是我们想要的
因为imgs的宽度和dots的宽度都是需要JS动态计算的。所有我们为了查看效果先给他加上
因此我们暂时在HTML结构中添加如下代码

1. <div class="imgs" style="width: 2600px;">
2. <div class="dots" style="width: 60px;">

效果图如下

  

JS行为:

大致思路
 1.  设置配置参数(图片宽度,小圆点的宽度,要渲染的doms元素,要添加的图片地址等)

 2.  获取到图片的数量

 3.  初始化图片,因为imgs下的img都是需要JS动态生成的

 4.  根据图片的数量初始化元素尺寸(imgs、dots)

 5.  根据图片的数量创建相应的小圆点数量(span)

 6.  要做到无缝轮播,需要动态添加两张图片(头和尾都要增加一张图片)能够形成 5-1-2-3-4-5-1 的布局

 7.  给小圆点绑定激活状态

 8.  设置图片的初始位置,根据currentIndex设置

 9.  初始化总函数

 10. 运动函数(根据索引和方向来运动)

 11. 要图片缓缓滑动,其实就是逐渐改变他的 marginLeft 值,因此要有一个渐渐滑动函数。

 12. 在config配置定时器timer的值

 13. 计算出运动次数

 14. 判断当前的运动次数是否等于[计算出的运动次数],如果是则停止运动

 15. 计算每次改变的距离   [总距离 / 运动次数)= 每次改变的距离]   关键是总距离怎么算

 16. 计算每次运动改变的距离

 17. 重新设置他的marginLeft值

 18. 设置无缝轮播效果(对边界值的处理)

 19. 注册点击按钮事件

 20. 注册点击小圆点事件

 21. 自动轮播 (config中需要配置)

 22. 鼠标移入暂停定时器轮播,移除继续运动

// 第一步: 配置    配置是需要用户传入,调用时只需要改配置的参数即可。
var config = {
    imgWidth: 520,   // 图片的宽度
    dotWidth: 12,    // 小圆点的宽度
    doms: {          // 涉及的dom对象
        divBanner: document.querySelector(".banner"),
        divImgs: document.querySelector(".banner .imgs"),
        divDots: document.querySelector(".banner .dots"),
        divArrow: document.querySelector(".banner .arrow")
    },
    // 每张图片的地址
    imgs: ["img/1.jpg", "img/2.webp", "img/3.jpg", "img/4.jpg", "img/5.webp"],
    // 图片链接的地址
    href: ["#","#","#","#","#"]
}

// 第二步: 图片数量  动态获取  不要直接在config对象中写死,因为我们希望他是可以根据页面计算出来的,所以等config赋值完成再计算
config.imgNumber = config.imgs.length;

console.log(config);  // 可以在页面上打印看看这个对象

页面上打印结果如下

  

因为dots下的所有img都是动态添加上去的
所以我们要在JS中生成(别忘了删除HTML中的imgs下的代码)


/**
 * 第三步: 初始化所有的IMG图片
 */
function initImgs() {
    var str = "";  // 用来字符串拼接结构
    for (var i = 0; i < config.imgNumber; i++) {  // 有多少张图就循环添加几张
        // 利用es6模板字符串拼接生成HTML结构
        config.doms.divImgs.innerHTML = str += `
        <a href="${config.href[i]}">
        <img src="${config.imgs[i]}"/>
        </a>`;
        // config.href[i]   添加对应的图片链接的地址
        // config.imgs[i]   添加对应的每张图片的地址
    }
}

页面效果图如下

  

因为dots和img的宽度都是动态添加上去的
所以我们要在JS中设置他们的宽度(别忘了删除HTML中的多余的代码)

/**
 * 第四步: 初始化元素尺寸
 */
function initDivSize() {
    // 小圆点的总宽度  =  一个小圆点的宽度 * 图片的数量
    config.doms.divDots.style.width = config.dotWidth * config.imgNumber + "px";
    // 轮播图片总宽度  =  一张图片的宽度 * 图片的数量 + 2 张空白的区域 (头和尾都要增加一张图片能够形成 5-1-2-3-4-5-1 的布局)
    config.doms.divImgs.style.width = config.imgWidth * (config.imgNumber + 2) + "px";
}

页面效果如下
并且滚动条向右拉会有两个空白区域

  

下面创建开始创建小圆点的
别忘了删除HTML中的多余的代码


/**
 * 第五步: 初始化Dots元素
 */
function initDots() {
    // 5.1 创建小圆点
    for (var i = 0; i < config.imgNumber; i++) {    // 有多少张图就循环添加几个小圆点
        var span = document.createElement("span");  // 每循环一次添加一个span元素
        config.doms.divDots.appendChild(span);      // 每循环一次将span元素添加到Dots中
    }
}

页面效果如下

  

要想完成无缝轮播
需要在第一张图前添加最后一张图
最后一张图后添加第一张图
从而完成视觉差的效果


/**
 * 第六步: 复制首尾两张图片到空白区域
 */
function addNewImg() {
    var divImg = config.doms.divImgs.children;         // 6.1 复制图片先获取到所有的子元素[此处获取到的是包含a标签的所有元素]
    var first = divImg[0];                             // 保存第一张图片
    var last = divImg[divImg.length - 1];              // 保存最后一张图片
    var newImg = first.cloneNode(true);                // 深度克隆就是连他的img也一起克隆了。[cloneNode(true)]
    config.doms.divImgs.appendChild(newImg);           // 把克隆到的第一张图片添加到imgs后面
    newImg = last.cloneNode(true);                     // 重新给newImg赋值  深度克隆[就是连他的img也一起克隆了。cloneNode(true)]
    config.doms.divImgs.insertBefore(newImg, first);   // 把克隆到的最后一张图片添加到第一张图片前面  [insertBefore(新的元素,谁的前面)]
}

效果图如下:

  

设置小圆点的状态
在config中新添加一个 currentIndex: 0 的属性
根据currentIndex来设置小圆点状态


/**
 * 第七步: 设置小圆点状态
 */
function initDotStatu() {
    for (var i = 0; i < config.imgNumber; i++){     // 有多少图就循环多少次
        var dot = config.doms.divDots.children[i];  // 保存循环的当前i项
        if (config.currentIndex === i) {            // 如果当前的i等于了配置的currentIndex则给他添加一个class类名(激活状态)
            dot.className = "active";
        } else {
            dot.className = "";
        }
    }
}

页面效果如下
可更改currentIndex的值查看相应的页面效果

  

下面开始设置图片的初始位置
图片的初始位置应该是根据currentIndex的值来设置的
先来分析一下:

  

假设当前的索引为currentIndex为0
那么marginLeft = (-CurrentIndex - 1)* imgWidth;
代码如下:


/**
 * 第八步: 设置图片的位置,根据currentIndex来设置
 */
function initImgPosition() {
    var left = (-config.currentIndex - 1) * config.imgWidth;  // 看图分析
    config.doms.divImgs.style.marginLeft = left + "px";  // 重新设置divImgs的位置
}

至此,我们把初始化初始化工作完成。
因为这里有六个初始化函数,所以我们可以写一个汇总初始化的方法;来调用他们


/**
 * 第九步: 初始化总函数
 */
function init() {
    initImgs();
    initDivSize();
    initDots();
    addNewImg();
    setDotStatu();
    initImgPosition();
}
init();

那么,接下来的工作就不简单了,要完成切换的效果
运动函数switchTo要根据参数的index和direction进行变化
代码如下:


/**
 * 第十步: 运动函数
 * @param {Number} index       传入的index索引值
 * @param {String} direction  "left"     "right"
 */
function switchTo(index, direction) {
    // 10.1 设置默认方向
    if (!direction) {
        direction = "left";
    }
    // 10.2 如果索引一样就啥也不做,直接返回
    if (index === config.currentIndex) {
        return;
    }
    // 10.3 运动函数最终的目的是为了什么?  (改变marginLeft)
    var newLeft = (-index - 1) * config.imgWidth;
    // 10.4 调用动画函数 [暂时放着,等到 第十一步 完成再添加这个函数]
    animationSwich();
    // 10.5 重新更新currentIndex的值;
    config.currentIndex = index;
    // 10.6 改变完marginLeft之后的小圆点状态是不是也要更新?所以在这里也要调用下setDotStatu函数
    setDotStatu();
}

那么,动画滑动渐渐改变marginLeft的值是不是需要定时器来操作
因此我们在config配置中添加一些必须的定时器代码(我们需要知道每张图运动的间隔时间,还有总时间)
代码如下:


    timer: {  // 第十一步: 运动计时器的配置
        duration: 16,  // margin-left运动间隔的时间,单位毫秒
        total: 500,  //  margin-left运动的总时间,单位毫秒
        id: null  //  计时器的id
    },

有了定时器的配置,我们开始编写一个基本的定时器函数
在switchTo函数内创建一个定时的animationSwich函数
代码如下:


/**
 * 第十二步(10.4.1): 设置动画,逐步改变margin-left的值  [定时器]
 */
function animationSwich() {
    stopAnimation();
        // 需要保存一些配置
        // ......
    config.timer.id = setInterval(function () {
        // 需要做的的事情(改变marginLeft值)
        // ......
    }, config.timer.duration);
}

// 第十二步(12.1): 清空定时器函数
function stopAnimation() {
    clearInterval(config.timer.id);  // 清空当前的定时器
    config.timer.id = null;          // 设置当前的定时器为空
}

关键是需要做的事情是
每一次运行时改变的多少
还有就是我要改变多少次?到了一定次数我们要停止

代码如下:

function animationSwich() {
    stopAnimation();
    // 第十三步:运动的次数  向上取整(总时间 / 运动间隔时间)
    var number = Math.ceil(config.timer.total / config.timer.duration);
    config.timer.id = setInterval(function () {
        // 需要做的的事情(改变marginLeft值)
        // ......
    }, config.timer.duration);
}

得到当前的的运动次数
每次启动定时器是加一
如果当前的运动次数等于[计算出的运动次数],就停止运动


/**
 * 第十二步(10.4.1): 设置动画,逐步改变margin-left的值  [定时器]
 */
function animationSwich() {
    stopAnimation();
    // 第十三步: 运动的次数  向上取整(总时间 / 运动间隔时间)
    var number = Math.ceil(config.timer.total / config.timer.duration);
    // 第十四步: 当前的运动次数
    var curNumber = 0;
    config.timer.id = setInterval(function () {
        // 第十四步(14.1)
        curNumber++;                 // 每次加一
        if (curNumber === number) {  // 当前的运动次数等于[计算出的运动次数]
            stopAnimation();         // 停止运动
        }
    }, config.timer.duration);       // 运动间隔
}

要想计算每次改变的距离
就要总距离除以次数
关键是如何计算总距离?
我们先来看下面的分析图

图片往左边运动

  

图片往右边运动

  

分析图看懂了我们开始上代码


/**
 * 第十二步(10.4.1): 设置动画,逐步改变margin-left的值  [定时器]
 */
function animationSwich() {
    stopAnimation();
    // 第十三步: 运动的次数  向上取整(总时间 / 运动间隔时间)
    var number = Math.ceil(config.timer.total / config.timer.duration);
    // 第十四步: 当前的运动次数
    var curNumber = 0;
    // 第十五步: 计算总距离
    var distance;  // 15.1 定义一个总距离
    // 15.2 他是一个包含像素的字符串,所以我们把他转换成数字
    var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
    var totalWidth = config.imgNumber * imgWidth;
    // 15.3 如果他的方向为左
    if (direction === "left") {
        // 目标的newLeft 小于当前的marginLeft          [newLeft在 10.3中我们已经得到了]
        if (newLeft < marginLeft) {
            // 总距离 = 目标的newLeft - 当前的marginLeft
            distance = newLeft - marginLeft;
        } else {
            // 总距离 = - (总距离 - |目标的newLeft - 当前的marginLeft| 绝对值)
            distance = -(totalWidth - Math.abs(newLeft - marginLeft));
        }
    } else {
        if (newLeft > marginLeft) {
            // 总距离 = 目标的newLeft - 当前的marginLeft
            distance = newLeft - marginLeft;
        } else {
            // 总距离 = 总距离 - |目标的newLeft - 当前的marginLeft| 绝对值
            distance = totalWidth - Math.abs(newLeft - marginLeft);
        }
    }
    config.timer.id = setInterval(function () {
        // 第十四步(14.1)
        curNumber++;                 // 每次加一
        if (curNumber === number) {  // 当前的运动次数等于[计算出的运动次数]
            stopAnimation();         // 停止运动
        }
    }, config.timer.duration);       // 运动间隔
}

总距离有了,运动次数有了,接着计算每次改变的距离


/**
 * 第十二步(10.4.1): 设置动画,逐步改变margin-left的值  [定时器]
 */
function animationSwich() {
    stopAnimation();
    // 第十三步: 运动的次数  向上取整(总时间 / 运动间隔时间)
    var number = Math.ceil(config.timer.total / config.timer.duration);
    // 第十四步: 当前的运动次数
    var curNumber = 0;
    // 第十五步: 计算总距离
    var distance;  // 15.1 定义一个总距离
    // 15.2 他是一个包含像素的字符串,所以我们把他转换成数字
    var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
    var totalWidth = config.imgNumber * imgWidth;
    // 15.3 如果他的方向为左
    if (direction === "left") {
        // 目标的newLeft 小于当前的marginLeft          [newLeft在 10.3中我们已经得到了]
        if (newLeft < marginLeft) {
            // 总距离 = 目标的newLeft - 当前的marginLeft
            distance = newLeft - marginLeft;
        } else {
            // 总距离 = - (总距离 - |目标的newLeft - 当前的marginLeft| 绝对值)
            distance = -(totalWidth - Math.abs(newLeft - marginLeft));
        }
    } else {
        if (newLeft > marginLeft) {
            // 总距离 = 目标的newLeft - 当前的marginLeft
            distance = newLeft - marginLeft;
        } else {
            // 总距离 = 总距离 - |目标的newLeft - 当前的marginLeft| 绝对值
            distance = totalWidth - Math.abs(newLeft - marginLeft);
        }
    }

    // 第十六步: 计算每次改变的距离   总距离 / 次数
    var everyDistance = distance / number;

    config.timer.id = setInterval(function () {
        // 第十四步(14.1)
        curNumber++;                 // 每次加一
        if (curNumber === number) {  // 当前的运动次数等于[计算出的运动次数]
            stopAnimation();         // 停止运动
        }
    }, config.timer.duration);       // 运动间隔
}

每次marginLeft值给他重新加上每次运动改变的距离
重新设置他的marginLeft值


            config.timer.id = setInterval(function () {
            // 第十七步:每一次给他重新加上每次改变的距离
            marginLeft += everyDistance;
            // 第十七步:17.1 重新设置图片的marginLeft值
            config.doms.divImgs.style.marginLeft = marginLeft + "px";
            // 第十四步(14.1)
            curNumber++;                 // 每次加一
            if (curNumber === number) {  // 当前的运动次数等于[计算出的运动次数]
                stopAnimation();         // 停止运动
            }
        }, config.timer.duration);       // 运动间隔

然后我们在页面中调用它
效果如下图

  

一直往下调用函数,会出现空白的区域
因此我们要判断边界,当到达最后一张图片时,重置他的位置(marginLeft)
当到达第一张图片时,同样需要重置他的位置(marginLeft)
看下面的分析:

  

代码如下


        // 第十六步: 计算每次改变的距离   总距离 / 次数
        var everyDistance = distance / number;
        // 第十八 18.1: 判断临界值(无缝轮播)图片往左滑动并且当前的marginLeft值超过了总宽度 [marginLeft是负的,所以要用绝对值]
        if (direction === "left" && Math.abs(marginLeft) > totalWidth) {
            marginLeft += totalWidth;
        // 第十八 18.2: 判断临界值(无缝轮播)图片往右滑动并且当前的marginLeft值小于了了一个图片宽度时]
        } else if (direction === "right" && Math.abs(marginLeft) < config.imgWidth) {
            marginLeft -= totalWidth;
        }

由于五张图页面过大,我缩减为三张进行演示
效果演示

  

上面的运动函数写完后剩下的东西就比较简单了
一个小圆点事件,一个按钮事件
我们先来注册点击小圆点的事件


/**
 * 第十九步: 利用事件委托注册点击按钮事件
 */
config.doms.divArrow.onclick = function (e) {
    // 如果事件源中包含有left的属性
    if (e.target.classList.contains("left")) {
        toLeft(); // 调用图片向左的函数
    } else {
        toRight();// 调用图片向右的函数
    }
}

/**
 * 第十九步 19.2: 利用事件委托注册点击按钮事件
 */
function toLeft() {
    // 只需让他的index每点击一次减一
    var index = (config.currentIndex - 1);
    // 判断边界
    if (index < 0) {
    // 等于数量 -  1   因为index是从0开始的
        index = config.imgNumber - 1;
    }
    // 调用运动函数
    switchTo(index, "right")
}

/**
 * 第十九步 19.2: 利用事件委托注册点击按钮事件
 */

function toRight() {
    // 19.3 只需让他的index每点击一次加一   取余数。   如果是0  0+1/数量 取余 1
    var index = (config.currentIndex + 1) % config.imgNumber;
    // 调用运动函数
    switchTo(index, "left")
}

效果图如下

  

注册小圆点的点击事件
根据index来判断


/**
 * 第二十步: 利用事件委托注册小圆点的点击事件
 */
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");
    }
}

效果图如下

  

自动轮播的计时器配置与调用


    autoTimer: {  // 第二十一步:自动轮播的计时器
        duration: 2000,  // 每隔多长时间切换一张图片的,单位毫秒
        id: null//  计时器的id
    } 

// 调用自动轮播
config.autoTimer.id = setInterval(toRight, config.autoTimer.duration);

移入事件
移出事件


/**
 * 最后
 */
config.doms.divBanner.onmouseenter = function () {
    clearInterval(config.autoTimer.id);
    config.autoTimer = null;
}
config.doms.divBanner.onmouseleave = function () {
    if (config.autoTimer.id) {
        return;
    } else {
        config.autoTimer.id = setInterval(toRight, config.autoTimer.duration);
    }
}

附上完整代码

HTML结构

    <div class="banner">
        <div class="imgs">
        </div>
        <div class="dots">
        </div>
        <div class="arrow">
            <div class="item left">&lt;</div>
            <div class="item right">&gt;</div>
        </div>
    </div>
    <script src="./js/index.js"></script>

CSS样式


* {
    margin: 0;
    padding: 0;
}

.banner {
    position: relative;
    width: 520px;
    height: 280px;
    border: 2px solid #000000;
    margin: 100px auto;
    overflow: hidden;
}

.banner .imgs img {
    display: block;
    width: 520px;
    height: 280px;
}

.banner .imgs a {
    float: left;
}

.banner .dots {
    position: absolute;
    bottom: 12px;
    left: 0;
    right: 0;
    margin: 0 auto;
    background-color: rgba(255, 255, 255, .3);
    border-radius: 10px;
    padding: 2px 4px;
}

.banner .dots span {
    float: left;
    width: 8px;
    height: 8px;
    margin: 2px;
    background-color: seashell;
    border-radius: 50%;
    cursor: pointer;
}

.banner .dots span.active {
    background-color: skyblue;
}
.banner .arrow {
    display: none;
}

.banner:hover .arrow {
    display: block;
}

.banner .arrow .item {
    cursor: pointer;
    width: 20px;
    height: 30px;
    line-height: 30px;
    position: absolute;
    top: 125px;
    background-color: rgba(0, 0, 0, .3);
    padding-left: 3px;
    box-sizing: border-box;
}

.banner .arrow .item.left {
    border-radius: 0 17px 17px 0;
}

.banner .arrow .item.right {
    right: 0;
    border-radius: 17px 0 0 17px;
}

JS行为


// 第一步: 配置    配置是需要用户传入,调用时只需要改配置的参数即可。
var config = {
    imgWidth: 520,   // 图片的宽度
    dotWidth: 12,    // 小圆点的宽度
    doms: {          // 涉及的dom对象
        divBanner: document.querySelector(".banner"),
        divImgs: document.querySelector(".banner .imgs"),
        divDots: document.querySelector(".banner .dots"),
        divArrow: document.querySelector(".banner .arrow")
    },
    // 每张图片的地址
    imgs: ["img/1.jpg", "img/2.webp", "img/3.jpg", "img/4.jpg", "img/5.webp"],
    // 图片链接的地址
    href: ["#", "#", "#", "#", "#"],
    // 实际的图片数索引     取值范围: 0 ~ imgNumber - 1
    currentIndex: 0,
    timer: {  // 第十一步: 运动计时器的配置
        duration: 16,  // margin-left运动间隔的时间,单位毫秒
        total: 500,  //  margin-left运动的总时间,单位毫秒
        id: null  //  计时器的id
    },
    autoTimer: {  // 第二十一步:自动轮播的计时器
        duration: 2000,  // 每隔多长时间切换一张图片的,单位毫秒
        id: null//  计时器的id
    }
}

// 第二步: 图片数量  动态获取  不要直接在config对象中写死,因为我们希望他是可以根据页面计算出来的,所以等config赋值完成再计算
config.imgNumber = config.imgs.length;

/**
 * 第三步: 初始化所有的IMG图片
 */
function initImgs() {
    var str = "";  // 用来字符串拼接结构
    for (var i = 0; i < config.imgNumber; i++) {  // 有多少张图就循环添加几张
        // 利用es6模板字符串拼接生成HTML结构
        config.doms.divImgs.innerHTML = str += `
        <a href="${config.href[i]}">
        <img src="${config.imgs[i]}"/>
        </a>`;
        // config.href[i]   添加对应的图片链接的地址
        // config.imgs[i]   添加对应的每张图片的地址
    }
}

/**
 * 第四步: 初始化元素尺寸
 */
function initDivSize() {
    // 小圆点的总宽度  =  一个小圆点的宽度 * 图片的数量
    config.doms.divDots.style.width = config.dotWidth * config.imgNumber + "px";
    // 轮播图片总宽度  =  一张图片的宽度 * 图片的数量 + 2 张空白的区域 (头和尾都要增加一张图片能够形成 5-1-2-3-4-5-1 的布局)
    config.doms.divImgs.style.width = config.imgWidth * (config.imgNumber + 2) + "px";
}

/**
 * 第五步: 初始化Dots元素
 */
function initDots() {
    // 5.1 创建小圆点
    for (var i = 0; i < config.imgNumber; i++) {    // 有多少张图就循环添加几个小圆点
        var span = document.createElement("span");  // 每循环一次添加一个span元素
        config.doms.divDots.appendChild(span);      // 每循环一次将span元素添加到Dots中
    }
}

/**
 * 第六步: 复制首尾两张图片到空白区域
 */
function addNewImg() {
    var divImg = config.doms.divImgs.children;         // 6.1 复制图片先获取到所有的子元素[此处获取到的是包含a标签的所有元素]
    var first = divImg[0];                             // 保存第一张图片
    var last = divImg[divImg.length - 1];              // 保存最后一张图片
    var newImg = first.cloneNode(true);                // 深度克隆就是连他的img也一起克隆了。[cloneNode(true)]
    config.doms.divImgs.appendChild(newImg);           // 把克隆到的第一张图片添加到imgs后面
    newImg = last.cloneNode(true);                     // 重新给newImg赋值  深度克隆[就是连他的img也一起克隆了。cloneNode(true)]
    config.doms.divImgs.insertBefore(newImg, first);   // 把克隆到的最后一张图片添加到第一张图片前面  [insertBefore(新的元素,谁的前面)]
}

/**
 * 第七步: 设置小圆点状态
 */
function setDotStatu() {
    for (var i = 0; i < config.imgNumber; i++) {     // 有多少图就循环多少次
        var dot = config.doms.divDots.children[i];  // 保存循环的当前i项
        if (config.currentIndex === i) {            // 如果当前的i等于了配置的currentIndex则给他添加一个class类名(激活状态)
            dot.className = "active";
        } else {
            dot.className = "";
        }
    }
}

/**
 * 第八步: 设置图片的位置,根据currentIndex来设置
 */
function initImgPosition() {
    var left = (-config.currentIndex - 1) * config.imgWidth;  // 看图分析
    config.doms.divImgs.style.marginLeft = left + "px";  // 重新设置divImgs的位置
}

/**
 * 第九步: 初始化总的函数
 */
function init() {
    initImgs();
    initDivSize();
    initDots();
    addNewImg();
    setDotStatu();
    initImgPosition();
}

init();

/**
 * 第十步: 运动函数
 * @param {Number} index       传入的index索引值
 * @param {String} direction  "left"     "right"
 */
function switchTo(index, direction) {
    // 10.1 设置默认方向
    if (!direction) {
        direction = "left";
    }
    // 10.2 如果索引一样就啥也不做,直接返回
    if (index === config.currentIndex) {
        return;
    }
    // 10.3 运动函数最终的目的是为了什么?  (改变marginLeft)  [目标值:newLeft]
    var newLeft = (-index - 1) * config.imgWidth;
    // 10.4 调用动画函数 [暂时放着,等到 第十一步 完成再添加这个函数]
    animationSwich();
    // 10.5 重新更新currentIndex的值;
    config.currentIndex = index;
    // 10.6 改变完marginLeft之后的小圆点状态是不是也要更新?所以在这里也要调用下setDotStatu函数
    setDotStatu();

    /**
     * 第十二步(10.4.1): 设置动画,逐步改变margin-left的值  [定时器]
     */
    function animationSwich() {
        stopAnimation();
        // 第十三步: 运动的次数  向上取整(总时间 / 运动间隔时间)
        var number = Math.ceil(config.timer.total / config.timer.duration);
        // 第十四步: 当前的运动次数
        var curNumber = 0;
        // 第十五步: 计算总距离
        var distance;  // 15.1 定义一个总距离
        // 15.2 他是一个包含像素的字符串,所以我们把他转换成数字
        var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
        // 图片的真实宽度
        var totalWidth = config.imgNumber * config.imgWidth;
        // 15.3 如果他的方向为左
        if (direction === "left") {
            // 目标的newLeft 小于当前的marginLeft          [newLeft在 10.3中我们已经得到了]
            if (newLeft < marginLeft) {
                // 总距离 = 目标的newLeft - 当前的marginLeft
                distance = newLeft - marginLeft;
            } else {
                // 总距离 = - (总距离 - |目标的newLeft - 当前的marginLeft| 绝对值)
                distance = -(totalWidth - Math.abs(newLeft - marginLeft));
            }
        } else {
            if (newLeft > marginLeft) {
                // 总距离 = 目标的newLeft - 当前的marginLeft
                distance = newLeft - marginLeft;
            } else {
                // 总距离 = 总距离 - |目标的newLeft - 当前的marginLeft| 绝对值
                distance = totalWidth - Math.abs(newLeft - marginLeft);
            }
        }

        // 第十六步: 计算每次改变的距离   总距离 / 次数
        var everyDistance = distance / number;
        config.timer.id = setInterval(function () {
            // 第十七步:每一次给他重新加上每次改变的距离
            marginLeft += everyDistance;

            // 第十八 18.1: 判断临界值(无缝轮播)图片往左滑动并且当前的marginLeft值超过了总宽度 [marginLeft是负的,所以要用绝对值]
            if (direction === "left" && Math.abs(marginLeft) > totalWidth) {
                marginLeft += totalWidth;
                // 第十八 18.2: 判断临界值(无缝轮播)图片往右滑动并且当前的marginLeft值小于了了一个图片宽度时]
            } else if (direction === "right" && Math.abs(marginLeft) < config.imgWidth) {
                marginLeft -= totalWidth;
            }
            // 第十七步:17.1 重新设置图片的marginLeft值
            config.doms.divImgs.style.marginLeft = marginLeft + "px";
            // 第十四步(14.1)
            curNumber++;                 // 每次加一
            if (curNumber === number) {  // 当前的运动次数等于[计算出的运动次数]
                stopAnimation();         // 停止运动
            }
        }, config.timer.duration);       // 运动间隔
    }

    // 第十二步(12.1): 清空定时器函数
    function stopAnimation() {
        clearInterval(config.timer.id);  // 清空当前的定时器
        config.timer.id = null;          // 设置当前的定时器为空
    }
}

/**
 * 第十九步: 利用事件委托注册点击按钮事件
 */
config.doms.divArrow.onclick = function (e) {
    // 如果事件源中包含有left的属性
    if (e.target.classList.contains("left")) {
        toLeft(); // 调用图片向左的函数
    } else {
        toRight();// 调用图片向右的函数
    }
}

/**
 * 第十九步 19.2: 利用事件委托注册点击按钮事件
 */
function toLeft() {
    // 只需让他的index每点击一次减一
    var index = (config.currentIndex - 1);
    // 判断边界
    if (index < 0) {
    // 等于数量 -  1   因为index是从0开始的
        index = config.imgNumber - 1;
    }
    // 调用运动函数
    switchTo(index, "right")
}

/**
 * 第十九步 19.2: 利用事件委托注册点击按钮事件
 */

function toRight() {
    // 19.3 只需让他的index每点击一次加一   取余数。   如果是0  0+1/数量 取余 1
    var index = (config.currentIndex + 1) % config.imgNumber;
    // 调用运动函数
    switchTo(index, "left")
}

/**
 * 第二十步: 利用事件委托注册小圆点的点击事件
 */
config.doms.divDots.onclick = function (e) {
    // 如果事件源的span
    if (e.target.tagName === "SPAN") {
        // 将事件源下的子元素变成数组,再利用数组中的indexOf方法获取点击到的span元素的下标
        var index = Array.from(this.children).indexOf(e.target);
        // 再运用三目运算符来判断传入的方向   当前的span元素下表如果比原来的大就是图片往左运动,否则往右
        switchTo(index, index > config.currentIndex ? "left" : "right");
    }
}

config.autoTimer.id = setInterval(toRight, config.autoTimer.duration);

/**
 * 最后
 */
config.doms.divBanner.onmouseenter = function () {
    clearTimeout(config.autoTimer.id);
    config.autoTimer.id = null;
}
config.doms.divBanner.onmouseleave = function () {
    if (config.autoTimer.id) {
        return;
    } else {
        config.autoTimer.id = setInterval(toRight, config.autoTimer.duration);
    }
}

结语

整完!

js 实现淘宝无缝轮播图效果,可更改配置参数 带完整版解析代码[slider.js]的相关教程结束。

《js 实现淘宝无缝轮播图效果,可更改配置参数 带完整版解析代码[slider.js].doc》

下载本文的Word格式文档,以方便收藏与打印。