主题
DOM 对象深入分析(Day16 3/3)
本页对应 Day16_DOM,共 5 段视频,约 1 小时 6 分。
学习目标
- 能够使用 HTML 与 CSS 搭建轮播图结构,完成图片列表、底部按钮、左右箭头的布局,并实现默认显示第一张图片及对应按钮激活样式。
- 能够使用 JavaScript 为底部按钮绑定点击事件,实现按钮选中状态排他、对应图片切换,并说明双重遍历中事件回调的执行时机。
- 能够通过统一索引变量同步底部按钮与左右箭头,实现上一张、下一张切换,并处理索引越界后的首尾循环。
- 能够使用定时器实现轮播图自动播放,封装切换函数,并实现鼠标移入停止、移出继续播放。
- 能够使用 opacity 配合 CSS transition 替代 display 切换,实现轮播图图片淡入淡出效果,并说明图片绝对定位叠加的必要性。
知识点
轮播图布局:先把结构搭稳
为什么需要:轮播图形式很多,但布局结构基本一致,先把“容器—图片列表—按钮列表—左右箭头”搭好,后面切图逻辑才有稳定落点。怎么想:外层用定位容器,图片和按钮都用列表;图片默认只显示一张,其余隐藏;按钮和箭头覆盖在图片上。怎么做:HTML 保留图片和按钮结构,CSS 给容器宽高与相对定位,给图片 li 绝对定位并叠在一起。易错点:display:none 不占位,后面做淡入淡出时不能直接替代 opacity;箭头图是精灵图,右箭头要改 background-position。
html
<div class="play-books" id="play-books">
<ul class="image">
<li class="active"><a href="#"><img src="image/01.jpg" alt="轮播图第一张"></a></li>
<li><a href="#"><img src="image/02.jpg" alt="轮播图第二张"></a></li>
<li><a href="#"><img src="image/03.jpg" alt="轮播图第三张"></a></li>
<li><a href="#"><img src="image/04.jpg" alt="轮播图第四张"></a></li>
<li><a href="#"><img src="image/05.jpg" alt="轮播图第五张"></a></li>
</ul>
<ol class="btns">
<li class="active">1</li><li>2</li><li>3</li><li>4</li><li>5</li>
</ol>
<span class="arrow prev"></span>
<span class="arrow next"></span>
</div>css
.play-books { position: relative; width: 900px; height: 350px; margin: 100px auto; }
.image li { position: absolute; left: 0; top: 0; width: 900px; height: 350px; opacity: 0; transition: opacity .6s; }
.image li.active { opacity: 1; }
.btns { position: absolute; right: 10px; bottom: 10px; display: flex; }
.btns li { width: 20px; height: 20px; margin-left: 10px; border: 1px solid #fff; border-radius: 50%; background: #000; color: #fff; text-align: center; line-height: 20px; cursor: pointer; }
.btns li.active { color: #f60; border-color: #f60; }
.arrow { position: absolute; top: 50%; width: 40px; height: 100px; transform: translateY(-50%); background-image: url("image/arrow.png"); cursor: pointer; }
.arrow.prev { left: 0; }
.arrow.next { right: 0; background-position: -50px 0; }点击底部按钮切换:索引与排他
为什么需要:点哪个按钮就显示哪张图,同时只能有一个按钮处于选中态。怎么想:按钮和图片数量一致、索引一一对应,用一个 index 记录当前项;每次先“排他”清空所有 active,再给当前项加 active。怎么做:给每个按钮绑点击事件,点击时把 index 改成当前按钮索引,再调用统一的 setActive()。易错点:不要只给当前按钮加类而忘了清空旧按钮;转写稿里提到“按钮 forEach 里套图片 forEach”,执行时间不同,但更稳妥是抽成 setActive() 统一处理。
js
function setActive() {
imageItems.forEach(item => item.classList.remove('active'));
btnItems.forEach(item => item.classList.remove('active'));
imageItems[index].classList.add('active');
btnItems[index].classList.add('active');
}
btnItems.forEach((btn, i) => {
btn.addEventListener('click', () => {
index = i;
setActive();
});
});左右箭头与同步:用 index 做单一数据源
为什么需要:底部按钮、左右箭头、自动播放如果各改各的,就会不同步。怎么想:让它们都只改同一个 index,再统一调用 setActive(),就能同步。怎么做:封装 next() 和 prev();右箭头和自动播放调用 next(),左箭头调用 prev()。易错点:越界。转写稿调试时,到第 5 张再点下一个会找不到元素并报错,所以 next() 里判断 index > btnItems.length - 1 就回到 0;prev() 里判断 index < 0 就回到最后一项。另注意变量名写错会报错,转写稿里就出现过 BT index 少字母、intervalId 少 G 导致停不下来的调试过程。
js
function next() {
index++;
if (index > btnItems.length - 1) index = 0;
setActive();
}
function prev() {
index--;
if (index < 0) index = btnItems.length - 1;
setActive();
}
nextArrow.addEventListener('click', next);
prevArrow.addEventListener('click', prev);自动播放与暂停:定时器与封装
为什么需要:轮播图要自动切换,鼠标移入时又要暂停,方便用户查看。怎么想:定时器每隔一段时间调用 next();鼠标进入清定时器,离开重新设置。怎么做:用 intervalId 保存定时器标记,mouseenter 里 clearInterval(intervalId),mouseleave 里重新 setInterval(next, duration)。易错点:重新设置定时器时不要再写 let intervalId,否则停的是旧标记;转写稿里还提到相同代码不要写两遍,所以把“切下一张”封装成 next(),定时器直接传函数名,不要写成 next()。
js
let intervalId = setInterval(next, 3000);
playBooks.addEventListener('mouseenter', () => clearInterval(intervalId));
playBooks.addEventListener('mouseleave', () => {
intervalId = setInterval(next, 3000);
});淡入淡出:用 opacity 替代 display
为什么需要:display:none/block 切换很生硬,且 CSS 过渡不能作用在 display 上。怎么想:用 opacity:0/1 控制显隐,opacity 是纯数字,可以加过渡。怎么做:给图片 li 绝对定位并全部叠在 left:0; top:0,非激活 opacity:0,激活 opacity:1,再加 transition: opacity .6s。易错点:只把 display 换成 opacity 会发现五张图仍占位、层叠错乱,因为 opacity:0 的元素还占位置;所以必须配合绝对定位。转写稿中先改 opacity 发现错乱,再给 li 加绝对定位,最后加过渡,这个推导顺序要理解。
本节作业说明
本节作业是“滑动效果轮播图”。转写稿里课堂先把轮播图的布局、底部按钮切换、左右箭头切换、自动播放、鼠标悬停暂停跑通,最后用 opacity 做了淡入淡出。若作业要求“滑动”,应替换图片显示方式;但转写稿未展开位移滑动实现,本文不编造。下面把课堂推导整理成可复用的轮播核心。转写稿里的“轮波图”“Play干 books”“C抬”等,分别纠正为“轮播图”“play-books”“setActive”。
布局:先搭结构再谈效果
为什么需要:轮播图形式很多,但结构基本一致:外层容器包图片列表和按钮列表。
怎么想:外层 .play-books 负责宽高和相对定位;图片用 ul.image > li;底部按钮用 ol.btns > li;左右箭头最后加 span.arrow.prev/.next。
怎么做:
html
<div class="play-books" id="play-books">
<ul class="image">
<li class="active"><a href="#"><img src="image/01.jpg" alt="轮播图第一张"></a></li>
<li><a href="#"><img src="image/02.jpg" alt="轮播图第二张"></a></li>
<!-- 其余图片同理 -->
</ul>
<ol class="btns">
<li class="active">1</li><li>2</li><li>3</li><li>4</li><li>5</li>
</ol>
<span class="arrow prev"></span>
<span class="arrow next"></span>
</div>CSS 关键:图片 900×350,默认只显示 .active;按钮绝对定位到图片上;箭头宽 40 高 100,纵向居中用 top:50%; transform:translateY(-50%)。
易错点:ul/ol 默认有 list-style 和内边距,要先清掉;图片设 display:block 去掉底部空白;按钮和箭头要定位,父级必须 position:relative。
底部按钮切换与排他
为什么需要:点哪个按钮,哪张图显示,且原来选中的按钮要取消。
怎么想:这跟选项卡几乎一样:先全部取消,再给当前项加 active。
怎么做:
js
const playBooks = document.querySelector('#play-books');
const imageItems = playBooks.querySelectorAll('.image li');
const btnItems = playBooks.querySelectorAll('.btns li');
let index = 0;
function setActive() {
imageItems.forEach(item => item.classList.remove('active'));
btnItems.forEach(item => item.classList.remove('active'));
imageItems[index].classList.add('active');
btnItems[index].classList.add('active');
}
btnItems.forEach((btn, btnIndex) => {
btn.addEventListener('click', () => {
index = btnIndex;
setActive();
});
});易错点:外层 forEach 只是注册事件,点击回调稍后才执行;回调里的 btnIndex 与外层变量不要混。转写稿中曾把 btnIndex 拼错导致报错,调试时看控制台行号即可。
左右箭头与 index 同步
为什么需要:底部按钮和左右箭头不能各玩各的,必须控制同一套“当前索引”。
怎么想:用上层作用域的 index 记录当前图片和按钮索引;点按钮改 index,点箭头也改 index,最后统一调 setActive()。
怎么做:
js
function next() {
index++;
if (index > btnItems.length - 1) index = 0;
setActive();
}
function prev() {
index--;
if (index < 0) index = btnItems.length - 1;
setActive();
}
nextArrow.addEventListener('click', next);
prevArrow.addEventListener('click', prev);易错点:到第一张再点“上一个”要回到最后一张;到最后一张再点“下一个”要回到第一张。不要写死 4,用 btnItems.length - 1 更稳。
自动播放与鼠标悬停暂停
为什么需要:轮播图通常自动切换,但用户想细看时应暂停。
怎么想:定时器只做“index 递增 + 调 next”;鼠标进入清定时器,离开重新开。
怎么做:
js
const duration = 3000;
let intervalId = setInterval(next, duration);
playBooks.addEventListener('mouseenter', () => {
clearInterval(intervalId);
});
playBooks.addEventListener('mouseleave', () => {
intervalId = setInterval(next, duration);
});易错点:intervalId 拼写要一致;离开后重新赋值给同一个变量,否则下次停不下来。转写稿里就出现过变量少字母导致暂停失效的调试过程。
淡入淡出:用 opacity 替代 display
为什么需要:display:none/block 不能加过渡,切换很生硬。
怎么想:用 opacity:0/1 控制显隐,再给 opacity 加 transition。但 opacity:0 仍占位置,所以图片 li 要绝对定位叠在一起。
怎么做:
css
.image li {
position: absolute;
left: 0;
top: 0;
width: 900px;
height: 350px;
opacity: 0;
transition: opacity .6s;
}
.image li.active { opacity: 1; }易错点:只改 opacity 不定位,五张图会按文档流排开;过渡时间不宜过长,课堂用 400–600ms。作业若要求“滑动”,应把显示方式换成位移方案,而不是只改 opacity。
课堂画面





代码示例
代码 1:轮播图布局 + 点击底部按钮切换图片
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>轮播图-布局与底部按钮切换</title>
<style>
/* ---------- 全局重置 ---------- */
* { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { display: block; } /* 去掉图片底部空白 */
/* ---------- 外层容器 ---------- */
.play-books {
position: relative; /* 子元素定位的包含块 */
width: 900px;
height: 350px;
margin: 100px auto;
overflow: hidden;
}
/* ---------- 图片列表 ---------- */
.play-books .image li {
width: 900px;
height: 350px;
display: none; /* 默认全部隐藏 */
}
.play-books .image li.active { /* 权重更高,覆盖上面的 none */
display: block;
}
/* ---------- 底部按钮 ---------- */
.play-books .btns {
position: absolute; /* 叠到图片上面,必须定位 */
right: 20px;
bottom: 20px;
display: flex; /* 让 5 个圆点横着排 */
}
.play-books .btns li {
width: 20px;
height: 20px;
margin-left: 10px;
background: #000;
color: #fff;
border: 1px solid #fff;
text-align: center;
line-height: 20px; /* 文字纵向居中 */
border-radius: 50%; /* 变成圆形 */
cursor: pointer; /* 小手 */
}
.play-books .btns li.active {
color: #f60;
border-color: #f60;
}
/* ---------- 左右箭头 ---------- */
.play-books .arrow {
position: absolute;
top: 50%;
transform: translateY(-50%); /* 只做纵向居中 */
width: 40px;
height: 100px;
background-image: url(images/arrow.png); /* 精灵图,自行替换 */
cursor: pointer;
}
.play-books .arrow.prev { left: 0; }
.play-books .arrow.next {
right: 0;
background-position: -50px 0; /* 只改 X 坐标,取右侧那半张 */
}
</style>
</head>
<body>
<div class="play-books" id="play-books">
<!-- 图片列表 -->
<ul class="image">
<li class="active"><a href="#"><img src="images/01.jpg" alt=""></a></li>
<li><a href="#"><img src="images/02.jpg" alt=""></a></li>
<li><a href="#"><img src="images/03.jpg" alt=""></a></li>
<li><a href="#"><img src="images/04.jpg" alt=""></a></li>
<li><a href="#"><img src="images/05.jpg" alt=""></a></li>
</ul>
<!-- 底部按钮列表 -->
<ol class="btns">
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ol>
<!-- 左右箭头 -->
<span class="arrow prev"></span>
<span class="arrow next"></span>
</div>
<script>
(function () {
// 1. 获取元素
var playBooks = document.querySelector('#play-books');
var imageItems = playBooks.querySelectorAll('.image li'); // 所有图片(的 li)
var btnItems = playBooks.querySelectorAll('.btns li'); // 所有按钮
// 2. 给每个按钮绑定点击事件
btnItems.forEach(function (btnItem, index) {
btnItem.onclick = function () {
// 排他:先把所有按钮、所有图片都取消选中/隐藏
btnItems.forEach(function (item, i) {
item.classList.remove('active');
imageItems[i].classList.remove('active'); // 索引一一对应,可顺手一起排
});
// 再让当前按钮、对应图片激活
this.classList.add('active');
imageItems[index].classList.add('active');
};
});
})();
</script>
</body>
</html>要点与易错点
.play-books必须写position: relative,否则底部按钮和左右箭头的absolute会跑到页面上找别的包含块。.play-books .image li默认display: none,.active那条display: block权重更高,才能覆盖,这是“一次只显示一张”的关键。- 左右箭头用
top: 50% + transform: translateY(-50%)做纵向居中,只处理 Y 轴,不要写成translate(-50%, -50%)。 - 排他操作的本质是“先全部取消,再单独激活”,顺序不能反。
forEach里嵌套forEach不冲突:外层只负责绑定事件,内层回调要等点击时才执行,两者作用域和索引互不影响。- 图片
li和按钮li数量必须一致,才能用同一个index一起排他。
代码 2:完整版(左右箭头 + 自动播放 + 鼠标移入暂停 + 淡入淡出)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>轮播图-完整版</title>
<style>
* { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { display: block; }
.play-books {
position: relative;
width: 900px;
height: 350px;
margin: 100px auto;
overflow: hidden;
}
/* ---------- 关键改动:用 opacity 代替 display ---------- */
.play-books .image li {
position: absolute; /* 5 张图必须叠在一起,否则会各占一行位置 */
top: 0;
left: 0;
width: 900px;
height: 350px;
opacity: 0; /* 0 = 看不见,替代 display:none */
transition: opacity .6s; /* display 不能过渡,opacity 可以 */
}
.play-books .image li.active {
opacity: 1; /* 1 = 完全显示 */
}
.play-books .btns {
position: absolute;
right: 20px;
bottom: 20px;
display: flex;
z-index: 2; /* 保证圆点盖在图片上面 */
}
.play-books .btns li {
width: 20px;
height: 20px;
margin-left: 10px;
background: #000;
color: #fff;
border: 1px solid #fff;
text-align: center;
line-height: 20px;
border-radius: 50%;
cursor: pointer;
}
.play-books .btns li.active {
color: #f60;
border-color: #f60;
}
.play-books .arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 100px;
background-image: url(images/arrow.png);
cursor: pointer;
z-index: 2;
}
.play-books .arrow.prev { left: 0; }
.play-books .arrow.next { right: 0; background-position: -50px 0; }
</style>
</head>
<body>
<div class="play-books" id="play-books">
<ul class="image">
<li class="active"><a href="#"><img src="images/01.jpg" alt=""></a></li>
<li><a href="#"><img src="images/02.jpg" alt=""></a></li>
<li><a href="#"><img src="images/03.jpg" alt=""></a></li>
<li><a href="#"><img src="images/04.jpg" alt=""></a></li>
<li><a href="#"><img src="images/05.jpg" alt=""></a></li>
</ul>
<ol class="btns">
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ol>
<span class="arrow prev"></span>
<span class="arrow next"></span>
</div>
<script>
(function () {
// ---------- 1. 获取元素 ----------
var playBooks = document.querySelector('#play-books');
var imageItems = playBooks.querySelectorAll('.image li');
var btnItems = playBooks.querySelectorAll('.btns li');
var prevArrow = playBooks.querySelector('.arrow.prev');
var nextArrow = playBooks.querySelector('.arrow.next');
// ---------- 2. 关键状态变量 ----------
var index = 0; // 记录当前显示图片 / 选中按钮的索引(同步的核心)
var duration = 3000; // 轮播间隔,一般 3~5 秒
var timerId = null; // 定时器标记,暂停时要靠它清掉
// ---------- 3. 按 index 统一刷新视图 ----------
function setActive() {
imageItems.forEach(function (item) {
item.classList.remove('active');
});
btnItems.forEach(function (item) {
item.classList.remove('active');
});
imageItems[index].classList.add('active');
btnItems[index].classList.add('active');
}
// ---------- 4. 下一张 / 上一张(含边界判断) ----------
function next() {
index++;
if (index > btnItems.length - 1) { // 超过最后一张就回到 0
index = 0;
}
setActive();
}
function prev() {
index--;
if (index < 0) { // 小于 0 就跳到最后一个
index = btnItems.length - 1;
}
setActive();
}
// ---------- 5. 底部按钮:只改 index,再调用 setActive ----------
btnItems.forEach(function (btnItem, i) {
btnItem.onclick = function () {
index = i; // 改的是外层那个 index,不是回调参数
setActive();
};
});
// ---------- 6. 左右箭头 ----------
nextArrow.onclick = next; // 注意:传函数引用,不加括号
prevArrow.onclick = prev;
// ---------- 7. 自动播放 ----------
timerId = setInterval(next, duration);
// ---------- 8. 鼠标移入暂停 / 移出继续 ----------
playBooks.onmouseenter = function () {
clearInterval(timerId);
};
playBooks.onmouseleave = function () {
timerId = setInterval(next, duration); // 重新赋值给同一个变量,下次才能清掉
};
})();
</script>
</body>
</html>要点与易错点
index是同步的核心:底部按钮、左右箭头、自动播放三者都不直接操作 DOM,只改index,再统一调setActive()。这样三个入口控制的是同一套系统,不会各玩各的。- 边界判断不能省:
next()里index > btnItems.length - 1就归 0,prev()里index < 0就取length - 1。不判断的话索引会越界,imageItems[index]是undefined,classList直接报错。 setActive()里的index不要加形参:如果写成function setActive(index),那就是在函数内部重新定义了一个变量,外面的index就失效了,整个同步逻辑会崩。setInterval(next, duration)不能写成setInterval(next(), duration),加了括号是立刻执行并把返回值当回调。onmouseleave里必须写timerId = setInterval(...):如果不赋值(或写成var timerId = ...),新定时器的标记就存不进外层那个变量,下次clearInterval(timerId)清掉的还是旧标记,鼠标移入就停不下来了。- 淡入淡出必须改两处 CSS:一是
li加position: absolute; top: 0; left: 0(否则opacity: 0的元素仍占位置,五张图会上下排开);二是把display换成opacity,因为display无法参与transition。 transition加在.image li上,时间不宜太长,转写稿里最终用的是 600ms 左右,再长就显得拖沓。background-position: -50px 0里只改 X 坐标(负数往左推一张图的宽度),Y 保持 0;写成两个正数或写错轴,箭头就显示成和左边一样。
常见错误
切换图片时只有“啪”一下的硬切,加不上过渡 现象:给
display的none/block加了transition,但图片仍然瞬间跳变。 原因:display不是可过渡属性,值从none变block属于离散跳变,过渡规则对它无效。 修法:改用opacity: 0 / 1控制显隐,并把transition写在真正变化的那个属性上(transition: opacity .6s),时间控制在 400–600ms。改成
opacity后五张图全叠在一起、位置错乱 现象:第一张正常,后面的图往下排,或最后一张盖住前面所有。 原因:opacity: 0的元素仍然占据文档流位置,只是看不见,与display: none的“不占位”完全不同。 修法:给每个li加position: absolute; left: 0; top: 0,让五张图在父容器的同一个坐标点叠放,父容器保持position: relative。点到最后一张再点“下一个”,图片全没了,控制台报错 现象:
Uncaught TypeError: Cannot read properties of undefined (reading 'classList')。 原因:index没有做边界处理,越界后imgItems[index]是undefined,后续classList.add直接抛错。 修法:改索引的函数里先判断——index < 0时置为list.length - 1,index > list.length - 1时置为0;长度用集合的length,不要写死4。鼠标移入后定时器停不下来,或移出后越切越快 现象:鼠标放上去还在自动播放;或者移出几次后图片切换频率翻倍。 原因:
setInterval的返回值没有存到共享变量里,clearInterval收到的是undefined;或者每次移出都新建一个定时器而没有先清掉旧的,导致多个定时器叠加。 修法:在事件回调外层用同一个上层作用域变量保存 id,移入时clearInterval(timerId),移出时先清再timerId = setInterval(next, delay),全局只保留一个定时器。点新按钮后,旧按钮的高亮还在(排他没做干净) 现象:点了第 3 个圆点,第 1 个圆点依然是选中色,两个同时亮。 原因:只在回调里给自己
add('active'),没有先把所有按钮、所有图片的active清掉;或者清的时候用了querySelector只拿到第一个元素。 修法:每次点击回调开头先遍历全部按钮remove('active')、遍历全部图片remove('active'),再给当前index对应的按钮和图片add('active')。排他逻辑和“显示当前项”必须成对出现。函数里重新声明了
index,导致点击按钮图片不动 现象:圆点高亮正常,但图片始终停在第一张;或箭头点击完全没反应。 原因:在函数内部写了let index = ...或var index = ...,产生了新的局部变量,遮蔽了外层用于同步的共享index,外层值从未被修改。 修法:函数内只做赋值:index = btnIndex,不加let/var/const;改完立刻调用统一的渲染函数。把
setInterval的回调写成setInterval(next(), delay)现象:页面一打开图片立刻跳一张然后不动了,或者点击箭头后自动播放消失。 原因:next()是立即调用,传给定时器的是它的返回值(undefined),定时器等于什么都没执行。 修法:传函数引用setInterval(next, delay),需要传参时用箭头函数包一层。
练习
让轮播图不依赖固定张数 把 5 个
li改成由 JS 数组渲染生成(数组里放图片地址),然后往数组里加到 7 张。要求:不改任何边界判断代码,点击箭头仍能正确循环。验收点:从第 7 张点“下一个”回到第 1 张,不报错。手动操作后重置自动播放 现在点完箭头,定时器仍按原节奏触发,会出现“刚点完立刻又跳一张”。要求:任何一次手动切换(点圆点或箭头)后,自动播放的计时从 0 重新开始。写完后说明你把重置逻辑放在哪一层、为什么放这里最省代码。
给轮播图加页码指示 在右下角显示
3 / 5这样的当前页文本,随图片同步更新。验收点:自动播放、点圆点、点箭头三种方式下页码都正确;鼠标悬停暂停时页码不变。改用滑动效果(对应作业) 把淡入淡出换成横向滑动:所有图并排放在一个宽为
5 × 900px的轨道里,通过transform: translateX(-index * 900px)移动,并加过渡。要求:改完后原有三个功能(圆点、箭头、自动播放)全部保持正常。思考:li还需要position: absolute吗?边界判断代码需要改吗?键盘控制 + 空格暂停 支持左右方向键切换、空格键切换播放/暂停状态。要求暂停后鼠标移入移出不再恢复播放,需要空格再恢复。验收点:三种控制方式(键盘、鼠标点击、自动)互不冲突,不会出现两个定时器同时在跑。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 轮播图-布局 | 17:05 |
| 2 | 轮播图-点击底部按钮切换图片 | 10:54 |
| 3 | 轮播图-点击左右箭头切换图片 | 17:15 |
| 4 | 轮播图-自动播放 | 15:54 |
| 5 | 轮播图-淡入淡出效果 | 05:50 |
小结
- 轮播图的布局结构是通用的:一个定位父容器 + 叠放的图片列表 + 定位在底部的按钮列表 + 定位在两侧的箭头,形式再花哨也逃不出这个骨架。
- 让多张图“只显示一张”有两种实现,
display和opacity的区别不是审美问题,而是能不能加过渡的问题;要效果就必须选可过渡属性,代价是必须自己处理叠放定位。 - 整个轮播图真正的核心只有一个变量——当前索引。圆点、箭头、自动播放三者能同步,靠的不是互相调用,而是都只修改同一个索引,再由同一个渲染函数统一表现。
- 因此正确的封装顺序是:先有渲染函数,再让所有入口去改索引,最后让自动播放也走同一个“切下一张”函数。任何一处绕过索引直接操作 DOM,都会埋下不同步的隐患。
- 索引的边界判断必须基于集合的
length而不是硬编码数字,这是后续能否低成本改张数的分水岭。 - 定时器的两个必备动作:返回值必须被保存下来、再次创建前必须确保旧定时器已清除。这两点做不到,就会出现“停不下来”和“越来越快”两个经典症状。
- 排他操作和显示当前项要写在一起、成对出现;只加不清或只清不加,都会出现两个高亮或全都不亮的中间态。
- 当发现同一段逻辑在第二处出现时,就应该立刻抽成函数——自动播放和点击箭头共用“切下一张”,是本次重构中最值得做的一次抽象。