华为商城轮播图
1. 界面搭建
-
通过js操作DOM来创建元素
-
此处用两个按钮来实现左右图片的切换
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./css/reset.css"> <style> .banner { position: relative; } .banner .images { position: relative; height: 550px; } .banner .images .item { position: absolute; left: 0; top: 0; width: 100%; overflow: hidden; opacity: 0; } .banner .images .item.active { opacity: 1; } .banner .images .item img { /* 让图片始终保持居中 */ position: relative; transform: translate(-50%); left: 50%; width: 1920px; height: 550px; } .indicator { position: absolute; left: 0; right: 0; bottom: 42px; display: flex; justify-content: center; } .indicator .item { width: 8px; height: 8px; margin: 0 10px; border-radius: 50%; background-color: #aaa; cursor: pointer; } .indicator .item.active { background-color: #f00; } </style> </head> <body> <div class="banner"> <ul class="images"> </ul> <div class="control"> <button>上一个</button> <button>下一个</button> </div> <div class="indicator"></div> </div> <script> // 数据 var banners = [ {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'}, {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'} ] // 动态添加轮播图图片数据 var imagesEl = document.querySelector(".images") var activeItemEl = null for (var i = 0; i < banners.length; i++) { // 获取数据 var banner = banners[i] // 创建li元素 var itemEl = document.createElement("li") itemEl.classList.add("item") // 默认第一个为active if (i === 0) { itemEl.classList.add("active") activeItemEl = itemEl } imagesEl.append(itemEl) // 创建img元素 var imgEl = document.createElement("img") imgEl.src = banner.imgUrl itemEl.append(imgEl) } // 动态添加指示器内容 var indicatorEl = document.querySelector(".indicator") for (var i = 0; i < banners.length; i++) { var itemEl = document.createElement("div") itemEl.classList.add("item") if (i === 0) { itemEl.classList.add("active") } indicatorEl.append(itemEl) } </script> </body> </html>
2. 官网的效果
官网的效果其实是通过控制
opacity
,添加动画来实现
2.1 实现通过点击按钮切换图片
- 获取按钮对应的dom元素,记录当前元素的索引
- 切换时,当前元素的opacity变为0,上一张或下一张图片的opacity变为1,可定义一个prevIndex来记录上一张图片的索引
- 点击下一个索引加1,等于图片数量时,重置为0,点击上一个索引减1,等于-1时,重置为最后一张图片的索引,即
banners.length-1
// 记录变量 var prevIndex = 0 var curIndex = 0 var controlEl = document.querySelector(".control") var prevBtnEl = controlEl.querySelector(".prev") var nextBtnEl = controlEl.querySelector(".next") // 添加按钮的点击 prevBtnEl.onclick = function() { prevIndex = curIndex curIndex -- if(curIndex === -1) { curIndex = banners.length - 1 } switchBannerItem() } nextBtnEl.onclick = function() { prevIndex = curIndex curIndex ++ if(curIndex === banners.length) { curIndex = 0 } switchBannerItem() } // 封装图片切换函数 function switchBannerItem() { // 1.切换图片 // 记录当前作用元素的dom var curItemEl = imagesEl.children[curIndex] var prevItemEl = imagesEl.children[prevIndex] // 移除上一张图片的active prevItemEl.classList.remove('active') curItemEl.classList.add('active') // 2.切换指示器 var curInItemEl = indicatorEl.children[curIndex] var prevInItemEl = indicatorEl.children[prevIndex] prevInItemEl.classList.remove('active') curInItemEl.classList.add('active') }
2.2 实现自动轮播
-
一般情况下,轮播图是从左往右轮播,所以自动轮播只需要将下一个按钮的点击方法执行即可
-
每等待3s钟执行一次下一个图片的切换,为了代码阅读性,将其抽离成一个函数
nextSwitch
-
注意:鼠标移入banner时,是需要取消轮播的,即清除定时器,此处不理解可查阅我的上一篇文章JavaScript实战-王者荣耀轮播图
function nextSwitch() { prevIndex = curIndex curIndex ++ if(curIndex === banners.length) { curIndex = 0 } switchBannerItem() } // 自动轮播效果 var timer = null startTimer() // 取消自动轮播 var bannerEl = document.querySelector('.banner') bannerEl.onmouseenter = function() { clearInterval(timer) } // 开启自动轮播 bannerEl.onmouseleave = function() { startTimer() } function startTimer() { timer = setInterval(function() { nextSwitch() }, 3000) }
2.3 点击指示器切换轮播
-
在创建指示器时监听指示器的点击事件,改变其class
- 修改curIndex和prevIndex,如何拿到当前点击元素的index呢?
- 使用立即执行函数,此处不过多介绍,后续会补充相关文章
- 将var改为let,因为let有块级作用域,后续会补充相关文章
- 在外面遍历的时候给每一个itemEl上添加一个index属性,然后在点击的时候通过
this
来取(本文采用此方法)
- 调用切换图片的函数
// 记录指示器的索引 itemEl.index = i // 添加点击方法 itemEl.onclick = function() { prevIndex = curIndex curIndex = this.index switchBannerItem() }
- 修改curIndex和prevIndex,如何拿到当前点击元素的index呢?
2.4 完整js代码
// 数据
var banners = [
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'}
]
// 动态添加轮播图图片数据
var imagesEl = document.querySelector(".images")
var activeItemEl = null
for (var i = 0; i < banners.length; i++) {
// 获取数据
var banner = banners[i]
// 创建li元素
var itemEl = document.createElement("li")
itemEl.classList.add("item")
if (i === 0) {
itemEl.classList.add("active")
activeItemEl = itemEl
}
imagesEl.append(itemEl)
// 创建img元素
var imgEl = document.createElement("img")
imgEl.src = banner.imgUrl
itemEl.append(imgEl)
}
// 动态添加指示器内容
var indicatorEl = document.querySelector(".indicator")
for (var i = 0; i < banners.length; i++) {
var itemEl = document.createElement("div")
itemEl.classList.add("item")
if (i === 0) {
itemEl.classList.add("active")
}
indicatorEl.append(itemEl)
// 记录指示器的索引
itemEl.index = i
// 添加点击方法
itemEl.onclick = function() {
prevIndex = curIndex
curIndex = this.index
switchBannerItem()
}
}
// 添加按钮的点击
var prevIndex = 0
var curIndex = 0
var controlEl = document.querySelector(".control")
var prevBtnEl = controlEl.querySelector(".prev")
var nextBtnEl = controlEl.querySelector(".next")
prevBtnEl.onclick = function() {
prevIndex = curIndex
curIndex --
if(curIndex === -1) {
curIndex = banners.length - 1
}
switchBannerItem()
}
nextBtnEl.onclick = function() {
nextSwitch()
}
// 封装图片切换函数
function switchBannerItem() {
// 1.切换图片
// 记录当前作用元素的dom
var curItemEl = imagesEl.children[curIndex]
var prevItemEl = imagesEl.children[prevIndex]
// 移除上一张图片的active
prevItemEl.classList.remove('active')
curItemEl.classList.add('active')
// 2.切换指示器
var curInItemEl = indicatorEl.children[curIndex]
var prevInItemEl = indicatorEl.children[prevIndex]
prevInItemEl.classList.remove('active')
curInItemEl.classList.add('active')
}
function nextSwitch() {
prevIndex = curIndex
curIndex ++
if(curIndex === banners.length) {
curIndex = 0
}
switchBannerItem()
}
// 自动轮播效果
var timer = null
startTimer()
// 移除定时器和开启定时器
var bannerEl = document.querySelector('.banner')
bannerEl.onmouseenter = function() {
clearInterval(timer)
}
bannerEl.onmouseleave = function() {
startTimer()
}
function startTimer() {
timer = setInterval(function() {
nextSwitch()
}, 3000)
}
3. 无限轮播
3.1 改变布局
- 此处仍然使用定位来做,通过修改图片的
left
值实现轮播- 移除图片的opacity样式
- 设置图片的left值
// 在创建元素的代码块中修改left值 // 设置itemEl的位置 itemEl.style.left = `${i * 100}%`
3.2 位移轮播
-
点击按钮,整体移动图片,即通过调整父元素
ul
的transform
-
给父元素
ul
添加动画效果transition: all 500ms ease;
// 封装图片切换函数 function switchBannerItem() { // 1.切换图片 imagesEl.style.transform = `translateX(-${curIndex *100}%)` // 2.切换指示器 var curInItemEl = indicatorEl.children[curIndex] var prevInItemEl = indicatorEl.children[prevIndex] prevInItemEl.classList.remove('active') curInItemEl.classList.add('active') }
3.3 无限轮播
-
克隆first和last的Element
- cloneNode
- 追加到对应的位置:最后一个图片后加第一个图片,第一个图片后加最后一个图片
- 修改left值
- last.left = -100%
- first.left = (100*bannerCount)%
// 追加:(无限轮播), 最后和最前分别添加一个元素 var firstItem = imagesEl.children[0].cloneNode(true) // true 指深度克隆 var lastItem = imagesEl.children[bannersCount - 1].cloneNode(true) imagesEl.append(firstItem) imagesEl.prepend(lastItem) // 调整位置 firstItem.style.left = `${bannersCount * 100}%` lastItem.style.left = '-100%'
-
索引的判断放到滚动之后
- 因为当图片滚到banners最后一个时,应该再向左移动一次,展示克隆的firstItem
- 滚到第一个再向上一个时同理
-
快速修正位置 - 不能有动画
// 封装修复位置的函数 function fixBannerPosition() { setTimeout(() => { imagesEl.style.transition = 'none' imagesEl.style.transform = `translateX(${-curIndex * 100}%)` }, animationDuration) } // 封装图片切换函数 function switchBannerItem() { // 1.切换图片 imagesEl.style.transition = `all ${animationDuration}ms ease` imagesEl.style.transform = `translateX(${-curIndex * 100}%)` if(curIndex === bannersCount) { curIndex = 0 // 调整位置 fixBannerPosition() } else if(curIndex === -1) { curIndex = bannersCount - 1 fixBannerPosition() } // 2.切换指示器 var curInItemEl = indicatorEl.children[curIndex] var prevInItemEl = indicatorEl.children[prevIndex] prevInItemEl.classList.remove('active') curInItemEl.classList.add('active') }
3.4 增加对定时器的处理
- 离开界面/进入界面对定时器处理
- 离开界面时清除定时器
- 进入界面开始定时器
// 通过window的焦点 document.onvisibilitychange = function() { if (document.visibilityState === "visible") { startTimer() } else if (document.visibilityState === "hidden") { stopTimer() } }
- timer更加严谨
- 保证轮播图只开启一个定时器
function startTimer() { if (timer) return timer = setInterval(function() { nextSwitch() }, 3000) } function stopTimer() { if (!timer) return clearInterval(timer) timer = null // 清除定时器之后, 必须timer赋值为null,否则下次的startTimer()直接return掉了 }
3.5 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/reset.css">
<style>
.banner {
position: relative;
overflow: hidden;
}
.banner .images {
position: relative;
height: 550px;
transition: all 500ms ease;
}
.banner .images .item {
position: absolute;
left: 0;
top: 0;
width: 100%;
overflow: hidden;
}
.banner .images .item img {
/* 让图片始终保持居中 */
position: relative;
transform: translate(-50%);
left: 50%;
width: 1920px;
height: 550px;
}
.indicator {
position: absolute;
left: 0;
right: 0;
bottom: 42px;
display: flex;
justify-content: center;
}
.indicator .item {
width: 8px;
height: 8px;
margin: 0 10px;
border-radius: 50%;
background-color: #aaa;
cursor: pointer;
}
.indicator .item.active {
background-color: #f00;
}
</style>
</head>
<body>
<div class="banner">
<ul class="images">
</ul>
<div class="control">
<button class="prev">上一个</button>
<button class="next">下一个</button>
</div>
<div class="indicator"></div>
</div>
<script>
// 数据
var banners = [
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'},
{imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'}
]
var bannersCount = banners.length
var animationDuration = 500
// 动态添加轮播图图片数据
var imagesEl = document.querySelector(".images")
var activeItemEl = null
for (var i = 0; i < bannersCount; i++) {
// 获取数据
var banner = banners[i]
// 创建li元素
var itemEl = document.createElement("li")
itemEl.classList.add("item")
if (i === 0) {
itemEl.classList.add("active")
activeItemEl = itemEl
}
imagesEl.append(itemEl)
// 设置itemEl的位置
itemEl.style.left = `${i * 100}%`
// 创建img元素
var imgEl = document.createElement("img")
imgEl.src = banner.imgUrl
itemEl.append(imgEl)
}
// 动态添加指示器内容
var indicatorEl = document.querySelector(".indicator")
for (var i = 0; i < bannersCount; i++) {
var itemEl = document.createElement("div")
itemEl.classList.add("item")
if (i === 0) {
itemEl.classList.add("active")
}
indicatorEl.append(itemEl)
// 记录指示器的索引
itemEl.index = i
// 添加点击方法
itemEl.onclick = function() {
prevIndex = curIndex
curIndex = this.index
switchBannerItem()
}
}
// 追加:(无限轮播), 最后和最前分别添加一个元素
var firstItem = imagesEl.children[0].cloneNode(true) // true 指深度克隆
var lastItem = imagesEl.children[bannersCount - 1].cloneNode(true)
imagesEl.append(firstItem)
imagesEl.prepend(lastItem)
// 调整位置
firstItem.style.left = `${bannersCount * 100}%`
lastItem.style.left = '-100%'
// 添加按钮的点击
var prevIndex = 0
var curIndex = 0
var controlEl = document.querySelector(".control")
var prevBtnEl = controlEl.querySelector(".prev")
var nextBtnEl = controlEl.querySelector(".next")
prevBtnEl.onclick = function() {
prevIndex = curIndex
curIndex --
switchBannerItem()
}
nextBtnEl.onclick = function() {
nextSwitch()
}
// 封装图片切换函数
function switchBannerItem() {
// 1.切换图片
imagesEl.style.transition = `all ${animationDuration}ms ease`
imagesEl.style.transform = `translateX(${-curIndex * 100}%)`
if(curIndex === bannersCount) {
curIndex = 0
fixBannerPosition()
} else if(curIndex === -1) {
curIndex = bannersCount - 1
fixBannerPosition()
}
// 2.切换指示器
var curInItemEl = indicatorEl.children[curIndex]
var prevInItemEl = indicatorEl.children[prevIndex]
prevInItemEl.classList.remove('active')
curInItemEl.classList.add('active')
}
function nextSwitch() {
prevIndex = curIndex
curIndex ++
switchBannerItem()
}
// 自动轮播效果
var timer = null
startTimer()
// 移除定时器和开启定时器
var bannerEl = document.querySelector('.banner')
bannerEl.onmouseenter = function() {
stopTimer()
}
bannerEl.onmouseleave = function() {
startTimer()
}
function startTimer() {
if (timer) return;
timer = setInterval(function() {
nextSwitch()
}, 3000)
}
function stopTimer() {
if (!timer) return;
clearInterval(timer)
timer = null // 清除定时器之后, 必须timer赋值为null
}
// 封装修复位置的函数
function fixBannerPosition() {
setTimeout(() => {
imagesEl.style.transition = 'none'
imagesEl.style.transform = `translateX(${-curIndex * 100}%)`
}, animationDuration)
}
// 通过window的焦点
document.onvisibilitychange = function() {
if (document.visibilityState === "visible") {
startTimer()
} else if (document.visibilityState === "hidden") {
stopTimer()
}
}
</script>
</body>
</html>
完美收官!!!