电商首页那种「几秒换一张、可点圆点、可点左右箭头」的效果,叫轮播(carousel)。框架很多,但自己用原生 JS 写一遍,能搞清 显示层、状态、定时器 三件事。
HTML 结构
<div id="flash" class="carousel">
<ul id="pic" class="slides">
<li class="is-active"><img src="https://cdn.jsdelivr.net/gh/madneal/blog-image@main/images/optimized/content/a92b09d22995cf7861e5.webp" alt="slide 1"></li>
<li><img src="https://cdn.jsdelivr.net/gh/madneal/blog-image@main/images/optimized/content/358aa74966f1ebd20707.webp" alt="slide 2"></li>
<li><img src="https://cdn.jsdelivr.net/gh/madneal/blog-image@main/images/optimized/content/1e4b825ce0e55ce896d9.webp" alt="slide 3"></li>
</ul>
<ol id="num" class="dots"></ol>
<button type="button" id="left" class="arrow" aria-label="上一张">‹</button>
<button type="button" id="right" class="arrow" aria-label="下一张">›</button>
</div>
要点:图片用有意义的 alt;箭头用 button 而不是空链接,对键盘与无障碍更友好。
CSS 关键
.carousel { position: relative; width: 730px; height: 454px; overflow: hidden; }
.slides li { position: absolute; inset: 0; display: none; }
.slides li.is-active { display: block; }
.dots { position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%); z-index: 2; }
.dots li { display: inline-block; width: 10px; height: 10px; margin: 0 4px; border-radius: 50%; background: #666; cursor: pointer; }
.dots li.is-active { background: #f00; }
.arrow { position: absolute; top: 50%; z-index: 2; transform: translateY(-50%); }
#left { left: 8px; } #right { right: 8px; }
所有 slide 叠在同一位置,只显示带 is-active 的那一张。也可用 opacity + transition 做淡入淡出。
JavaScript 逻辑
(function () {
const slides = [...document.querySelectorAll('#pic li')];
const dotsBox = document.querySelector('#num');
let index = 0;
let timer = null;
// 生成圆点
slides.forEach((_, i) => {
const li = document.createElement('li');
if (i === 0) li.className = 'is-active';
li.addEventListener('click', () => go(i));
dotsBox.appendChild(li);
});
const dots = [...dotsBox.children];
function go(i) {
slides[index].classList.remove('is-active');
dots[index].classList.remove('is-active');
index = (i + slides.length) % slides.length;
slides[index].classList.add('is-active');
dots[index].classList.add('is-active');
}
document.querySelector('#left').onclick = () => go(index - 1);
document.querySelector('#right').onclick = () => go(index + 1);
function play() { timer = setInterval(() => go(index + 1), 3000); }
function stop() { clearInterval(timer); }
const root = document.querySelector('#flash');
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', play);
play();
})();
状态只有一个 index;所有 UI 都从 go 派生,避免左右箭头与圆点各写一套。
常见坑
- 定时器叠加:每次
play前先clearInterval。 - 图片未统一尺寸:容器会被撑乱,CSS 里固定高宽并
object-fit: cover。 - 只有 display 切换无过渡:要动画需
opacity或位移轨道。 - 移动端:需考虑滑动手势;也可用
scroll-snap做更现代方案。
无障碍与性能
- 自动播放应提供暂停(悬停/按钮)
- 用户
prefers-reduced-motion时可关闭自动播放 - 非首屏图
loading="lazy",首图优先
小结
轮播 = 绝对定位叠图 + 当前索引 + 定时器。原生实现几十行就够理解;上线项目可再换 Swiper 等库,但原理相同。旧笔记里的完整 CSS/JS 可按本文结构重写,比复制一长串无注释代码更易维护。