一、前言
上一篇有讲到原生 JS 实现无缝轮播图基础版;这一篇主要实现进阶版,包括左右按钮点击功能以及指示灯功能。想要了解基础版请移步https://blog.youkuaiyun.com/weixin_44135121/article/details/87874216。想要在 Vue 中使用轮播图请移步至 https://blog.youkuaiyun.com/weixin_44135121/article/details/102947047。
二、实现轮播图思路
布局:
1. 使用 flex 布局使子元素水平排列;
2. 子元素宽度设置100%,flex-shrink 设置为 0(这个属性保证子元素不被压缩);
定时器无缝轮播:
轮播之前需要将首尾各添加一张图片。其目的就是实现无缝轮播。
然后将父元素 left 向左移动一个屏幕宽度的距离。这样才能让第二张(因为首尾各添加了一张图片,所以原先的第一张就变成了第二张)变成可视范围的第一张。
轮播到最后一张图片(这里的最后一张指的是拼接的第一张)时;
①清除掉定时器;
②开启临界处理(重置父元素的left);
③开启下一轮轮播。
这里是实现无缝轮播的关键;其目的是播放最后一张的同时,与第一张偷梁换柱。因为最后一张与第一张图片相同,所以快速地改变其 left 用户并无感知。并且同时开启下一轮定时器,1.5s 之后执行第二张图片播放。而这个 1.5s 之内就好了准备(清除当前定时器,重置父元素 left)。
右边按钮点击无缝轮播:
逻辑同定时器无缝轮播类似,因为方向都是从右向左播放。无缝轮播的核心都是播放到最后一张时偷梁换柱(播放最后一张图片的1.5s 同时,改变父元素 left 成初始值,负数的屏幕宽度)。
左边按钮点击无缝轮播:
左边按钮无缝轮播的核心与以上差不多,唯一的区别就是当播放到第一张图片时,开始偷梁换柱(播放第一张图片的1.5s 同时,改变父元素 left 成最大值,负数的(屏幕宽度*(子元素个数-2)))。
指示灯点击无缝轮播:
点击第一张图片时,同左边按钮点击无缝轮播。点击最后一张图片时,同右边按钮点击无缝轮播。
三、图示
四、代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Carousel</title>
<meta name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1">
</head>
<style>
body{
margin: 0;
padding: 0;
}
.slide {
display: flex;
position: relative;
left: 0;
}
.slide img{
width: 100%;
height: 250px;
}
.slide>div{
width: 100%;
flex-shrink: 0;
height: 250px;
}
.carousel {
overflow: hidden;
width: 100%;
height: 250px;
}
.btn {
width: 50px;
height: 50px;
cursor: pointer;
}
.indicator {
display: flex;
justify-content: center;
padding: 0;
position: absolute;
bottom: 10px;
width: 100%;
}
.indicator li {
list-style: none;
border: 8px solid #999;
margin: 0 10px;
border-radius: 50%;
}
li.li-active {
border: 8px solid red;
}
li:hover {
cursor: pointer;
}
button{
border: none;
background: none;
outline: none;
}
.left{
position: absolute;
left: 10px;
top: 50%;
transform: translate(0,-50%);
}
.right{
position: absolute;
right: 10px;
top: 50%;
transform: translate(0,-50%);
}
.contain{
position: relative;
}
</style>
<body>
<div class="contain">
<div class="carousel">
<div class="slide">
<img src="img/1.jpg" alt="">
<img src="img/2.jpg" alt="">
<img src="img/3.jpg" alt="">
</div>
</div>
<div class="">
<button class="left"><img src="img/left.png" alt="" class="btn"></button>
<button class="right"><img src="img/right.png" alt="" class="btn"></button>
</div>
<ul class="indicator">
<li class="li-active"></li>
<li></li>
<li></li>
</ul>
</div>
</body>
<script>
var contain = document.querySelector('.contain');//获取需要改变left的元素
var slide = document.querySelector('.slide');//获取需要改变left的元素
var leftBtn = document.querySelector('.left');//获取左边按钮
var rightBtn = document.querySelector('.right');//获取右边按钮
var indicator = document.querySelector('.indicator');//指示灯父元素
var indicatorChild = indicator.children;//指示灯的个数
var slideLength = indicatorChild.length+2;//轮播图个数
var timer = null;//定时器初始为null
var currentIndex = 1;//当前索引
var width = window.innerWidth;//屏幕宽度
window.onload = function () {
var everyTime = 2000;//轮播的间隔时间
var firstDom = slide.firstElementChild.cloneNode(true);
var lastDom = slide.lastElementChild.cloneNode(true);
slide.appendChild(firstDom);
slide.insertBefore(lastDom,slide.firstElementChild);
slide.style.left = -width+'px';
autoPlay(everyTime);//页面打开后自动开启播放
//当有鼠标划过时停止定时器
contain.onmouseenter = function () {
clearInterval(timer);
};
//鼠标摞开时启动定时器
contain.onmouseleave = function () {
autoPlay(everyTime);
};
//点击左边按钮
leftBtn.onclick = function () {
currentIndex--;
if (currentIndex == 0) {
criticality(indicatorChild.length,'left');
} else {
play(-(width * currentIndex),currentIndex);
}
};
//点击右边按钮
rightBtn.onclick = function () {
currentIndex++;
if (currentIndex == slideLength - 1) {
criticality(1,'right');
} else {
play(-(width * currentIndex),currentIndex);
}
};
//指示灯点击时
for (let i = 0; i < indicatorChild.length; i++) {
indicatorChild[i].onmouseenter = function () {
//先将同级class移除;
for (var j = 0; j < indicatorChild.length; j++) {
if (indicatorChild[j].className == 'li-active') {
indicatorChild[j].className = '';
}
}
//点击时到达临界值
if (i == 0 && currentIndex == indicatorChild.length) {
currentIndex++;
criticality(1);
}else if(i == (indicatorChild.length -1) && currentIndex == 1) {
currentIndex--;
criticality(indicatorChild.length);
}else {
//设置所点击的元素class,并移动到指示灯所在图片
play(-(width * (i+1)),(i+1));
currentIndex = i+1;
}
}
}
};
//自动播放
function autoPlay(period) {
timer = setInterval(function () {
currentIndex++;
//当移动到最后一张,也就是模拟的第一张
if (currentIndex == slideLength - 1) {
//首先清除原先的定时器
clearInterval(timer);
//处理临界情况
criticality(1);
//通次继续下一次轮播
return autoPlay(period);
} else {
if(currentIndex < slideLength-1){
play(-(width * currentIndex),currentIndex);
}
}
}, period)
}
//轮播公共方法
function play(left,activeIndex) {
//1、图片移动left px;
slide.style.left = left + 'px';
slide.style.transition = 'left 1.5s';
//2、改变指示灯颜色
for(var i = 0;i<indicatorChild.length;i++){
if(i == activeIndex-1){
indicatorChild[i].className = 'li-active';
}else {
indicatorChild[i].className = '';
}
}
}
function criticality(boundary,flag) {
//继续走临界的轮播
play(-(width * currentIndex),boundary);
//将鼠标禁止轮播的时间
var now=(new Date()).getTime()+1500;
//在轮播的同时将距离重置
setTimeout(function () {
slide.style.left = -width*boundary + 'px';
slide.style.transition = 'left 0s';
currentIndex = boundary;
}, 1500);
//点击边距的时候要将下一步操作的按钮取消1500ms的时间
if((new Date()).getTime()<=now){
if(flag == 'left'){
leftBtn.disabled=true;
setTimeout(function () {
leftBtn.disabled=false;
},now-(new Date()).getTime())
}else {
rightBtn.disabled=true;
setTimeout(function () {
rightBtn.disabled=false;
},now-(new Date()).getTime())
}
}
}
</script>
</html>