轮播图
思路
- 页面初始化:动态创建img元素、设置容器宽度、动态创建指示器。
- 交互: 核心函数moveTo、next、prev
- 连通交互:指示器点击、左右切换、自动轮播
- 感受核心思想,先把核心功能实现,剩余的细节再修正。 期间会遇到一个又一个的问题,类似于boss,打败一个得到一个奖励。
- 当遇到某个函数知道某个状态(动画结束),另一个函数却又依赖这个状态时,那么就可以使用回调模式。
实现
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" />
</head>
<body>
<div class="carousel-container">
<div class="carousel-list"></div>
<div class="indicator"></div>
<div class="arrow">
<div class="arrow-left"></div>
<div class="arrow-right"></div>
</div>
</div>
<script src="./js/animate.js"></script>
<script src="./js/index.js"></script>
</body>
</html>css
.carousel-container {
width: 500px;
height: 300px;
overflow: hidden;
/* outline: 2px solid lightblue; */
margin: 0 auto;
position: relative;
}
.carousel-list {
width: 500%;
height: 100%;
}
.carousel-item {
width: 500px;
height: 100%;
object-fit: cover;
float: left;
}
.indicator {
position: absolute;
width: 100%;
left: 0;
bottom: 0;
height: 30px;
text-align: center;
}
.indicator-item {
display: inline-block;
width: 20px;
height: 2px;
background: #fff;
cursor: pointer;
margin: 0 5px;
}
.indicator-item.active {
background: #ffa44f;
}
.arrow-left,
.arrow-right {
position: absolute;
width: 50px;
height: 100%;
top: 0;
background: rgba(0, 0, 0, 0.5);
cursor: pointer;
opacity: 0;
color: #fff;
}
.arrow-left:hover,
.arrow-right:hover {
opacity: 1;
}
.arrow-left {
left: 0;
}
.arrow-left::before {
content: '<';
}
.arrow-right {
right: 0;
}
.arrow-right::before {
content: '>';
}
.arrow-left::before,
.arrow-right::before {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 50%;
height: 30px;
line-height: 30px;
margin-top: -15px;
}js
function createAnimation(options) {
var from = options.from; // 起始值
var to = options.to; // 结束值
var totalMS = options.totalMS || 1000; // 变化总时间
var duration = options.duration || 15; // 动画间隔时间
var times = Math.floor(totalMS / duration); // 变化的次数
var dis = (to - from) / times; // 每一次变化改变的值
var curTimes = 0; // 当前变化的次数
var timerId = setInterval(function () {
from += dis;
curTimes++; // 当前变化增加一次
if (curTimes >= times) {
// 变化的次数达到了
from = to; // 变化完成了
clearInterval(timerId); // 不再变化了
options.onmove && options.onmove(from);
options.onend && options.onend();
return;
}
// 无数的可能性
options.onmove && options.onmove(from);
}, duration);
}js
(function () {
function $(selector) {
return document.querySelector(selector);
}
// 初始化
var curIndex = 0; // 当前显示的是第几张图片
var doms = {
container: $('.carousel-container'),
carouselList: $('.carousel-list'),
indicator: $('.indicator'),
arrowLeft: $('.arrow-left'),
arrowRight: $('.arrow-right'),
};
var containerWidth = doms.container.clientWidth; // 容器可见区域的宽度
var urls = [
'./img/Wallpaper1.jpg',
'./img/Wallpaper2.jpg',
'./img/Wallpaper3.jpg',
'./img/Wallpaper4.jpg',
'./img/Wallpaper5.jpg',
]; //记录了要显示的所有轮播图的图片路径
function init() {
function _createImg(url) {
var img = document.createElement('img');
img.src = url;
img.className = 'carousel-item';
doms.carouselList.appendChild(img);
}
for (var i = 0; i < urls.length; i++) {
_createImg(urls[i]);
// 创建指示器
var div = document.createElement('div');
div.className = 'indicator-item';
doms.indicator.appendChild(div);
}
// 多加一张额外的图片
_createImg(urls[0]);
// 设置容器宽度
doms.carouselList.style.width = doms.carouselList.children.length + '00%';
// 设置指示器的激活状态
setIndicatorStatus();
}
/**
* 根据curIndex设置指示器的状态
*/
function setIndicatorStatus() {
// 1. 将目前激活的指示器取消激活
var active = $('.indicator-item.active');
if (active) {
active.className = 'indicator-item';
}
// 2. 激活当前的指示器
var index = curIndex;
if (index > urls.length - 1) {
index = 0;
}
doms.indicator.children[index].className = 'indicator-item active';
}
init();
// 交互
var totalMS = 300;
var isPlaying = false; // 是否有正在进行的切换动画
/**
* 将轮播图从当前的位置,切换到newIndex位置
* @param {*} newIndex 新的位置的图片索引
*/
function moveTo(newIndex, onend) {
if (isPlaying || newIndex === curIndex) {
return; // 有动画进行 或 切换目标和当前一致,不做任何事情
}
isPlaying = true;
var from = parseFloat(doms.carouselList.style.marginLeft) || 0;
var to = -newIndex * containerWidth;
createAnimation({
from: from,
to: to,
totalMS: totalMS,
onmove: function (n) {
doms.carouselList.style.marginLeft = n + 'px';
},
onend: function () {
isPlaying = false;
onend && onend();
},
});
curIndex = newIndex; // 更改当前显示的图片索引
setIndicatorStatus();
}
// 处理指示器的点击事件
for (var i = 0; i < doms.indicator.children.length; i++) {
(function (i) {
doms.indicator.children[i].onclick = function () {
moveTo(i);
};
})(i);
}
function next() {
var newIndex = curIndex + 1;
var onend;
if (newIndex === doms.carouselList.children.length - 1) {
// 切换到最后一张图片了
// 等动画完成后,要回到第一张图片
onend = function () {
doms.carouselList.style.marginLeft = 0;
curIndex = 0;
};
}
moveTo(newIndex, onend);
}
function prev() {
var newIndex = curIndex - 1;
if (newIndex < 0) {
var maxIndex = doms.carouselList.children.length - 1;
doms.carouselList.style.marginLeft = -maxIndex * containerWidth + 'px';
newIndex = maxIndex - 1;
}
moveTo(newIndex);
}
doms.arrowLeft.onclick = prev;
doms.arrowRight.onclick = next;
var duration = 2000; // 自动切换的间隔
var timerId;
function autoStart() {
if (timerId) {
// 已经有自动切换在进行了
return;
}
timerId = setInterval(next, duration);
}
function autoStop() {
clearInterval(timerId);
timerId = null;
}
autoStart(); // 最开始自动切换
doms.container.onmouseenter = autoStop;
doms.container.onmouseleave = autoStart;
})();js
/* 工具 */
function $(selector) {
return document.querySelector(selector);
}
/* 初始化 */
const pageData = {
imgWidth: 500,//图片宽度
totalMS: 300,//动画总时长
isPlaying: false,//动画是否正在进行
timerId: null,//计时器id
timerDuration: 2000,//计时器间歇
}
const doms = {
container: $('.carousel-container'),
carouselList: $('.carousel-list'),
indicator: $('.indicator'),
arrowLeft: $('.arrow-left'),
arrowRight: $('.arrow-right'),
}
var urls = [
'./img/Wallpaper1.jpg',
'./img/Wallpaper2.jpg',
'./img/Wallpaper3.jpg',
'./img/Wallpaper4.jpg',
'./img/Wallpaper5.jpg',
];
let currIndex = 0;//当前索引
// 初始函数
function init() {
function _createImg(url) {
const img = document.createElement('img');
img.src = url;
img.className = 'carousel-item';
doms.carouselList.appendChild(img);
}
function _createIndicators() {
const div = document.createElement('div');
div.className = 'indicator-item';
div.style.margin = "0 4px";
doms.indicator.appendChild(div);
}
urls.forEach(url => _createImg(url));//创建图片
_createImg(urls[0]);//克隆第一张图片
doms.carouselList.style.width = `${doms.carouselList.children.length * 100}%`;//设置容器宽度
urls.forEach(url => _createIndicators());//创建指示器
setIndicatorStatus();//默认激活第一个指示器
}
init();
/* 交互 */
// 设置指示器激活
function setIndicatorStatus() {
const active = $(".indicator-item.active");
if (active) {
active.className = "indicator-item";
}
// 边界处理
let index = currIndex;
if (index > urls.length - 1) {
index = 0;
}
doms.indicator.children[index].classList.add('active');
}
// 移动到目标
function moveTo(index, cb) {
if (pageData.isPlaying) {
return;
}
pageData.isPlaying = true;
const from = parseFloat(doms.carouselList.style.marginLeft) || 0;
const to = -index * pageData.imgWidth;
createAnimation({
from: from,
to: to,
totalMS: pageData.totalMS,
onmove: function (n) {
doms.carouselList.style.marginLeft = n + 'px';
},
onend: function () {
pageData.isPlaying = false;
cb && cb();//动画结束后执行的回调和外面的逻辑关联起来
}
})
currIndex = index;
setIndicatorStatus();
}
// 下一张
function next() {
if (pageData.isPlaying) {
return;
}
currIndex++;
let endCb = null;//动画结束后的回调
// 边界处理
if (currIndex == doms.carouselList.children.length - 1) {
endCb = function () {
doms.carouselList.style.marginLeft = 0;
currIndex = 0;
};
}
moveTo(currIndex, endCb);
}
// 上一张
function prev() {
if (pageData.isPlaying) {
return;
}
currIndex--;
// 边界处理
if (currIndex < 0) {
const maxIdx = doms.carouselList.children.length - 1;
doms.carouselList.style.marginLeft = - maxIdx * pageData.imgWidth + 'px';
currIndex = maxIdx - 1;
}
moveTo(currIndex);
}
// 自动轮播
function autoPlay() {
if (pageData.timerId) {
return;
}
pageData.timerId = setInterval(next, pageData.timerDuration);
}
// 停止轮播
function stopPlay() {
clearInterval(pageData.timerId);
pageData.timerId = null;
}
// 绑定事件-指示器
for (let i = 0; i < doms.indicator.children.length; i++) {
doms.indicator.children[i].onclick = function () {
moveTo(i);
}
}
// 绑定事件-开始/停止轮播
autoPlay();
doms.container.onmouseenter = stopPlay;
doms.container.onmouseleave = autoPlay;
// 绑定事件-左右箭头
doms.arrowLeft.onclick = prev;
doms.arrowRight.onclick = next;