本文详细解析如何使用原生JavaScript开发一个功能完整的图片轮播组件。内容涵盖HTML/CSS结构要求、核心原理(利用scrollLeft控制显示)、关键算法(缓动函数与位置计算)以及面向对象封装。同时提供性能优化方案,解决动画冲突与重复点击问题,并附带完整源码下载。
图片轮播组件功能概览
本文介绍如何使用原生JavaScript开发一个功能完整的图片轮播组件。
效果展示如下:

- 组件支持自动循环播放图片,底部设有导航按钮,点击可切换至对应图片。
- 图片切换过程包含平滑的动画过渡效果。
- 当鼠标悬停在图片区域时,自动轮播暂停,同时显示左右切换箭头,点击箭头可手动切换图片。
- 鼠标移出图片区域后,轮播将从当前图片位置继续自动播放。
- 组件提供配置接口,允许设置轮播方向、是否循环播放以及切换间隔时间。
HTML与CSS结构要求
对HTML和CSS结构有特定要求:
<div class="carousel-box">
<div class="carousel">
<ul class="clearfix" >
<li><img src="img/carousel01.jpg" alt=""></li>
<li><img src="img/carousel02.jpg" alt=""></li>
<li><img src="img/carousel03.jpg" alt=""></li>
<li><img src="img/carousel04.jpg" alt=""></li>
<li><img src="img/carousel05.jpg" alt=""></li>
<li><img src="img/carousel06.jpg" alt=""></li>
</ul>
</div>
</div>
- HTML结构必须采用两层嵌套,最内层需包含一个ul元素,所有图片必须包裹在li标签内。
- 开发者可以自定义类名,只需同步修改CSS文件中的对应类名即可。配置组件时,传入正确的DOM元素选择器即可生效。
- 组件不限制图片的宽度和数量,只需在CSS文件中调整相应的数值即可适配。
/*需要更改的值*/
.carousel img{
width: 600px;
height: 400px;
}
.carousel,
.carousel-box {
width: 600px; /*单张图片宽度*/
height: 400px; /*单张图片高度*/
}
.carousel ul{
width: 3600px; /*单张图片宽度x图片数量*/
}
核心实现原理
实现原理如下:
将所有图片横向排列,最外层容器和包裹容器均设置overflow:hidden以隐藏溢出部分。最外层容器用于定位导航按钮和切换箭头,通过操作包裹容器的scrollLeft属性来控制当前显示的图片。
关键功能模块实现
实现思路与功能模块划分:
为实现上述功能,需要构建以下核心方法:
1. 图片切换函数:接收滚动方向参数,调用缓动函数执行图片切换,并调用按钮状态更新函数以高亮当前对应按钮。
2. 缓动函数:负责实现平滑的动画过渡。
3. 按钮状态更新函数:负责点亮当前图片对应的导航按钮。
4. 初始化函数:负责绑定事件、创建导航按钮和切换箭头,并初始化初始显示位置。
5. 创建箭头函数:生成左右切换箭头并绑定点击事件。
6. 创建按钮函数:生成底部导航按钮组。
7. 开始轮播函数:作为外部接口,控制轮播的方向、间隔时间和循环状态。
8. 轮播函数:执行具体的自动轮播逻辑。
9. 停止函数:用于终止自动轮播过程。
此外,还包含若干通用辅助方法:$()用于选取DOM元素;addClass(ele, "className")用于添加类名;removeClass(ele, "className")用于移除类名;$.add(ele, "type", fun)用于绑定DOM事件;getCSS(ele, "prop")用于获取元素样式属性值;$.delegateTag("selector", "tagName", "type", fun)用于实现事件代理。
具体实现步骤如下:
假设页面包含6张图片,每张图片宽度为600px。以下按功能独立性逐步实现:
1. 缓动函数 liner
缓动函数的作用是逐步改变目标元素的属性值,直至达到目标值。该函数适用于水平或垂直轮播,也可用于元素在页面中的位移动画。它接收四个参数:目标元素、要改变的属性名、目标值以及移动步数。
liner=function(ele,prop,next,num){
var speed=(next-ele[prop])/num,
i=0;
(function(){
ele[prop]+=speed;
i++;
if (i<num) {
setTimeout(arguments.callee,30);
}
})();
},
2. 按钮状态更新函数 light
点亮按钮的本质是为其添加active类名,熄灭则是移除该类名。
如何确定当前需要点亮的按钮?
最直接的方法是获取按钮元素,因此可为每个按钮添加index属性,调用该函数时传入目标按钮的index值即可。
如何确定需要熄灭的按钮?
同样采用直接获取的方式,在作用域链末尾添加一个变量active,用于记录当前高亮的按钮,函数内部直接将其移除active类名即可。
light=function(index){
removeClass(active,"active");
active=$(this.wrapSelec+" "+"[index="+index+"]");
addClass(active,"active");
}
3. 图片切换函数 go
需要计算下一个scrollLeft的值:
若向左移动,scrollLeft应减去600;若当前值为0,则切换至3000。公式为:ele.scrollLeft===0 ? width*(len-1) : ele.scrollLeft-width。
若向右移动,scrollLeft应加上600,即从0到600,600到1200……直至3000回到0。可使用判断语句,或使用公式:next=(cur+distance)%(distance*num),即(ele.scrollLeft+width)%(width*len)。
需要计算下一个要高亮的按钮index:
逻辑与计算scrollLeft类似,向左移动时:index===0 ? len-1 : index-1;向右移动时:(index+1)%len。
go=function(dire){
var index=active.getAttribute("index")-0,
nextIndex,
nextPosition;
if (dire==="next") {
nextIndex=(index+1)%len;
nextPosition=(ele.scrollLeft+width)%(width*len);
}else{
nextIndex=index===0? len-1:index-1,
nextPosition=ele.scrollLeft===0?width*len:ele.scrollLeft-width;
}
light(nextIndex);
animate.liner(ele,"scrollLeft",nextPosition);
}
其中len(图片总数)、width(图片宽度)、ele(包裹容器)需被其他函数访问,因此也添加到作用域链末尾。
len=ele.getElementsByTagName("img").length
width=parseInt(getCSS(ele.getElementsByTagName("img")[0],"width"));
ele=$(eleSelec),其中eleSelec是包裹容器的选择器,例如.carousel。
4. 创建箭头函数 createArrow
创建一个向左箭头,绑定向左移动的事件处理函数;创建一个向右箭头,绑定向右移动的事件处理函数。
createArrow=function(){
var prev=document.createElement("div"),
next=document.createElement("div");
prev.appendChild(document.createTextNode("<"));
next.appendChild(document.createTextNode(">"));
prev.className="arrow prev";
next.className="arrow next";
container.appendChild(prev);
container.appendChild(next);
addClass(container,"hide");
$.add(next,"click",function(){
go("next");
});
$.add(prev,"click",function(){
go("prev");
});
}
container代表最外层容器,需被其他函数访问,因此也添加到作用域链末尾。
container=$(wrapSelec),其中wrapSelec是最外层容器的选择器,例如.carousel-box。
5. 创建按钮函数 createBtn
为每个按钮添加index属性以便状态更新,为按钮组添加类名以便样式设置和获取:
createBtn=function(){
var div=document.createElement("div"),
btns='';
for(var i=0;i<len;i++){
btns+='<a href="#" index="'+i+'"></a>';
}
div.innerHTML=btns;
addClass(div,"carousel-btn");
container.appendChild(div);
}
6. 轮播函数
根据配置(顺时针或逆时针)判断调用go("prev")还是go("next")。
若配置为循环,则递归调用自身;若不循环,则在完成一轮后停止。
因此需要引入方向变量、循环标志变量以及计数变量。
为此,向作用域链末尾添加四个变量:direction、loop、count、begin,其中begin用于清除定时器。
circle=function(){
count++;
if (loop||count<len) {
if (direction==="forward") {
go("next");
}else{
go("prev");
}
}
begin=setTimeout(arguments.callee,t);
}
7. 停止函数 stop
stop=function(){
clearTimeout(begin);
}
8. 初始化函数 init
首次使用时,创建按钮和箭头,并为按钮绑定click事件处理程序(获取点击按钮的index并点亮,切换至对应图片),然后根据配置的顺时针或逆时针方向展示相应的图片和按钮状态。
因此需向作用域链末尾添加一个变量,用于标记是否已完成初始化。
init=function(){
createBtn();
createArrow();
$.delegateTag(wrapSelec+" "+".carousel-btn","a","click",function(e,target){
$.prevent(e);
light(target.getAttribute("index"));
animate.liner(ele,"scrollLeft",target.getAttribute("index")*width);
});
$.add(container,"mouseenter",function(){
stop();
removeClass(container,"hide");
});
$.add(container,"mouseleave",function(){
addClass(container,"hide");
begin=setTimeout(circle,t);
});if (direction==="forward") {
light(0);
}else{
light(len-1);
ele.scrollLeft=width*(len-1);
}
haveStart=true;
}
9. 开始轮播函数 start
该函数作为外部接口,用于控制轮播方向、间隔时间和循环状态,并将计数器归零。
由于可能重复启动轮播,每次启动前需清除之前的定时器。
start=function(dir,th,lo){
stop();
count=0;
direction=dir;
t=th*1000;
loop=lo;
if (!haveStart) {
init();
}
begin=setTimeout(circle,t);
}
至此,所有必要函数编写完毕。若将这些函数和变量封装在一个函数中,并将外层容器和包裹容器的类名或ID传入,该函数返回一个包含start和stop方法的对象,即可使用此组件。
然而,上述实现仅支持单一实例,即一个页面只能有一个轮播组件。若需在同一页面使用多个轮播实例,不能将所有变量封装在单一函数中,而应将其放入对象中。每个实例拥有独立的变量,但共享一组方法。
此时,这些变量无法直接访问,必须通过对象属性访问,即使用this关键字。
这会导致一个问题:this指向调用时的环境。当在事件处理程序或定时器中访问这些变量时,直接使用this会指向错误的环境,因此需要创建闭包。
具体做法是:在能获取到正确this的时机,将其赋值给一个局部变量,然后在事件处理程序或定时器中访问该局部变量,从而获取正确的对象引用。
以init函数为例进行改造:
carouselProto.init=function(){
var that=this;
this.createBtn();
this.createArrow();
$.delegateTag(this.wrapSelec+" "+".carousel-btn","a","click",function(e,target){
$.prevent(e);
that.light(target.getAttribute("index"));
animate.liner(that.ele,"scrollLeft",target.getAttribute("index")*that.width);
});
$.add(this.container,"mouseenter",function(){
that.stop();
removeClass(that.container,"hide");
});
$.add(this.container,"mouseleave",function(){
addClass(that.container,"hide");
that.begin=setTimeout(function(){
that.circle();
},that.t);
});if (this.direction==="forward") {
this.light(0);
}else{
this.light(this.len-1);
this.ele.scrollLeft=this.width*(this.len-1);
}
this.haveStart=true;
};
改造后,即可创建多个实例,每个实例拥有独立的状态属性,并共享原型中的方法。
若采用原型继承方式,可创建一个对象作为实例的原型,并编写一个工厂函数来生成实例:
var carouselProto={};
//把上面那些方法给这个对象
carouselProto.light=...
carouselProto.go=...
carouselProto.stop=...
//创建实例对象函数
var carousel=function(eleSelec,wrapSelec){
var that=Object.create(carouselProto);
that.wrapSelec=wrapSelec;
that.ele=$(eleSelec);
that.container=$(wrapSelec);
that.len=that.ele.getElementsByTagName("img").length;
that.width=parseInt(getCSS(that.ele.getElementsByTagName("img")[0],"width"));
return that;
}
//创建实例,使用组件
var carousel1=carousel(".carousel",".carousel-box");
carousel1.start("forward",3,true);
var carousel2=carousel(".carousel2",".carousel-box2");
carousel2.start("backward",2,true);
性能优化方案
性能优化建议:
1. 当点击的按钮恰好是当前已高亮的按钮时,仍会调用light和animate.liner函数。可添加判断逻辑,若点击的是当前正确按钮,则跳过后续执行。
$.delegateTag(this.wrapSelec+" "+".carousel-btn","a","click",function(e,target){
$.prevent(e);
var index=target.getAttribute("index");
if (index===that.active.getAttribute("index")) {
return
}
that.light(index);
animate.liner(that.ele,"scrollLeft",target.getAttribute("index")*that.width);
});
2. 在图片切换动画执行期间,若用户点击按钮或箭头,会触发下一次动画,导致动画混乱、图片错位及性能下降。为防止此情况,可引入一个变量记录缓动动画是否正在执行,仅当动画未执行时,点击按钮或箭头才触发函数。
liner=function(ele,prop,next){
var speed=(next-ele[prop])/10,
i=0;
ele.animating=true;
(function(){
ele[prop]+=speed;
i++;
if (i<10) {
setTimeout(arguments.callee,60);
}else{
ele.animating=false;
}
})();
}
if (!this.ele.animating) {
this.light(nextIndex);
animate.liner(this.ele,"scrollLeft",nextPosition);
}
源码:源码下载demo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。