2013

6

19

スマホでフリックとかループとか色々カスタマイズできるスライダー作ってやんよ!!!(前編)

スポンサードリンク


更新ご無沙汰してましたm(_ _)m べ、別にさぼってたワケじゃないです。

スマホサイトでスライダーを導入する際にプラグインを探してみて思ったんですが・・・
このプラグインのこの機能はイイんだけど、コレはできないんかい(#`Д´)ノ!!

ってな具合でそれぞれ長所短所があってなかなかBESTでMUSTなスライダーってないモンだなと思い(リサーチが足りないだけかも)
そもそもスライダーって難しいモノなのかな?と思ったので、どうせなら自分で欲しい機能を実装できるスライダーを自作してみようかと思います。

フリックスライダーの仕様設計と予備知識

今回スライダーに実装させたい機能

色々スライダープラグインを見て「この機能は欲しいな」と思ったモノは入れたいので

フリック対応機能

ON/OFF切り替えができるループ機能

タップしてもスライドさせられるページャー機能

サムネイル画像からもスライドできる機能

この辺の機能を実装させたいと思います。

※あくまで簡易的に『作ってみた』系のノリなので、細かいエラーやバグ等は考えずにあくまで実験的に作成しますのであしからず

スマホ関連で用意されている主要イベント関数

マウスがないんでonClickmousehoverもできやしない・・・
そもそもスマホでのイベントを起こすにはどうするか?

touchstart関数

スクリーンパネルに指が触れた場合に発生するイベント関数

touchmove関数

スクリーンパネルをフリックさせた場合に発生するイベント関数

touchend関数

スクリーンパネルから指が離れた場合に発生するイベント関数

touchcancel関数

画面回転などの処理によってタッチイベントがキャンセルされた場合に発生するイベント関数

上記の様なタッチパネル用のイベント関数が用意されています。ありがたや・・・
で、今回作成するフリックスライダーの原理としては
  • 1.フリックスライダーを動かすために画面に触れる処理(touchstart
  • 2.現在表示している画像を変更させるために左右どちらかにスライドさせる処理(touchmove
  • 3.フリック後に画面から指を離したら該当の画像が定位置に納まる処理(touchend
の関数を用いれば動作させる事ができるので上記3点の関数を使って認識処理を行ってみる。

タッチイベントのデモ

上記の各イベント処理を確認できるデモがこちら
お手持ちのスマホ実機で確認して下さい。

$(function(){
    $("div").bind({
        //①タッチ開始イベント
	'touchstart' : function(e){
		$("#hoge").text("タッチされました!!(゚∀゚)");
	},
        //②フリック中イベント
	'touchmove' : function(e){
		$("#hoge").text("フリック移動中!!(゚Д゚≡゚Д゚)");
	},
        //③タッチ終了イベント
	'touchend' : function(e){
		$("#hoge").text("タッチ終了(´・ω・`)");
	}
    })
});
  • ①ボックス内をタッチした場合<p id=”hoge”>内に『タッチされました!!(゚∀゚)』とテキストが表示されます。
  • ②ボックス内でタッチしたまま指を移動した場合<p id=”hoge”>内に『フリック移動中!!(゚Д゚≡゚Д゚)』とテキストが表示されます。
  • ③ボックス内から指を離した場合<p id=”hoge”>内に『タッチ終了(´・ω・`)』とテキストが表示されます。
要は雛形のスライダーを作りコンテンツ内にこのスクリプトと各イベントファンクション内に処理を記述していけば良いハズ!!

スライダーコンテンツの作成

フリック処理ができてもスライダーを作らない事には先に進まないので急ぎ足で作成。

クリック式スライダーデモ

フリックイベントを実装するための雛形スラーダーのデモがこちら

HTMLの記述

<div id="flame">
<ul class="clearfix" id="slider">
	<li><img alt="" src="画像一枚目" width="300" height="381" /></li>
	<li><img alt="" src="画像二枚目" width="300" height="381" /></li>
	<li><img alt="" src="画像三枚目" width="300" height="381" /></li>
</ul>
<p id="prev">&laquo;</p>
<p id="next">&raquo;</p>
</div>

CSSの記述

#flame {
    width:300px;
    height:381px;
    position:relative;
    overflow:hidden;
    margin: 10px auto;
}
ul#slider {
    position:absolute;
    z-index:0;
    background:#999;
    width:900px;
}
ul#slider li {
    float:left;
    width:300px;
    height:381px;
    background:#333;
    list-style:none;
}
ul#slider li img{
    width: 100%;
}
.clearfix:after {
    clear: both;
    content: ".";
    display: block;
    height: 0;
    visibility: hidden;
}
p#prev, p#next {
	position:absolute;
	top:30%;
	width:60px;
	height:100px;
	text-indent:-9999px;
	display:block;
	z-index:100;
}
p#prev {
	left:0;
	background:url(前項アイコン画像のパス) no-repeat left center;
}
p#next {
	right:0;
	background:url(次項アイコン画像のパス) no-repeat right center;
}

JavaScriptの記述

$(function(){
    var flickBox = $('#flame');    //①
    var flickList = $('ul#slider');    //②
    var flickListImg   = flickList.children('li');    //③
    var imgListCount = flickListImg.length;    //④
    var imgListWidth = 300;    //⑤
    var imgListHeight = 381;    //⑥
    var slideLoop = true;    //⑦
    var next = $('p#next');    //⑧
    var prev = $('p#prev');    //⑨
    var slideSpeed = 300;    //⑩
    var imgListCotainer = imgListWidth * imgListCount;    //⑪

    prev.click(function () {    //⑫
	var offset = flickList.position().left;    //⑬
	switch (true){
	    case (offset == 0):
		if(slideLoop == true){
		    flickList.animate({left: -(imgListWidth * 2) },slideSpeed);    //⑭
		}else{
		    flickList.animate({left: 100 },100).animate({left: 0 },slideSpeed);    //⑮
		 }
	    break;
	    case (offset == -imgListWidth):
		flickList.animate({left: 0 },slideSpeed);
	    break;
	    case (offset == -(imgListWidth * 2)):
		flickList.animate({left: -imgListWidth },slideSpeed);
	    break;
	}
    });

    next.click(function () {    //⑯
	var offset = flickList.position().left;
	switch (true){
	    case (offset == 0):
		flickList.animate({left: -imgListWidth },slideSpeed);
		break;
	    case (offset == -imgListWidth):
		flickList.animate({left: -(imgListWidth * 2) },slideSpeed);
            break;
	    case (offset == -(imgListWidth * 2)):
		if(slideLoop == true){
		    flickList.animate({left: 0 },slideSpeed);
		}else{
		    flickList.animate({left: -(imgListWidth * 2 + 100) },100).animate({left: -600 },slideSpeed);
		}
	    break;
	}
    });
});

フィールド値の設定解説

  • ①スライダーコンテンツ要素をflickBox変数に代入
  • ②スライダー画像の収納要素をflickList変数に代入
  • ③スライダー画像要素をflickListImg変数に代入
  • ④スライダー画像の枚数を取得しimgListCount変数に代入
  • ⑤スライダー画像の横幅をimgListWidth変数に代入
  • ⑥スライダー画像の縦幅をimgListHeight変数に代入
  • ⑦スライダーのループ機能のON/OFF(true / false)をslideLoop変数に代入
    (デモページの切り替えボタンで機能の切り替え可能)
  • ⑧次項ページャー要素をnext変数に代入
  • ⑨前項ページャー要素をprev変数に代入
  • ⑩アニメーションのスピード設定(1秒 = 1000)をslideSpeed変数に代入
  • ⑪flickList(②)の横幅を設定し、imgListCotainer変数に代入

ファンクション処理の設定解説

  • prev(⑨)をクリックされた処理
  • flickList(②)内の現在の左からの場所を所得
  • ⑭ループ機能がONの場合、画像2枚分(imgListWidth * 2)のpxをアニメーション移動しスライド
  • ⑮ループ機能がOFFの場合、ユーザーの視覚的にわかりやすい様に一瞬跳ね返りアニメーション後停止
  • next(⑧)をクリックされた処理
こちらのスライダーを元にフリックイベントを導入していきます。
画像を増やしたい場合はswitchのcase文を追加しスライドさせるpxを画像幅(imgListWidth)に移動させたい枚数の数を乗算させてください。
ループ機能をOFFにした場合の跳ね返りアニメーションもとりあえず100pxだけ跳ねさせていますが、こちらもお好みで変更させて下さい。

フリックイベントの実装

さて、ようやく本題に入りますが・・・前項で作成したスライダーに先に述べたタッチイベントを元にフリック処理を実装したデモがこちら

javascript

$(function () {
    var flickBox = $('#flame');
    var flickList = $('ul#slider');
    var flickListImg   = flickList.children('li');
    var imgListCount = flickListImg.length;
    var slideLoop = true;
    var slideSpeed = 500;
    var prev = $('p#prev');
    var next = $('p#next');

    //①フリック画像初期レイアウト設定
    var imgListWidth = 300;
    var imgListHeight = 381;
    var imgListCotainer = imgListWidth * imgListCount;
    var slideDecision = imgListWidth / 2;
    var slideLimit = imgListCotainer - imgListWidth;

    $(flickList).bind({
	//②タッチ開始イベント処理
       'touchstart': function(e) {
	    this.touchX = event.changedTouches[0].pageX;
	    this.slideX = flickList.position().left;
	},
     //③フリックイベント処理
	'touchmove': function(e) {
	    this.slideX = this.slideX - (this.touchX - event.changedTouches[0].pageX );
	    this.accelX = (event.changedTouches[0].pageX - this.touchX) * 5;
	    this.touchX = event.changedTouches[0].pageX;
	    $(this).css("left", this.slideX);
	},
    //④タッチ終了イベント処理
	'touchend': function(e) {
	    if(this.accelX > slideDecision){
		this.accelX = slideDecision;
	    }
	    if(this.accelX < -slideDecision){ 	        this.accelX = -slideDecision; 	    } 	    this.slideX += this.accelX; 	    this.accelX = 0; 			 	    //⑤左の端までフリックした場合の跳ね返り 	    if(this.slideX > 0) {
		this.slideX = 0;
		if(slideLoop == true){
		    $(this).animate({left: -((imgListWidth * imgListCount) - imgListWidth) },slideSpeed);
	    }else{
		    $(this).animate({left:this.slideX},slideSpeed);
	    };
	    //⑥右の端までフリックした場合の跳ね返り
	    }else if(this.slideX < -slideLimit - 100) { 		if(slideLoop == true){ 		    $(this).animate({left: 0 },slideSpeed); 	    }else{ 		    this.slideX = -slideLimit; 		    $(this).animate({left:this.slideX},slideSpeed); 	    }         //⑦スライドの慣性エフェクト 	    }else{ 		    var edge = this.slideX % imgListWidth; 		    if(edge > -(imgListWidth / 3)){
		        this.slideX -= edge;
		        $(this).animate({left:this.slideX},100,"linear");
		}else{
		    this.slideX = this.slideX - edge - imgListWidth;
		    $(this).animate({left:this.slideX},100,"linear");
		}
	}
	}
    });
});

イベント処理の解説

タッチイベントの取得や基本的な使い方などは『Developer’s Blog』様のこちらのエントリーを参考にさせていただきました。大変勉強になりましたm(_ _)m

①フリック画像初期レイアウト設定

  • 1.変数imgListWidthにフリック画像の横幅の値を代入
  • 2.変数imgListHeightにフリック画像の縦幅の値を代入
  • 3.変数imgListCotainerにフリック画像の横幅×画像枚数の値を代入
  • 4.変数slideDecisionにフリック画像の横幅の半分の値を代入
  • 5.変数slideLimitに変数imgListCotainerからフリック画像1枚分の横幅の値を引いた値を代入

②タッチ開始イベント処理

  • 1.画面をタッチした横座標はイベントハンドラ関数changedTouchesプロパティのpageXメソッドの配列内に格納された値を変数this.slideXに代入
  • 2.変数flickListを相対位置としたときにタッチされた座標を比較した値を変数this.slideXに代入

③フリックイベント処理

  • 1.変数this.touchXで取得した座標からtouchstartイベント内でで比較した変数this.slideXを差し引いた値が新たにtouchmoveイベントの変数this.slideXに代入され、触った位置から指がどれだけ移動したかを示す値となる。
  • 2.touchmoveイベント発生時の変数this.touchXの座標と、ひとつ前のtouchmove発生時の指の位置の差の値を移動速度として変数this.accelXに代入
  • 3.上記の変数this.slideXの値をcssのleftプロパティのパラメーターに書き換え画像を移動させる

④タッチ終了イベント処理

  • 1.フリックと画像切り替えの判定値を求めるために、変数this.accelXと画像横幅の半分の値を代入した変数slideDecisionを比較し、変数this.accelXに変数slideDecisionの値を新たに左なら”+”、右なら”-”演算子を加えた値としてthis.accelXに代入
  • 2.変数this.slideXに変数this.accelXを追加した値を代入後、変数this.accelXの値を0にする。

⑤左の端までフリックした場合の跳ね返り

  • 1.現時点の変数this.slideXの値が0より大きい(1枚目の画像位置より前へフリックされた)場合、変数this.slideXの値を0(1枚目の画像の定位置に戻す)にする。
  • 2.ここでスライダーのループ機能がONに設定されているの場合、最後の画像へ移動させるために画像横幅分×画像の枚数分の値の移動をしたいが、そのままの値に移動してしまうと表示できる画像がなくなってしまうため画像1枚分の値は差し引く。(変数flickBoxのcssのoverflow:hiddenを切ると視覚的にわかりやすと思います。)
  • 3.ループ機能がOFF設定の場合は変数this.slideXの値が0なので1枚目の画像の定位置で固定

⑥右の端までフリックした場合の跳ね返り

  • 1.現時点の変数this.slideXよりも最後の画像の表示位置までの値の変数slideLimitが大きい場合の条件分岐
  • 2.スライダーのループ機能がONに設定されている場合、1枚目の画像の定位置まで移動
  • 3.ループ機能がOFF設定の場合、変数this.slideXを変数slideLimitに代入し、値が変更された変数this.slideXまで移動する

⑦スライドの慣性エフェクト

  • 1.このままでは指で移動した値分しかスライドされずに思う様な動きになりませんので、フリック動作をスムーズにさせるために慣性エフェクトの処理を追加させます。
  • 2.変数imgListWidthから変数this.slideXで除算した値を変数edgeに代入
  • 3.スライダーの画像切り替えを判定するフリック距離の値を画像の1/3に設定(任意で変更可)を変数edgeと比較
  • 4.変数edgeが画像切り替え判定値より大きくフリックした場合、変数edgeの値を差し引き変数this.slideXに代入して左フリック用の切り替えアニメーション(デモでは0.1秒に設定)
  • 5.条件外の場合は上記と同条件の変数this.slideXから画像1枚分の値を加えた値分の右フリック用の切り替えアニメーション(デモでは0.1秒に設定)

サムネイル機能の追加

これでページャーアイコンからもフリックアクションからも画像が切り替えられる様になりました。後はサムネイルからも切り替えができるための機能を追加します。デモはこちら

HTMLの追加

</pre>
<ul class="panelNavi clearfix">
	<li><a href="#"><img alt="" src="1枚目の画像" width="96" height="122" /></a></li>
	<li><a href="#"><img alt="" src="2枚目の画像" width="96" height="122" /></a></li>
	<li><a href="#"><img alt="" src="3枚目の画像" width="96" height="122" /></a></li>
</ul>
<pre>

CSSの追加

ul.panelNavi {
    margin: 0 auto;
    width: 318px;
}
.clearfix:after {
    clear: both;
    content: ".";
    display: block;
    height: 0;
    visibility: hidden;
}
.panelNavi li {
    float: left;
    padding: 3px;
    text-align: center;
    width: 100px;
}
.panelNavi li a{
	display:block;
	margin:2px;
}

.panelNavi li a.imgHover{
	border:#4ae 2px solid;
}
.panelNavi li a.imgNoHover{
	border:#FFF 2px solid;
}

JavaScriptの追加

	thumbnail.click(function(){
		var index = thumbnail.index(this);
			var i=0;
			while (i <= index){
					flickList.animate({left: -(imgListWidth * i) },slideSpeed);
					i=i+1;
		}
	});

以上、フリック対応機能、ON/OFF切り替えができるループ機能、タップしてもスライドさせられるページャー機能、サムネイル画像からもスライドできる機能を加えたフリックスライダーのテスト版ができました。
あくまでロジックの把握を兼ねてテスト版で自作したものなので、動作のエラー等はご愛嬌というコトで・・・。

今回たくさん勉強になりましたが、記事の執筆中に色々と回収が必要なところが出てきて違う仕様を思いついたので、近日中に改良版を公開できればと思います。
乞うご期待!!!・・・はしなくて大丈夫です。

トップへ