この記事では、画像カルーセル コンポーネントの JavaScript 実装を紹介します。これ以上は説明せずに、次の内容を見てみましょう:
効果:
画像を自動的にループし、下に に切り替えるボタンがあります。対応する画像。
画像を切り替えるアニメーションを追加します。
マウスを画像上に置くとカルーセルが停止し、左右に2つの矢印が表示され、クリックすると画像が切り替わります。
マウスが画像領域から離れると、カルーセルは現在の位置から継続します。
カルーセルの方向、ループするかどうか、および間隔を設定するインターフェイスを提供します。
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>
は 2 つのボックスがネストされている必要があり、最も内側のボックスには 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. イージング関数ライナー
イージング関数の機能は、目標値に達するまでターゲット要素の属性値を少しずつ変更することです。これを使用する要素は、水平方向に回転した画像、垂直方向に回転した画像、またはページの左端からページの右端まで到達したい小さなボックスである可能性があります。したがって、4 つのパラメータ (対象の要素、変更する属性値、対象の値、移動数) を受け取る必要があります。
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=function(index){ removeClass(active,"active"); active=$(this.wrapSelec+" "+"[index="+index+"]"); addClass(active,"active"); }
3. 画像切り替え関数 go
は次のscrollLeft値を計算する必要があります:
左に移動している場合はscrollLeftを-600にする必要があり、既に0の場合は3000に切り替えます。 scrollLeft= ==0?width*(len-1):ele.scrollLeft-width;
右に移動する場合、scrollLeft は +600、つまり 0——>600,600——>1200 である必要があります。 ...,3000 ——>0。ここでは、上記のような判断を使用することも、次の式 = (cur + distance)%( distance * num) を使用することもできます。つまり、(ele.scrollLeft+width)%(width*len)
点灯させる次のボタンのインデックスを取得する必要があります:
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),eleSelect は.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"); }); }
给每个按钮添加一个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用于清除定时器。
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时,将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); }