轮播图
轮播图是Web开发中常见的组件之一,通常第三方框架都自带这个组件,或是通过使用第三方插件实现,无需程序员自己重新编码,但在面试中会被问道其实现原理。轮播图的实现方式有很多种,本文轮播图实现代码来自[黑马程序员的视频教程],纯js实现,支持无缝滚动。
动画
在实现轮播图前,我们需要封装一个动画函数。
function animate(obj, target) {
obj.timer = setInterval(function () {
var step = (target - obj.offsetLeft) / 10;
if (obj.offsetLeft >= target) clearInterval(obj.timer);
obj.style.left = obj.offsetLeft + step + "px";
}, 15);
}
// 调用
var div = document.querySelector("div");
div.addEventListener("click", function () {
animate(div, 300);
});
这里会存在一个Bug,就是多次点击div盒子触发animate()函数,导致有多个定时器的步长累加,速度随点击次数增加。
解决办法:
1.创建定时器前先清除
// 清除定时器
clearInterval(obj.timer);
// 创建定时器
obj.timer = setInterval(function () {
...
}
2.通过if判断定时器是否被创建,没有创建则执行创建定时器代码
if (typeof obj.timer == "undefined") {
obj.timer = setInterval(function () {
...
}
}
步长(step)计算
动画需要按照先快后慢的方式运行。这里需要用到步长公式:
步长 = (目标值-当前位置)/10
步长公式理解:分母不变,分子越小,结果越小;从而结果由大到小变化,前面的结果越大,得到步长越大,速度越快;后面的结果越小,速度越慢。
var step = (target - obj.offsetLeft) / 10;
因为步长公式运算会存在小数,所以 if (obj.offsetLeft >= target) clearInterval(obj.timer); 判断其实并没有起作用,因为step = 0.4,target = 300,obj.offsetLeft= 296,
陷入了死循环。解决办法:
步长为正数,先上取整。为负数向下取整。
假设step=0.4,如果往下取整那么step=0,那么obj.offsetLeft >= target等式不成立(因为obj.offsetLeft比target小0.4),计时器就陷入了死循环。
同理,当为负数时,只有向下取整。(注意等式条件为:obj.offsetLeft == target)
step = step > 0 ? Math.ceil(step) : Math.floor(step);
动画的完整代码:
// 动画函数 (对象,目标位置,回调函数)
function animate(obj, target, callback) {
// 清除定时器
clearInterval(obj.timer);
obj.timer = setInterval(function () {
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
// 回调函数
callback && callback();
}
obj.style.left = obj.offsetLeft + step + "px";
}, 15);
}
轮播图实现原理
多个图片并排排列,为了解决无缝切换的问题,最后一张为第一张图片。通过动画使focus1~4这四张图片不断显示,从而达到轮播的效果。
静态图示:
动态图示:
步骤
移动步长的计算公式
移动步长 = 索引 * mian盒子宽度;因为向左移动,所以取负。
-index * mainWidth
- 第一张移动 0 ,表示不移动,因为当前显示的图片就是第一张。
- 第二张移动 1*mainWidth = 1个mainWidth单位,表示移动1张图片的距离,显示的内容就是第2张。
- 第三张移动 2*mainWidth = 2个mainWidth单位,表示移动2张图片的距离,显示的内容就是第3张。依次类推
因为index是我们设置的属性(li.setAttribute('index', i);)所以最大值就是图片数量-1。
1.鼠标经过,左右按钮显示,动画停止;鼠标离开,左右按钮显示,动画自动播放。
var left = document.querySelector('.left'); // 左翻页按钮
var right = document.querySelector('.right'); // 右翻页按钮
var main = document.querySelector('.main'); // 用户可视区域
var mainWidth = main.offsetWidth; // mian盒子宽度
// 鼠标经过
main.addEventListener('mouseenter', function () {
left.style.display = 'block';
right.style.display = 'block';
// 清除定时器,自动翻页动画停止
clearInterval(timer);
timer = null;
});
// 鼠标离开
main.addEventListener('mouseleave', function () {
left.style.display = 'none';
right.style.display = 'none';
// 自动翻页动画开始
timer = setInterval(function () {
right.click();
}, 2000);
});
// 定时器,自动翻页
var timer = setInterval(function () {
right.click();
}, 2000);
2.点击小圆圈实现滚动图片。
var ul = main.querySelector('ul'); // 图片列表
var ol = main.querySelector('.circle'); // 圆圈列表
// 动态创建圆圈,数量根据 图片个数 决定
for (var i = 0; i < ul.children.length; i++) {
var li = document.createElement('li');
li.setAttribute('index', i);
ol.appendChild(li);
// 绑定点击事件
li.addEventListener('click', function () {
// 所有圆圈去除类名 current
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 当前圆圈添加类名 current
this.className = 'current';
// 获得索引。用户点击了第几个图片
var index = this.getAttribute('index');
num = index;
circle = index;
// 向左滚动
animate(ul, -index * mainWidth);
})
}
// 克隆第一个图片放到ul的最后
ol.children[0].className = 'current';
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
3.点击左、右翻页按钮实现翻页,小圆圈跟随变动。
var num = 0; // 当前滚动图片索引
var circle = 0; // 圆圈索引
var flag = true; // 是否滚动完成的标志
// 右翻页
right.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果滚动到最后一张,即克隆的第一张
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * mainWidth, function () {
flag = true;
});
circle++;
// 圆圈到了最后一个
if (circle == ol.children.length) {
circle = 0;
}
circleChange();
}
});
// 左翻页
left.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果是第一张
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * mainWidth + 'px';
}
num--;
animate(ul, -num * mainWidth, function () {
flag = true;
});
circle--;
if (circle < 0) {
circle = ol.children.length - 1;
}
circleChange();
}
});
// 圆圈状态切换
function circleChange() {
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
ol.children[circle].className = 'current';
}
代码下载:https://wybing.lanzouw.com/iAjpe0jok2ab
完整代码
index.html
<!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>轮播图</title>
<link rel="stylesheet" href="./css/index.css">
<script src="./js/animate.js"></script>
<script src="./js/index.js"></script>
</head>
<body>
<div class="main">
<!-- 左按钮 -->
<a href="javascript:;" class="left"><</a>
<!-- 右按钮 -->
<a href="javascript:;" class="right">></a>
<!-- 滚动区 -->
<ul>
<li>
<a href="#"><img src="img/focus1.jpg"></a>
</li>
<li>
<a href="#"><img src="img/focus2.jpg"></a>
</li>
<li>
<a href="#"><img src="img/focus3.jpg"></a>
</li>
<li>
<a href="#"><img src="img/focus4.jpg"></a>
</li>
</ul>
<!-- 小圆 -->
<ol class="circle">
</ol>
</div>
</body>
</html>
index.css
*{
padding: 0;
margin: 0;
}
ul,ol{
list-style: none;
}
a{
text-decoration: none;
}
.main{
position: relative;
width: 590px;
height: 470px;
margin: 150px auto;
overflow: hidden;
}
.main ul{
position: absolute;
left: 0;
top: 0;
width: 1000%;
}
.main ul li{
float:left;
}
.left,.right{
display: none;
position: absolute;
top: 50%;
margin-top: -20px;
width: 24px;
height: 40px;
background: rgba(0, 0, 0, .3);
text-align: center;
line-height: 40px;
color: #fff;
font-size: 18px;
z-index: 2;
}
.right{
right: 0;
}
.circle {
position: absolute;
bottom: 15px;
left:50%;
transform: translateX(-50%);
}
.circle li {
float: left;
width: 8px;
height: 8px;
border: 2px solid orange;
margin: 0 3px;
border-radius: 50%;
cursor: pointer;
}
.current {
background-color: orange;
}
animate.js
// 动画函数 (对象,目标位置,回调函数)
function animate(obj, target, callback) {
// 清除定时器
clearInterval(obj.timer);
obj.timer = setInterval(function () {
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
// 回调函数
callback && callback();
}
obj.style.left = obj.offsetLeft + step + "px";
}, 15);
}
index.js
window.addEventListener('load', function () {
var left = document.querySelector('.left'); // 左翻页按钮
var right = document.querySelector('.right'); // 右翻页按钮
var main = document.querySelector('.main'); // 用户可视区域
var mainWidth = main.offsetWidth; // mian盒子宽度
// 鼠标经过
main.addEventListener('mouseenter', function () {
left.style.display = 'block';
right.style.display = 'block';
// 清除定时器,自动翻页动画停止
clearInterval(timer);
timer = null;
});
// 鼠标离开
main.addEventListener('mouseleave', function () {
left.style.display = 'none';
right.style.display = 'none';
// 自动翻页动画开始
timer = setInterval(function () {
right.click();
}, 2000);
});
var ul = main.querySelector('ul'); // 图片列表
var ol = main.querySelector('.circle'); // 圆圈列表
// 动态创建圆圈,数量根据 图片个数 决定
for (var i = 0; i < ul.children.length; i++) {
var li = document.createElement('li');
li.setAttribute('index', i);
ol.appendChild(li);
// 绑定点击事件
li.addEventListener('click', function () {
// 所有圆圈去除类名 current
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 当前圆圈添加类名 current
this.className = 'current';
// 获得索引。用户点击了第几个图片
var index = this.getAttribute('index');
num = index;
circle = index;
// 向左滚动
animate(ul, -index * mainWidth);
})
}
// 克隆第一个图片放到ul的最后
ol.children[0].className = 'current';
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
var num = 0; // 当前滚动图片索引
var circle = 0; // 圆圈索引
var flag = true; // 是否滚动完成的标志
// 右翻页
right.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果滚动到最后一张,即克隆的第一张
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * mainWidth, function () {
flag = true;
});
circle++;
// 圆圈到了最后一个
if (circle == ol.children.length) {
circle = 0;
}
circleChange();
}
});
// 左翻页
left.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果是第一张
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * mainWidth + 'px';
}
num--;
animate(ul, -num * mainWidth, function () {
flag = true;
});
circle--;
if (circle < 0) {
circle = ol.children.length - 1;
}
circleChange();
}
});
// 圆圈状态切换
function circleChange() {
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
ol.children[circle].className = 'current';
}
// 定时器,自动翻页
var timer = setInterval(function () {
right.click();
}, 2000);
})
参考
- 代码来自黑马程序员视频教程