Aelous-Blog

养一猫一狗,猫叫夜宵,狗叫宵夜

0%

JS实现焦点轮播图

轮播图是在大家学习 JavaScript 时常写的小项目,网络上相关的资源也很多。我在学习的过程中,将写轮播图的经历做一些简单的总结,希望大家在看后能有所收获,如果有不对的地方,请大家在留言区指出。

前言

轮播图是什么

Q:轮播图是什么? A:简单解释:所谓的轮播图,就是几张图片轮流显示。

我们先随便找个网站访问一下,例如 淘宝网 首页中间这一块。

淘宝网首页轮播图

中间的滚动区域就是轮播图。

哪些知识点

通过轮播图,我们可以巩固(学到)什么知识点呢?

  1. DOM 操作 HTML
  2. 事件运用
  3. 定时器
  4. 无限滚动技巧
  5. JavaScript 动画
  6. 函数递归

注意


个人建议:作为新手的我们,不管要做多么高大上的特效,都先完成静态页面,再去添加动态的处理!!!切记切记!


原理

实现效果

轮播图的实现效果:即在一个窗口,鼠标移入后左右箭头会出现,可以点击动画切换图片,同时下面的小圆点会跟随切换(点击小圆点可以实现同样的功能),可以在此基础上循环播放。

总结:轮播图片具备以下特点:

  1. 点击左右两边的箭头切换图片
  2. 当鼠标移出图片范围,自动间隔时间切换;当鼠标移入图片范围,停止自动切换
  3. 切换到某一张图片时,底部的圆点样式也跟着改变
  4. 点击底部圆点也会切换到相应位置的图片

基本布局

我们首先要明白轮播图的实现原理和基本布局:

将一系列大小相等的图片平铺,利用 CSS 布局只显示一张图片,其余隐藏。通过 JS 代码修改图片的偏移量实现切换,按钮绑定切换事件,或者定时器实现自动播放。

初始基本结构如下:

  • 最外层需要有一个容器包裹着(通常是 div,类型定义为 container)
  • 容器设置宽高,以及 overflow 为 hidden,超出宽高部分隐藏,
  • 容器里面包含着两个容器:imgList 和 btnList 以及两个 a 标签(左右按钮)
  • imgList 中包裹着所有的图片,宽为所有图片的总宽度,position 为 absolute(通过改变 left 来实现图片轮播的效果)
  • btnList 中包裹着轮播图下部的指示圆点,position 也为 absolute

样式可以自己按照自己想要的修改,在此我们主要将关键部分。并且完成代码。

为节省页面空间,之后代码均不格式化,请大家自行修改。


html 结构

html 代码按照我们讲的基本布局完成,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>
<!--主容器-->
<div id="container">
<!--图片容器-->
<div id="imgList" style="left: -600px">
<img src="img/5.jpg" alt="1" />
<img src="img/1.jpg" alt="1" />
<img src="img/2.jpg" alt="2" />
<img src="img/3.jpg" alt="3" />
<img src="img/4.jpg" alt="4" />
<img src="img/5.jpg" alt="5" />
<img src="img/1.jpg" alt="5" />
</div>
<!--按钮容器-->
<div id="btnList">
<span index="1" class="on"></span>
<span index="2"></span>
<span index="3"></span>
<span index="4"></span>
<span index="5"></span>
</div>
<!--翻页按钮-->
<a href="#" id="prev" class="arrow">&lt;</a>
<a href="#" id="next" class="arrow">&gt;</a>
</div>
</body>

代码说明:btnList 中的每一个 span 标签我们添加了自定义属性 index,是为了在 JS 处理时更方便;imgList 定义了内联属性,是为了初始显示第一张图片。

CSS 样式

代码的 CSS 样式由我们各自喜好定制即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
* { margin: 0; padding: 0; text-decoration: none; }
body { padding: 20px; }
#container { width: 600px; height: 400px; border: 3px solid #333; overflow: hidden; position: relative; }
#imgList { width: 4200px; height: 400px; position: absolute; z-index: 1; }
#imgList img { float: left; }
#btnList { position: absolute; height: 10px; width: 100px; z-index: 2; bottom: 20px; left: 250px;}
#btnList span { cursor: pointer; float: left; border: 1px solid #fff; width: 10px; height: 10px; border-radius: 50%; background: #333; margin-right: 5px; }
#btnList .on { background: orangered; }
.arrow { cursor: pointer; display: none; line-height: 39px; text-align: center; font-size: 36px; font-weight: bold; width: 40px; height: 40px; position: absolute; z-index: 2; top: 180px; background-color: RGBA(0, 0, 0, 0.3); color: #fff; }
.arrow:hover { background-color: RGBA(0, 0, 0, 0.7); }
#container:hover .arrow { display: block; }
#prev { left: 20px; }
#next { right: 20px; }

JS 代码书写

接下来让我们一步一步完成。

获取页面的 DOM 元素,如下:

1
2
3
4
5
let oDiv = document.getElementById('container')
let oImgList = document.getElementById('imgList')
let oButtons = document.getElementById('btnList').getElementsByTagName('span')
let oPrev = document.getElementById('prev')
let oNext = document.getElementById('next')

给两个按钮绑定点击事件,来移动 imgList:

1
2
3
4
5
6
oPrev.onclick = function() {
oImgList.style.left = parseInt(oImgList.style.left) + 600 + 'px' // 右移600px
}
oNext.onclick = function() {
oImgList.style.left = parseInt(oImgList.style.left) - 600 + 'px' // 左移600px
}

抽象出 animate

两个点击事件中移动 imgList 的代码我们可以抽象出来,改为如下:

1
2
3
4
5
6
7
8
9
function animate(offsetLeft) {
oImgList.style.left = parseInt(oImgList.style.left) + offsetLeft + 'px'
}
oPrev.onclick = function() {
animate(600) // 整体右移600px,相当于往前翻页
}
oNext.onclick = function() {
animate(-600) // 整体左移600px,相当于往后翻页
}

此时按下 next 按钮和 prev 按钮可以实现翻页,但是没有边界判断,会翻出空白来,所以我们添加边界条件。

当我们翻页到第一张时,再翻上一页,会翻到第 5 张图,所以我们在第一张前面添加了一个第五张图片的缓冲图同理当你翻页到第五张之后,再翻下一页,会到第一张的缓冲图

此处我多做一些说明:

假设我们有 N 张照片,把容器宽度设置为N+2个图片的宽度,示例如下图,两端填充如图,当处于一端时,且即将进入循环状态的时候,如第二张图,从状态 1 滑动到状态 2,在滑动结束的时候,将当前的位置直接转到状态 3,直接修改容器偏移,在视觉上是循环的。

无限滚动原理

添加边界条件

将 animate 改为如下代码:

1
2
3
4
5
6
7
8
9
function animate(offsetLeft) {
oImgList.style.left = parseInt(oImgList.style.left) + offsetLeft + 'px'
if (parseInt(oImgList.style.left) > -600) { // 抵达第5张的预设图
oImgList.style.left = -3000 + 'px' // 切换到第五张图
}
if (parseInt(oImgList.style.left) < -3000) {
oImgList.style.left = -600 + 'px' // 切换到第一张图
}
}

将代码中的冗余变量做提取:

1
2
3
4
5
6
7
8
9
10
function animate(offsetLeft) {
let newLeft = parseInt(oImgList.style.left) + offsetLeft
oImgList.style.left = newLeft + 'px'
if (newLeft > -600) { // 抵达第5张的预设图
oImgList.style.left = -3000 + 'px' // 切换到第五张图
}
if (newLeft < -3000) { // 抵达第一张预设图
oImgList.style.left = -600 + 'px' // 切换到第一张图
}
}

按钮基础效果完成
可以看到基础效果已经完成。

绑定圆点跟随效果

我们在按钮按下轮播图轮播的时候,需要底部圆点也跟随移动用作指示效果:

首先我们定义一个变量 index 方便操作,我们要在每次点击时显示圆点变化,代码如下:

1
2
3
4
5
6
7
8
9
10
11
oPrev.onclick = function() {
index -= 1 // 角标值减1
showButton() // 显示圆点变化
animate(600) // 整体右移600px,相当于往前翻页
}

oNext.onclick = function() {
index += 1
showButton() // 显示圆点变化
animate(-600) // 整体左移600px,相当于往后翻页
}

那么显示部分的代码如何编写呢?

1
2
3
4
5
6
7
8
9
function showButton() {
for (let i = 0; i < oButtons.length; i++) {
if (oButtons[i].className === 'on') { // 其余圆点样式关闭
oButtons[i].className = ''
break // 如果已经清除了圆点样式,直接跳出循环,减少资源消耗
}
}
oButtons[index - 1].className = 'on' // 当前索引的圆点点亮
}

此时尝试,会发现,圆点跟着变化了,但是圆点并没有设置边界条件,接下来给圆点设置边界:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
oPrev.onclick = function() {
if (index === 1) { // 边界设置
index = 5
} else {
index -= 1 // 角标值减1
}
showButton() // 显示圆点变化
animate(600) // 整体右移600px,相当于往前翻页
}

oNext.onclick = function() {
if (index === 5) { //边界设置
index = 1
} else {
index += 1
}
showButton() // 显示圆点变化
animate(-600) // 整体左移600px,相当于往后翻页
}

圆点动画效果完成

绑定圆点点击事件

我们想要实现在点击圆点的时候,图片也会进行切换,思考一下和点击翻页有什么不同??

翻页时:每次只会左移和右移固定的宽度,但是点击圆点时,左移和右移的宽度需要我们计算得出,比如从 1 到 3 那就需要移动 1200px。
如何实现呢?这时候我们的自定义属性就有用了,我们在 html 代码中对每个 span 标签添加了 index 这个自定义属性,可以通过 index 属性的值和程序中的 index 来计算宽度。代码如下:

1
2
3
4
5
6
7
8
9
for (let i = 0; i < oButtons.length; i++) {
oButtons[i].onclick = function() { // 遍历圆点,绑定点击事件
let myIndex = parseInt(this.getAttribute('index')) // 获取自定义属性值
let offsetLeft = -600 * (myIndex - index) // 计算宽度
animate(offsetLeft) // 移动
index = myIndex // 更新index值
showButton() // 更新圆点动画
}
}

优化

如果当前页面在第一张图片,再次点击第一张图片,不应该进行切换,所以我们应该给是否点击当前页面做判断。

1
2
3
4
5
6
7
8
9
for (let i = 0; i < oButtons.length; i++) {
oButtons[i].onclick = function() { // 遍历圆点,绑定点击事件
if (this.className === 'on') { // 判断是否有必要执行
return
}
...
...
}
}

至此,我们的基本页面已经完成了,接下来完成动画效果部分。

动画效果

动画效果我们不采用 CSS3 实现,全部采用 JS 实现,利用 JS 的递归,在一定时间之内,对 left 的偏移量进行细分,细分过后逐步实现。

修改 animate 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function animate(offsetLeft) {
let newLeft = parseInt(oImgList.style.left) + offsetLeft
let time = 300 // 位移总时间
let interval = 10 // 位移间隔时间
let speed = offset / (time / interval) // 每次的位移量

function go() {
if (
(speed < 0 && parseInt(oImgList.style.left) > newLeft) ||
(speed > 0 && parseInt(oImgList.style.left) < newLeft)
) { // 判断条件
oImgList.style.left = parseInt(oImgList.style.left) + speed + 'px'
setTimeout(go, interval) // 间隔10ms再次执行go(递归)
} else {
oImgList.style.left = newLeft + 'px'
if (newLeft > -600) { // 抵达第5张的预设图
oImgList.style.left = -3000 + 'px' // 切换到第五张图
}
if (newLeft < -3000) { // 抵达第一张预设图
oImgList.style.left = -600 + 'px' // 切换到第一张图
}
}
}

go() // 执行
}

此时基本动画已经执行完毕,但是会有 bug,在动画执行过程中,点击到按钮或者圆点,会再次触发动画,导致不规则情况出现。如下图:

动画不规则情况

所以我们应该在每次执行的时候判断是否当前正在动画,此时定义一个标志位 animated,初始值为 false。
在执行动画的时候将 animated 置为 true,执行完毕为 false。在每次执行的时候添加判断即可,将 animate 修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function animate(offsetLeft) {
if (animated) {
return
}
animated = true
...
function go() {
if (
...
} else {
animated = false
...
}
}
go() // 执行
}

自动播放

我们希望在鼠标滑出图片,不做任何操作的时候,图片自动播放,鼠标滑入时,停止自动播放。

自动播放就相当于,每隔一定的时间,执行一次 next.onclik() 事件:

1
2
3
4
5
6
7
8
9
10
11
function play() { // 自动播放
timer = setInterval(function() { // 定时3秒执行next.onclick()
next.onclick()
}, 3000)
}
function stop() {
clearInterval(timer) // 清除定时器
}
oDiv.onmouseout = play // 鼠标移除执行play
oDiv.onmouseover = stop // 鼠标移入执行stop
play() // 首次执行

至此我们的焦点轮播图就完成了,效果图如下:

给出所有 JavaScript 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
window.onload = function () {
let oDiv = document.getElementById('container')
let oImgList = document.getElementById('imgList')
let oButtons = document.getElementById('btnList').getElementsByTagName('span')
let oPrev = document.getElementById('prev')
let oNext = document.getElementById('next')
let index = 1
let animated = false
let timer
// 显示底部圆点
function showButton () {
for (let i = 0; i < oButtons.length; i++) {
if (oButtons[i].className === 'on') { // 其余圆点样式关闭
oButtons[i].className = ''
break // 如果已经清除了圆点样式,直接跳出循环,减少资源消耗
}
}
oButtons[index - 1].className = 'on' // 当前索引的圆点点亮
}
// 动画
function animate (offsetLeft) {
if (animated) {
return
}
animated = true
let newLeft = parseInt(oImgList.style.left) + offsetLeft
let time = 1000 // 位移总时间
let interval = 10 // 位移间隔时间
let speed = offsetLeft / (time / interval) // 每次的位移量
function go () {
if ((speed < 0 && parseInt(oImgList.style.left) > newLeft) || (speed > 0 && parseInt(oImgList.style.left) < newLeft)) {
oImgList.style.left = parseInt(oImgList.style.left) + speed + 'px'
setTimeout(go, interval) // 间隔10ms再次执行go(递归)
} else {
animated = false
oImgList.style.left = newLeft + 'px'
if (newLeft > -600) { // 抵达第5张的预设图
oImgList.style.left = -3000 + 'px' // 切换到第五张图
}
if (newLeft < -3000) { // 抵达第一张预设图
oImgList.style.left = -600 + 'px' // 切换到第一张图
}
}
}
go() // 执行
}
function play () {
timer = setInterval(function () {
next.onclick() // 间隔触发
}, 1000)
}
function stop () {
clearInterval(timer) //清除定时器
}
oDiv.onmouseout = play
oDiv.onmouseover = stop
play() // 首次执行
oPrev.onclick = function () { // 上翻
if (index === 1) { // 边界设置
index = 5
} else {
index -= 1 // 角标值减1
}
showButton() // 显示按钮变化
animate(600) // 整体右移600px,相当于往前翻页
}
oNext.onclick = function () { // 下翻
if (index === 5) { // 边界设置
index = 1
} else {
index += 1
}
showButton() // 显示圆点变化
animate(-600) // 整体左移600px,相当于往后翻页
}
// 绑定底部圆点
for (let i = 0; i < oButtons.length; i++) {
oButtons[i].onclick = function () { // 遍历圆点,绑定点击事件
if (this.className === 'on') { // 判断是否有必要执行
return
}
let myIndex = parseInt(this.getAttribute('index')) // 获取自定义属性值
let offsetLeft = -600 * (myIndex - index) // 计算宽度
animate(offsetLeft) // 移动
index = myIndex // 更新index值
showButton() // 更新圆点动画
}
}
}

Github地址如下:
https://github.com/DongpoXu/imooc

参考

慕课网焦点轮播图
封装一个简单的原生 js 焦点轮播图插件

End~~ 撒花= ̄ω ̄=花撒
如果您读文章后有收获,可以打赏我喝咖啡哦~