前言: 轮播图应该是每个前端人员都会的东西。写这一篇的原因是去面试,然后面试官让我写轮播图,写是写出来了, 但是思路很混乱,代码很丑....so sad
一、HTML结构
<div class="wrapper-container"> // 需要显示设置宽高 <div class="wrapper" id="wrapper"> // 继承高度 宽度 = 轮播图宽度 * 图片数量 <img src="./images/1.webp" alt=""> <img src="./images/2.jpg" alt=""> <img src="./images/3.webp" alt=""> </div>
</div>
// 比较简单 img如果需要做包裹可以加a标签或者li标签,自行看需求
二、CSS样式
.wrapper-container { position: relative; // 最外层的盒子要设置position width: 590px; height: 470px; overflow: hidden; // 溢出隐藏,只显示宽度内的元素 margin-left: 300px; } .wrapper { position: absolute; left: 0; top: 0; width: 300%; // 宽度自行根据轮播图片的数量 transition: all .5s; } .wrapper img { width: 590px; display: block; // 处理图片间隙 float: left; }
首先,我们要让一个元素动,就得先元素脱离文档流,也就是设置absolute定位。
内层盒子的宽度 = 图片的数量 * 一个图片的宽度
到这里,我们第一步的结构样式都已经搭建完成
下一步,我们让图片动起来,自动播放。
三、JS
1.自动播放
自动播放涉及两个概念,定时器函数,设置dom节点的偏移
// 相关变量的初始化
let index = 0, dom = document.querySelector("#wrapper"), // 内层盒子 wrapper = document.querySelector(".wrapper-container"), // 外层盒子 timer = null, // 定时器标记 childLength = dom.children.length, // 图片的数量 stepWidth = dom.children[0].offsetWidth // 最外层盒子宽度——对应每次偏移的位置大小
// 写一个autoPlay的函数
function autoPlay () {
index++ // 这里++或者是--看自己想轮播往哪个方向走
index = index === childLength ? 0 : index // 这里的判断是 如果index到了最后一张,那就让他回到第一张,达到重复轮播的目的
dom.style.left = -index * stepWidth + 'px' // 这里我给index取反了,是因为我的轮播从左往右轮播,假设一开始偏移值 0 ,第二次偏移值就是 -590px, 第三次就是 -590px * 2
timer = setTimeout(autoPlay, 3000)
}
// 写好函数了,调用跑起来
timer = setTimeout(autoPlay, 3000) // setTimeout只跑一次,执行到autoPlay函数内部由触发了一次,达到循环播放
到这里,一个基本的轮播就算完成了一小半了。
下面,我们来完善细节
在原有的基础上,添加当前图片的位置标记,如图
二、添加图片位置标记
在原有的结构上修改一下
<div class="wrapper" id="wrapper"> // 继承高度 宽度 = 轮播图宽度 * 图片数量 <img src="./images/1.webp" alt=""> <img src="./images/2.jpg" alt=""> <img src="./images/3.webp" alt=""> </div>
<div class="indicator"> <li class="active"></li> <li></li> <li></li> </div>
/* 他和wrapper是 同级关系 */
样式
.indicator { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 3; } .indicator li { display: inline-block; width: 10px; height: 10px; background-color: rgba(0, 0, 0, .7); } .indicator li.active { background-color: rgba(255, 255, 255, .8); // 当前显示的状态颜色 }
逻辑部分
let activeLi = document.querySelector(".active"), indicator = document.querySelector(".indicator"), allLi = indicator.children
// 我们添加一个domChange()函数,来控制位置标记的显示状态
function domChange() {
activeLi = document.querySelector(".active") // 为了清除上一个active,需要每次重新获取
activeLi.className = ""
allLi[index].className = 'active' // 这里的index是全局的,是我们上次autoPlay去修改后的index,index始终是当前显示图片的索引值
}
到这里,位置标志的跟随显示应该也可以了。我们接着做下一次
三、添加前后按钮之——鼠标进入暂停autoPlay
我们给外层盒子注册两个函数——mouseenter,mouseleave
// 思路应该很清晰,当我们鼠标进入盒子的时候,让函数暂停,也就是清除定时器
// clearTimeout();
// 当鼠标离开了盒子,再开启一个相同的定时器
const handleMouseEnter = function () {
clearTimeout(timer)
} const handleMouseLeave = function () { clearTimeout(timer) timer = setTimeout(autoPlay, 3000) } wrapper.addEventListener('mouseenter', handleMouseEnter) wrapper.addEventListener('mouseleave', handleMouseLeave)
做完了以上,我们现在来添加前后按钮
首先是页面结构,同样是indicator和wrapper是同级的
<div class="indicator"> <li class="active"></li> <li></li> <li></li> </div> <div class="arrow-container"> <div class="arrow-prev"><</div> <div class="arrow-next">></div> </div>
样式修改
.arrow-container { position: absolute; top: 0; left: 0; display: flex; width: 100%; height: 100%; justify-content: space-between; align-items: center; } .arrow-prev, .arrow-next { font-size: 50px; color: #fff; cursor: pointer; }
接下来是逻辑代码
arrowPrev.onclick = function () { index-- // 往左走,做-- index = index === -1 ? allLi.length - 1 : index // 再判断是否第一张,由于先--了,所以是 0 - 1 = -1 domChange() } arrowNext.onclick = function () { index++ // 往右走,做++ index = index === allLi.length ? 0 : index domChange() }
这时候我们对domChange函数修改一下
function domChange () {
activeLi = document.querySelector(".active")
activeLi.className = ''
allLi[index].className = 'active'
// 添加了这一句,因为很多地方都需要对这个dom进行操作
dom.style.left = -index * stepWidth + 'px'
}
回到autoPaly函数中
function autoPlay () {
index++
index = index === childLength ? 0 : index
// 我们去掉原来直接在这里设置的dom.style.left
// 而是调用函数
domChange()
timer = setTimeout(autoPlay, 3000)
}
以上至此,初略写了一个带按钮,标记的轮播图。
如果你还有兴趣,比如说划过li标签的时候,让图片也动,或者是多个侧边栏,右边是轮播,然后正常显示内容。
以下是源码
HTML跟JS
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="./css/index.css"> </head> <body> <div class="banner-container"> <div class="nav-container"> <div class="li-container"> <li index="1">电子产品</li> <li index="2">生活用品</li> <li index="3">每日超市</li> <li index="4">电竞游戏</li> <li index="5">斗鱼直播</li> </div> <div class="content-container"> <div>电子产品</div> <div>生活用品</div> <div>每日超市</div> <div>电竞游戏</div> <div>斗鱼直播</div> </div> </div> <div class="wrapper-container"> <div class="wrapper" id="wrapper"> <img src="./images/1.webp" alt=""> <img src="./images/2.jpg" alt=""> <img src="./images/3.webp" alt=""> </div> <div class="indicator"> <li class="active"></li> <li></li> <li></li> </div> <div class="arrow-container"> <div class="arrow-prev"><</div> <div class="arrow-next">></div> </div> </div> </div> <script> let index = 0, dom = document.querySelector("#wrapper"), wrapper = document.querySelector(".wrapper-container"), timer = null, childLength = dom.children.length, stepWidth = dom.children[0].offsetWidth, running = false let activeLi = document.querySelector(".active"), indicator = document.querySelector(".indicator"), allLi = indicator.children let arrowPrev = document.querySelector(".arrow-prev"), arrowNext = document.querySelector(".arrow-next") let navLiWrapper = document.querySelector(".li-container"), contentWrapper = document.querySelector(".content-container") timer = setTimeout(autoPlay, 3000) function autoPlay () { if (running) { return } running = true index++ index = index === childLength ? 0 : index domChange() setTimeout(() => running = false, 300) timer = setTimeout(autoPlay, 3000) } const handleMouseEnter = () => clearTimeout(timer) const handleMouseLeave = function () { clearTimeout(timer) timer = setTimeout(autoPlay, 3000) } wrapper.addEventListener('mouseenter', handleMouseEnter) wrapper.addEventListener('mouseleave', handleMouseLeave) Array.prototype.forEach.apply(allLi, [(value, index) => value.setAttribute("index", index)]) const liEnter = function (e) { let target = e.target if (target.tagName === 'LI') { index = target.getAttribute("index") domChange() } } indicator.addEventListener('mouseover', liEnter) arrowPrev.onclick = function () { index-- index = index === -1 ? allLi.length - 1 : index domChange() } arrowNext.onclick = function () { index++ index = index === allLi.length ? 0 : index domChange() } function domChange () { activeLi = document.querySelector(".active") activeLi.className = '' allLi[index].className = 'active' dom.style.left = -index * stepWidth + 'px' } const handleNavEnter = function (e) { const target = e.target if (target.tagName !== 'LI') return const dom = document.querySelector('.hoverShow') if (dom) dom.className = '' const index = target.getAttribute('index') contentWrapper.children[index - 1].className = 'hoverShow' } const handleNavLeave = function (e) { const dom = document.querySelector(".hoverShow") if (dom) { dom.className = '' } } navLiWrapper.addEventListener("mouseover", handleNavEnter) navLiWrapper.addEventListener("mouseleave", handleNavLeave) </script> </body> </html>
然后是CSS
* { padding: 0; margin: 0; } li { list-style-type: none; } .banner-container { width: 960px; height: 470px; margin: 50px auto; position: relative; } .nav-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; text-align: center; } .li-container { width: 300px; height: 100%; float: left; background-color: green; } .li-container li { padding: 10px 0; margin: 10px 0; } .li-container li:hover { cursor: pointer; } .content-container { position: relative; top: 0; left: 0; margin-left: 300px; height: 100%; overflow: hidden; } .content-container div { position: relative; top: 0; left: 0; width: 100%; height: 100%; display: none; background-color: #fff; z-index: 4; } .content-container .hoverShow { display: block; } .wrapper-container { position: relative; width: 590px; height: 470px; overflow: hidden; margin-left: 300px; } .wrapper { position: absolute; left: 0; top: 0; width: 300%; transition: all .5s; } .wrapper img { width: 590px; display: block; float: left; } .indicator { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 3; } .indicator li { display: inline-block; width: 10px; height: 10px; background-color: rgba(0, 0, 0, .7); } .indicator li.active { background-color: rgba(255, 255, 255, .8); } .indicator li:hover { cursor: pointer; } .arrow-container { position: absolute; top: 0; left: 0; display: flex; width: 100%; height: 100%; justify-content: space-between; align-items: center; } .arrow-prev, .arrow-next { font-size: 50px; color: #fff; cursor: pointer; }
致辞,再次感谢~