无缝轮播是网页中比较常见的一种轮播方式,很多网站首页都应用了无缝轮播,这种效果使页面看起来更顺滑。
下面用示意图清楚的展现了无缝轮播的原理:
一共有3张图片轮播,但是为什么有5张图片呢?这是因为要想实现无缝轮播的效果,就要用“障眼法”。
每当图片滚动到最后一张或者第一张时,都会自动回到第一张或最后一张,加入这两张图片有一个过渡的效果。
好了,在介绍了无缝轮播之后,小伙伴们是不是跃跃欲试了呢,别着急,在正式写代码之前让我们先把逻辑理清楚。
假设当前正在播放的图片是1,令currentIndex = 1
,当前left = 实际运动过程中距离左边的长度,目标left = 要切换的图片距离左边的长度,且这两个均为负数。
如果当前图片移动方向是向左,当目标left < 当前left
时,假设是从0移动到2:
//目标left,index是要切换的图片的索引,imgWidth是图片宽度
var newLeft = index * imgWidth;
//计算需要移动的总距离
var distance = newLeft - marginLeft;
当目标left > 当前left
时,假设是从2移动到0:
//3张图片总宽
var totalWidth = 3 * igmWidth;
distance = -(totalWidth - Math.abs(newLeft - marginLeft));
接下来分析图片向右移动的情况
当目标left > 当前left
时,这时候移动的就是正数了
distance = newLeft - marginLeft;
当目标left < 当前left
distance = totalWidth - Math.abs(newLeft - marginLeft);
好的,既然逻辑清楚了,接下来就是愉快的写代码环节。
/**
* 切换到某一个图片的索引
* @param {*} index 要切换到目标的图片的索引
* @param {*} direction 图片移动的方向 "left" "right"
*/
function switchTo(index, direction) {
if (index == config.currentIndex) {
return;
}
if (!direction) {
direction = "left";
}
//最终的marginLeft
var newLeft = (-index - 1) * config.imgWidth;
animateSwitch();
//重新设置当前索引
config.currentIndex = index;
//设置小圆点的选中状态
setDotsActiveStatus();
/**
* 逐步改变marginLeft
*/
function animateSwitch() {
//先停止之前的动画
stopAnimate();
//1、计算运动的次数
var number = Math.ceil(config.timer.total / config.timer.duration);
//当前的运动次数
var currentNum = 0;
//2、计算需要移动总距离
var distance;
//获取当前的left值
var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
var totalWidth = config.imgsNum * config.imgWidth;
if (direction == "left") {
if (newLeft < marginLeft) {
distance = newLeft - marginLeft;
} else {
distance = -(totalWidth - Math.abs(newLeft - marginLeft));
}
} else {
if (newLeft > marginLeft) {
distance = newLeft - marginLeft;
} else {
distance = totalWidth - Math.abs(newLeft - marginLeft);
}
}
//3、计算每次运动的距离
var everyDistance = distance / number;
//将函数注册为config.timer.duration秒后调用,之后每隔config.timer.duration之后重复调用
config.timer.id = setInterval(function () {
//改变div的marginLeft
marginLeft += everyDistance;
if (direction == "left" && Math.abs(marginLeft) > totalWidth) {
marginLeft += totalWidth;
} else if (direction == "right" && Math.abs(marginLeft) < config.imgWidth) {
marginLeft -= totalWidth;
}
config.doms.divImgs.style.marginLeft = marginLeft + "px";
currentNum++;
if (currentNum == number) {
stopAnimate();
}
}, config.timer.duration)
function stopAnimate() {
clearInterval(config.timer.id);
config.timer.id = null;
}
}
}
这里我定义了一个函数去处理切换图片的逻辑,里面的animateSwitch
函数就是处理切换动画的,注意首先要停止当前动画,要不然一直点击的时候,就会一直执行这个函数,导致速度越来越快。
之后就是我们前面分析过的逻辑,计算出移动的总距离,再算出每次运动的距离,通过这个距离再调用setInterval
计时器,需要判断的是当图片移动到最后一张时怎么重新回到第一张,这里我们把marginLeft
的绝对值减去总宽度。
当到达marginLeft = totalWidth
这个临界值时,说明图片到了最后一张或者第一张,这个时候判断一下图片的运动方向,如果图片向左运动,下一次刷新假如到了下图位置:
这个时候令marginLeft = marfinLeft + totalWidth
就会运动到如下图位置,当然这个在浏览器中刷新是非常快的,人眼是感觉不到已经变化的了
图片向右运动同理,只需要将marginLeft = marfinLeft - totalWidth
即可
最后要取消动画的时候,只需要把计时器清除,并将id设为null。
以上就是无缝轮播的核心代码以及逻辑分析,仅供参考,各位小伙伴们如果看懂了一定要自己动手练习,我会把整个js代码和整个项目都放在末尾,有需要的可以看一下,也可以私信我发给你。前端知识多而乱,只有掌握好基础后期才能游刃有余。
//轮播配置对象
var config = {
imgWidth: 640, //图片宽度
dotsWidth: 8, //圆点宽度
doms: { //dom对象
divImgs: document.querySelector('.img'),
divDots: document.querySelector('.dots'),
divArrow: document.querySelector('.arrow'),
divLeft: document.querySelector('.left'),
divRight: document.querySelector('.right'),
},
currentIndex: 0, //当前显示的图片索引 0 ~ imgsNum-1
timer: { //运动计数器配置
duration: 16, //运动间隔的时间
total: 500, //运动的总时间,单位毫秒
id: null //计时器的id
},
autoTimer: null //自动移动的计时器
};
//图片数量
config.imgsNum = config.doms.divImgs.children.length;
/**
* 初始化函数
*/
function init() {
//初始化元素尺寸
initSize();
//初始化元素
initElements();
//初始化显示图片的位置
initImgPosition();
//设置小圆点的选中状态
setDotsActiveStatus();
}
init();
/**
* 初始化元素尺寸
*/
function initSize() {
//包裹圆点的尺寸,+6是margin相加的值
config.doms.divDots.style.width = (config.dotsWidth + 6) * config.imgsNum + "px";
//轮播图整体尺寸
config.doms.divImgs.style.width = config.imgWidth * (config.imgsNum + 2) + "px";
}
/**
* 初始化元素
*/
function initElements() {
//创建小圆点
for (var i = 0; i < config.imgsNum; i++) {
var span = document.createElement('span');
config.doms.divDots.appendChild(span);
}
//复制图片,实现无缝轮播
var children = config.doms.divImgs.children;
//第一张图片
var first = children[0];
//最后一张图片
var last = children[children.length - 1];
var newImg = first.cloneNode(true); //深度克隆
config.doms.divImgs.appendChild(newImg);
newImg = last.cloneNode(true); //深度克隆
config.doms.divImgs.insertBefore(newImg, first);
}
/**
* 初始化显示图片的位置
*/
function initImgPosition() {
//设置的currentIndex为几就显示第几张图片
var left = (-config.currentIndex - 1) * config.imgWidth;
config.doms.divImgs.style.marginLeft = left + "px";
}
/**
* 设置小圆点的选中状态
*/
function setDotsActiveStatus() {
//小圆点外边框
var children = config.doms.divDots.children;
for (var i = 0; i < children.length; i++) {
var dot = children[i];
if (i == config.currentIndex) {
dot.className = "active";
} else {
dot.className = "";
}
}
}
/**
* 切换到某一个图片的索引
* @param {*} index 要切换到目标的图片的索引
* @param {*} direction 图片移动的方向 "left" "right"
*/
function switchTo(index, direction) {
if (index == config.currentIndex) {
return;
}
if (!direction) {
direction = "left";
}
//最终的marginLeft
var newLeft = (-index - 1) * config.imgWidth;
animateSwitch();
//重新设置当前索引
config.currentIndex = index;
setDotsActiveStatus();
/**
* 逐步改变marginLeft
*/
function animateSwitch() {
//先停止之前的动画
stopAnimate();
//1、计算运动的次数
var number = Math.ceil(config.timer.total / config.timer.duration);
//当前的运动次数
var currentNum = 0;
//2、计算需要移动总距离
var distance;
//当前的left值
var marginLeft = parseFloat(getComputedStyle(config.doms.divImgs).marginLeft);
var totalWidth = config.imgsNum * config.imgWidth;
if (direction == "left") {
if (newLeft < marginLeft) {
distance = newLeft - marginLeft;
} else {
distance = -(totalWidth - Math.abs(newLeft - marginLeft));
}
} else {
if (newLeft > marginLeft) {
distance = newLeft - marginLeft;
} else {
distance = totalWidth - Math.abs(newLeft - marginLeft);
}
}
//3、计算每次运动的距离
var everyDistance = distance / number;
//将函数注册为config.timer.duration秒后调用,之后每隔config.timer.duration之后重复调用
config.timer.id = setInterval(function () {
//改变div的marginLeft
marginLeft += everyDistance;
if (direction == "left" && Math.abs(marginLeft) > totalWidth) {
marginLeft += totalWidth;
} else if (direction == "right" && Math.abs(marginLeft) < config.imgWidth) {
marginLeft -= totalWidth;
}
config.doms.divImgs.style.marginLeft = marginLeft + "px";
currentNum++;
if (currentNum == number) {
stopAnimate();
}
}, config.timer.duration)
function stopAnimate() {
clearInterval(config.timer.id);
config.timer.id = null;
}
}
}
/**
* 点击左右按钮和小圆点切换图片
*/
function changeImg() {
//给按钮注册点击事件
config.doms.divArrow.onclick = function (e) {
if (e.target.classList.contains("left")) {
var index = config.currentIndex - 1;
if (index < 0) {
index = config.imgsNum - 1;
}
switchTo(index, "right");
} else {
var index = (config.currentIndex + 1) % config.imgsNum;
switchTo(index, "left");
}
}
//给小圆点注册点击事件
config.doms.divDots.onclick = function (e) {
if (e.target.tagName == "SPAN") {
var index = Array.from(this.children).indexOf(e.target);
switchTo(index, index > config.currentIndex ? "left" : "right");
}
}
}
//点击左右按钮切换图片
changeImg();
/**
* 自动轮播
*/
config.autoTimer = setInterval(function() {
var index = (config.currentIndex + 1) % config.imgsNum;
switchTo(index, "left");
}, 3000);
百度网盘链接:https://pan.baidu.com/s/1vQB8t_dI1zZeeXa-TA9rzw
提取码:6ie9