前言: 轮播图应该是每个前端人员都会的东西。写这一篇的原因是去面试,然后面试官让我写轮播图,写是写出来了, 但是思路很混乱,代码很丑....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;
}
致辞,再次感谢~