JavaScript原生图片轮播组件实现与源码解析

作者:纸飞机时间:2026-10-30 17:13:29浏览:538

本文详细解析如何使用原生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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章