前言:
本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽。
本篇文章为您分析一下原生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结构中添加如下代码
因为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下的代码)
所以我们要在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中的多余的代码)
所以我们要在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中的多余的代码
别忘了删除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来设置小圆点状态
在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的值来设置的
先来分析一下:
图片的初始位置应该是根据currentIndex的值来设置的
先来分析一下:
假设当前的索引为currentIndex为0
那么marginLeft = (-CurrentIndex - 1)* imgWidth;
代码如下:
那么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进行变化
代码如下:
运动函数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配置中添加一些必须的定时器代码(我们需要知道每张图运动的间隔时间,还有总时间)
代码如下:
因此我们在config配置中添加一些必须的定时器代码(我们需要知道每张图运动的间隔时间,还有总时间)
代码如下:
timer: { // 第十一步: 运动计时器的配置
duration: 16, // margin-left运动间隔的时间,单位毫秒
total: 500, // margin-left运动的总时间,单位毫秒
id: null // 计时器的id
},
有了定时器的配置,我们开始编写一个基本的定时器函数
在switchTo函数内创建一个定时的animationSwich函数
代码如下:
在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值
重新设置他的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)
看下面的分析:
因此我们要判断边界,当到达最后一张图片时,重置他的位置(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来判断
根据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"><</div>
<div class="item right">></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);
}
}
结语
整完!