前言:

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

        本篇文章为您分析一下原生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]的更多相关文章

  1. js 实现淘宝放大镜功能,可更改配置参数 带完整版解析代码[magnifier.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写淘宝放大镜效果 基本功能: 运 ...

  2. js 实现图片瀑布流效果,可更改配置参数 带完整版解析代码[waterFall.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS实现图片瀑布流效果 页面需求 1 ...

  3. js 实现文字滚动功能,可更改配置参数 带完整版解析代码。

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写文字滚动效果 需求分析: 需要 ...

  4. js 实现对象的混合与克隆效果,带完整版解析代码[helpers.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写淘宝无缝轮播图效果 对象混合 ...

  5. 原生JS实现淘宝无缝轮播

    <!DOCTYPE html ><html><head><meta http-equiv="Content-Type" content=& ...

  6. 纯js实现淘宝商城轮播图

    需求: 循环无缝自动轮播3张图片,点击左右箭头可以手动切换图片,鼠标点击轮播图下面的小圆点会跳转到对应的第几张图片.鼠标放到轮播图的图片上时不再自动轮播,鼠标移开之后又继续轮播.效果图: 下面是htm ...

  7. JQ无缝轮播图-插件封装

    类似京东的这种无缝轮播效果: 实例代码下载 HTML代码: <body> <!-- /*觅me 探索生活*/ --> <div class="test" ...

  8. js原生实现轮播图效果(面向对象编程)

    面向对象编程js原生实现轮播图效果 1.先看效果图 2.需要实现的功能: 自动轮播 点击左右箭头按钮无缝轮播 点击数字按钮切换图片 分析:如何实现无缝轮播? 在一个固定大小的相框里有一个ul标签,其长 ...

  9. jQuery插件slides实现无缝轮播图特效

    初始化插件: slides是一款基于jQuery无缝轮播图插件,支持图内元素动画,可以自定义动画类型 1 2 3 4 5 6 7 8 9 10 $(".slideInner").s ...

随机推荐

  1. java 第六周上机练习 04.09

    1.编写一个简单程序,要求数组长度为5,静态赋值10,20,30,40,50,在控制台输出该数组的值. int [] arr= {10,20,30,40,50}; for(int i=0;i<a ...

  2. 使用ElasticSearch赋能HBase二级索引 | 实践一年后总结

    前言:还记得那是2018年的一个夏天,天气特别热,我一边擦汗一边听领导大刀阔斧的讲述自己未来的改革蓝图.会议开完了,核心思想就是:我们要搞一个数据大池子,要把公司能灌的数据都灌入这个大池子,然后让别人 ...

  3. Spring ApplicationContext 容器

    Spring ApplicationContext 容器 Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文. Application Con ...

  4. 并发——深入分析CountDownLatch的实现原理

    一.前言   最近在研究java.util.concurrent包下的一些的常用类,之前写了AQS.ReentrantLock.ArrayBlockingQueue以及LinkedBlockingQu ...

  5. uni-app商城项目(01)

    1.项目准备: 1.新建项目,清理项目结构 2.完成项目初始化配置. 2.项目开始阶段: 1.完成tabBar配置,新建需要的页面 2.在 '/utis'封装需要的发送请求api,有利于功能的实现. ...

  6. Docker 常用命令(.NET Core示例)

    Docker安装 CentOS Docker 安装 安装 Docker Desktop for Mac.Docker Desktop for Windows 设置docker仓库镜像加速器 迁移Doc ...

  7. k8s pod yaml参数说明

  8. leetcode 30 day challenge Counting Elements

    Counting Elements Given an integer array arr, count element x such that x + 1 is also in arr. If the ...

  9. Netty是如何处理新连接接入事件的?

    更多技术分享可关注我 前言 前面的分析从Netty服务端启动过程入手,一路走到了Netty的心脏——NioEventLoop,又总结了Netty的异步API和设计原理,现在回到Netty服务端本身,看 ...

  10. Linux访问Window共享文件夹的配置步骤

    1. Window下创建用户XXX(作用:Linux mount时需要提供用户和密码) 2. Window下共享文件夹给XXX用户,并根据实际需要设置读取/写入权限 3. Linux下创建挂载的目录 ...