什麼叫無縫輪播
無縫輪播是網頁中比較常見的一種輪播方式,很多網站首頁都應用了無縫輪播,這種效果使頁面看起來更順滑。
先來看一下我實作的效果圖:

無縫輪播的實作
下面用示意圖清楚的展現了無縫輪播的原理:
一共有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
時,這時候移動的就是正數了
當
目标left < 目前left
設定動畫效果
好的,既然邏輯清楚了,接下來就是愉快的寫代碼環節。
/**
* 切換到某一個圖檔的索引
* @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代碼和整個項目都放在末尾,有需要的可以看一下,也可以私信我發給你。前端知識多而亂,隻有掌握好基礎後期才能遊刃有餘。
附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);
整個項目zip
百度網盤連結:https://pan.baidu.com/s/1vQB8t_dI1zZeeXa-TA9rzw
提取碼:6ie9