一、背景
学习前端的路途上有很多东西需要我们去掌握,掌握轮播图的技术实现原理也是一个成为更好的前端工程师的必经之路。我曾经也看别人的网站里有过轮播图,一直觉得挺复杂的,也有点畏惧,经过尝试之后,发现轮播图其实不难。接下来我会为大家分享一下我制作的简易轮播图。效果图如下:
二、原理
1.HTML
首先轮播图需要一个视图窗口,就是能刚好展示一张图片大小的容器,我把他称之为"carousel-warpper",其次窗口里面需要第二个容器"carousel",用于包裹所有的图片,因此这个结构很适合ul、li。为了达到视觉欺骗,以及图片自然循环,我们需要图片组的前后为同一张图片,之后我会在JS中具体解释。
<div class="carousel-wrapper">
<div class="left"></div> <!-- 左边的按钮 -->
<ul class="carousel">
<li class="carousel-item">
<img src="http://d.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c334c3e91cb1e4c510fd9f9a16a.jpg" alt="1">
</li>
<li class="carousel-item">
<img src="http://attach.bbs.miui.com/forum/201306/23/110328s72xxse7lfis9fnd.jpg" alt="2">
</li>
<li class="carousel-item">
<img src="http://attachments.gfan.net.cn/forum/201412/21/231048c3pvghcz1dhcigfc.jpg" alt="3">
</li>
<li class="carousel-item">
<img src="http://attachments.gfan.com/forum/201503/19/211608ztcq7higicydxhsy.jpg" alt="4">
</li>
<li class="carousel-item">
<img src="http://img2.imgtn.bdimg.com/it/u=2626209138,2760863424&fm=26&gp=0.jpg" alt="5">
</li>
<li class="carousel-item">
<img src="http://d.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c334c3e91cb1e4c510fd9f9a16a.jpg" alt="1">
</li>
</ul>
<div class="right"></div> <!-- 右边的按钮 -->
</div>
2.CSS
样式这部分,我让整个"carousel-warpper"位于了浏览器中间,让其为相对定位,作为容器,里面的ul为绝对定位。其他样式可以根据需求改动,就不一一赘述,代码如下:
* {
margin: 0;
padding: 0;
list-style: none;
}
html, body {
height: 100%;
}
.carousel-wrapper {
border: 1px solid black;
width: 360px;
height: 200px;
position: relative;
left: 50%;
top: 50%;
z-index: 9;
transform: translate(-50%,-50%);
overflow: hidden;
}
.left, .right {
width: 0;
height: 0;
position: absolute;
top: 50%;
z-index: 10;
border: 16px solid transparent;
border-radius: 50% 50%;
box-shadow: 1px 1px 2px 3px #ccc;
cursor: pointer;
}
.left {
left: 3%;
border-right-color: #aaa;
}
.right {
right: 3%;
border-left-color: #aaa;
}
.carousel {
position: absolute;
left: 0;
top: 0;
display: flex;
flex-wrap: nowrap;
}
.carousel,.carousel-item {
height: 100%;
}
.carousel-item {
width: 360px;
}
.carousel-item img {
width: 100%;
height: 100%;
}
3.JS
JS里最为关键的便是怎样去实现这个移动动画,我把他定义为animation,接收三个参数,一个是direction(方向)、另一个是distance(距离)、最后一个是autoPlay(是否自动播放)。里面的变量会用到其他部分的,稍后我会完整贴出来。
function animation(direction, distance, autoPlay = false) {
clearInterval(timer); // 每次开启新的动画记得清除定时器,不然会产生很多定时器实例,造成卡顿和性能影响
var totalStep = 0; // 动画总共移动的步长
var flag = false; // 开关变量,用于控制图片移动过程中溢出部分的再次移动距离的次数。就是在图片移动过程中图片的位置可能超出窗口一部分,我们需要把这部分归位。
timer = setInterval(() => {
_step = direction ? parseInt(style.left) + step : parseInt(style.left) - step; // 图片需要显示的位置
style.left = _step + 'px';
totalStep += step;
if(totalStep >= distance) { // 到达目标位置
fix();
}
if(direction && parseInt(style.left) >= 0) style.left = -picWidth * (picNum - 1) + 'px'; // 如果图片的左侧离开了窗口的左侧,也就是会出现左侧空白,那么我们让他展示倒数第二张,这样他就以为是从第一张来到了第二张
if(!direction && Math.abs(parseInt(style.left)) >= picWidth * (picNum - 1)) style.left = 0 // 如果图片的右侧离开了窗口的右侧,也就是会出现右侧空白,那么我们让他展示正数第二张,这样他就以为是从最后一张来到了第二张
}, 16);
function fix() { // 用于将超出部分归位
if(parseInt(style.left) % picWidth !== 0) { // 如果图片超出了一部分
// style.left = (parseInt(parseInt(style.left) / picWidth) - 1) * picWidth + 'px';
if(!flag) {
totalStep += direction ? picWidth-(parseInt(style.left) % picWidth) : parseInt(style.left) % picWidth;
flag = true;
}
} else { // 否则完美到达目标位置
clearInterval(timer);
if(autoPlay) {
clearTimeout(timer2);
timer2 = setTimeout(() => { // 开启自动播放
animation(0, picWidth, true);
}, interval);
}
}
}
}
完整代码
(function() {
const carouselWrapper = document.querySelector('.carousel-wrapper'); // 窗口
const carousel = document.querySelector('.carousel'); // 图片容器
const left = document.querySelector('.left'); // 左边按钮
const right = document.querySelector('.right'); // 右边按钮
const picWidth = carousel.firstElementChild.clientWidth; // 图片宽度
const picNum = carousel.children.length; // 图片数量
const style = carousel.style; // 图片容器的样式
const step = 6; // 移动步长
const interval = 3000; // 播放间隔
var timer; // 移动定时器
var timer2; // 控制两个移动之间间隔时间的定时器
style.left = style.left === '' ? 0 : style.left; // 初始化容器的位置
function animation(direction, distance, autoPlay = false) {
clearInterval(timer);
var totalStep = 0;
var flag = false;
timer = setInterval(() => {
_step = direction ? parseInt(style.left) + step : parseInt(style.left) - step;
style.left = _step + 'px';
totalStep += step;
if(totalStep >= distance) {
fix();
}
if(direction && parseInt(style.left) >= 0) style.left = -picWidth * (picNum - 1) + 'px';
if(!direction && Math.abs(parseInt(style.left)) >= picWidth * (picNum - 1)) style.left = 0
}, 16);
function fix() {
if(parseInt(style.left) % picWidth !== 0) {
// style.left = (parseInt(parseInt(style.left) / picWidth) - 1) * picWidth + 'px';
if(!flag) {
totalStep += direction ? picWidth-(parseInt(style.left) % picWidth) : parseInt(style.left) % picWidth;
flag = true;
}
} else {
clearInterval(timer);
if(autoPlay) {
clearTimeout(timer2);
timer2 = setTimeout(() => {
animation(0, picWidth, true);
}, interval);
}
}
}
}
// 自动播放
timer2 = setTimeout(() => {
animation(0, picWidth, true);
}, interval);
// 鼠标移入,自动暂停
carousel.onmouseenter = function() {
clearInterval(timer);
}
// 鼠标移出,继续播放
carousel.onmouseleave = function() {
clearTimeout(timer2);
timer2 = setTimeout(() => {
animation(0, picWidth, true);
}, interval);
// 移入左边按钮,停止播放
left.onmouseenter = () => {
clearInterval(timer);
clearInterval(timer2);
}
// 移入右边按钮停止播放
right.onmouseenter = () => {
clearInterval(timer);
clearInterval(timer2);
}
}
// 事件委托,判断点击事件
carouselWrapper.addEventListener('click', function(e) {
var e = e || window.event,
target = e.target || e.srcElement;
if(target.className === 'left') {
animation(1,picWidth);
} else {
animation(0,picWidth);
}
})
}())
三、总结
遇到困难不要怕,微笑着面对它,消除恐惧的最好办法就是面对恐惧!加油,奥利给!
我的实现比较简单,如果大家有需要可以自行丰满一下,比如给轮播图加个展示当前所播放图片的次序的小点点。优化动画细节之类的。任何对于提高代码性能的建议都是好的建议。加油,冲鸭!