2013

6

25

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

スポンサードリンク


スマホ用にフリック式のスライダーを自作してみようというコトで前回のエントリースマホでフリックとかループとか色々カスタマイズできるスライダー作ってやんよ!!!(前編)を書きましたが、そちらで作成したものがこちら
ですが作成段階で設計ミスと感じるポイントや改善点などがちらほらありましたんで、それを踏まえて今回はよりUIを意識した新設計Ver.を作成してみます。

前回のフリックスライダーの問題点と改善点

スライドの移動アルゴリズムが一方通行式なのでループ機能ON時の両端から逆側への移動アニメーションが視覚的に不自然
表示されている画像をリアルタイムで取得していないため、フリックイベント以外の機能との拡張性がとても悪い
リアルタイムの画像取得を利用してサムネイルに視覚的にわかりやすいページネーション効果を追加
新たに一定時間で切り替わるオートスライダー機能の実装
以上の改善・追加を主な仕様として作成します。

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

前回のデモスライダーの問題はなんと言っても雛形となるスライダーの設計が良くなかったかなと・・・
まずはこちらを拡張性を意識して作り直します。
HTMLの記述
<div id="sliderContainer">
<div id="flame">
<ul class="clearfix" id="slider">
	<li><img alt="" src="1枚目画像パス" width="300" height="381" /></li>
	<li><img alt="" src="2枚目画像パス" width="300" height="381" /></li>
	<li><img alt="" src="3枚目画像パス" width="300" height="381" /></li>
</ul>
<ul id="slideIcon">
	<li id="prev">«</li>
	<li id="next">»</li>
</ul>
</div>
<div id="btnPagination">
<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>
</div>
</div>
CSSの記述
#flame {
	width:300px;
	height:381px;
	position:relative;
	overflow:hidden;
	margin: 10px auto;
}

ul#slider {
	margin-left: 0;
	width: 900px;
	height: 381px;
	overflow: hidden;
	position: relative;
}

ul#slider li {
	float: left;
	width: 300px;
	position: absolute;
}

ul#slider li img{
	width: 100%;
}

ul#slideIcon{
	width:100%;
	height:60px;
	position:relative;
}

ul#slideIcon li{
	display:inline-block;
	width:60px;
	height:70px;
	position:absolute;
	text-indent:-9999px;
	float:left;
}

ul#slideIcon li#prev {
	left: 0;
	top: -240px;
	background:url(左アイコン画像パス) no-repeat 20% center;
}

ul#slideIcon li#next {
	right: 0;
	top: -240px;
	background:url(右アイコン画像パス) no-repeat 80% center;
}

ul.panelNavi {
    margin: 0 auto;
    width: 318px;
}

.panelNavi li {
    float: left;
    padding: 3px;
    text-align: center;
    width: 96px;
}

.panelNavi li a{
	display:block;
	margin:5px;
}

.clearfix:after {
    clear: both;
    content: ".";
    display: block;
    height: 0;
    visibility: hidden;
}
JavaScriptの記述
$(function () {
    //①レイアウト系フィールド設定
	var flickBox = $('#flame');
	var flickList = $('ul#slider');
	var flickListImg   = flickList.children('li');
	var imgListCount = flickListImg.length;
	var thumbnail = $('ul.panelNavi li');
	var prev = $('#prev');
	var next = $('#next');

    //②パラメーター系フィールド設定
	var slideSpeed = 500;
	var imgListWidth = 300;
	var imgListHeight = 381;
	var imgNum = 0;
	var slideCurrent = 0;
	var imgSize = flickListImg.width();

	//③フリック対象画像設置設定
	flickListImg.each(function(){
		$(this).css("margin-left", -imgSize);
		if(imgNum == slideCurrent){
			$(this).css("margin-left", "0");
		}
		imgNum++;
	});

	//④次項・前項クリック処理
	next.click(function(e){
		//e.preventDefault();
		slideImg(slideCurrent + 1);
	});

	prev.click(function(e){
		//e.preventDefault();
		slideImg(slideCurrent - 1);
	});

	//⑤サムネイルクリック処理
	thumbnail.click(function(e){
		//e.preventDefault();
		var thisNum = thumbnail.index(this);
		if(thisNum != slideCurrent){
			slideImg(thisNum);
		}
	});

	//⑥スライド移動ファンクション
	function slideImg(next){
		var pos;
		if(slideCurrent < next){
			pos = imgSize;
		}else{
			pos = -imgSize;
		}

		if(next == imgNum){
			next = 0;
		}else if(next == -1){
			next = (imgNum - 1);
		}

		//⑦スライドエフェクト設定
		flickListImg.eq(next).css("margin-left", pos).animate({marginLeft: "0"}, "fast");
		flickListImg.eq(slideCurrent).animate({marginLeft: -pos}, "fast");
		slideCurrent = next;
	}
});

①、②のフィールド値設定解説

基本的には前回と同じ指定をしてありますが、用意されている画像のカウントに使用するための変数imgNum
現在表示されている画像をリアルタイムで取得させるために使用する変数slideCurrentとスライド値を設定するためにし使用する変数imgSizeを追加しました。
(変数imgSizeに入る値は変数imgListWidthと同様の物ですが、混同させると後の処理でややこしくなるのであえて使い分けています。)

③フリック対象画像設置設定解説

前回の一番の設計ミスはフリックさせる画像を一列に並べてその敷地内を左右移動して見せているイメージで作ったため画像切り替えの際の移動がユーザーの視覚的にわかりにくかったため、新設計では用意した画像分をeachファンクションで存在する画像を自動取得しスライドコンテナへ配置させ、表示させる画像をmargin-leftプロパティを0で配置させて、それ以外を画像幅分のネガティブマージンで引っ込ませて隠すイメージです。

④次項・前項クリック処理解説

次項アイコンの変数nextと前項アイコンの変数prevをクリックで後のslideImgファンクションのトリガーイベントに移ります。
もしページャーアイコンをaタグとして使う方は、クリックしてページリンクが働かない様にここでe.preventDefault()というイベント処理の伝播のみで、その処理を終了させる関数を使用しますが、今回のデモではaタグは使用していないので無くても問題無い・・・ハズです。

⑤サムネイルクリック処理解説

こちらもスライダー同様に現在表示されている画像とリンクしやすい様に変数thisNumにクリックされた画像番号を条件比較し対象スライド移動をslideImgファンクションイベントとリンクさせられる様になっています。こちらは画像リンクの処理は別物なのでaタグは必須ですからe.preventDefault()記述を忘れない様にしましょう。

⑥、⑦スライド移動・表示設定解説

ページャー・アイコンから発動されたイベントはユーザー定義のslideImg関数内で現在の画像の枚数を取得し、前後に画像があるか?を各条件分岐し条件にあった変数imgNumを”+”、”-”を加え次の画像へ切り替えます。
しかしユーザーにはスライドしたら左右移動している様に見せなければならないので左右それぞれanimate関数を使って横移動させて切り替わってる様に見せてます。

新設計スライダーデモ

以上の設計にして作成した新型スライダーのデモがこちら
見た目は変わり映えしませんが、ループとサムネイルからの挙動がスマートになりました。やったね!!

タッチイベントの実装

雛形となるスライダーコンテンツができたので、また改めてタッチイベント(詳しい仕様は前回のエントリーを参照)
JavaScriptの追加記述
	var flickFlag = true;

	$(flickListImg).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.touchX = event.changedTouches[0].pageX;

			switch(true){
				case(this.touchX > (this.touchX + this.slideX)):
						//e.preventDefault();
						if(flickFlag == true){
							slideImg(slideCurrent + 1);
							flickFlag = false;
						}
						break;

				case(this.touchX < (this.touchX + this.slideX)):
						//e.preventDefault();
						if(flickFlag == true){
							slideImg(slideCurrent - 1);
							flickFlag = false;
						}
						break;
				}
		},

		'touchend': function(e) {
			flickFlag = true;
		}
	});

上記タッチイベントを実装させたデモがこちら

JavaScriptの追加記述解説

タッチイベントの記述は基本的に前回と変わりません。touchmoveイベント時の処理は変数this.touchXで触った画面位置から変数this.touchXと、移動した値が代入された変数this.slideX分の合計値が小さければページャーイベントと同様の右スライド処理。大きければ左スライド処理となります。

操作性の問題点の改善

ここで前回にはなかっった変数flickFlagに注目していただきたいのですが、これが何を意味するのか?
答えはこちら

変数flickFlagの役割

そうです!!フリックした際にスライド条件を充たす度に(正確には1pxでも移動すればtouchmoveイベントは新規更新され)スライド画像が2枚分、3枚分と飛び飛びで移動されてしまいます。
縦スクロールならばフリックの移動幅と比例して画面が大きく移動するのは問題ありませんが、フリックスライダーの場合は一つ一つの移動がユーザーの意思にそぐわない動きでは駄目ですが、フリック時の指の振り幅は個人差があるので移動幅を制限するわけにもいきません。

そこで、変数flickFlagを追加し一度でもフリック判定条件を充たしたら値をfalseで返し、その後の処理には引き継がせないフラグを立て、touchendイベントつまりは指を離したらまたフラグを戻しフリック判定を可能に戻す大事な変数なのでした。

縦スクロールの対応処置

前項のタッチイベント実装処理のデモをスマホの実機で触っていただいた方には思った通りに動かずイライラする点があったと思います。
前回作成したフリックスライダーとは違い、今回は画像全体が左右フリック判定領域となっているので縦スクロールが利きません。おいおい(-_-;)
JavaScriptの追加記述
//⑦スライドエフェクト設定
flickListImg.eq(next).css("margin-left", pos).stop(true, true).animate({marginLeft: "0"}, "fast");
flickListImg.eq(slideCurrent).stop(true, true).animate({marginLeft: -pos}, "fast");

//タッチイベント処理
$(flickListImg).bind({
		'touchstart': function(e) {
			this.touchX = event.changedTouches[0].pageX;
			this.slideX = flickList.position().left;

			this.touchY = event.changedTouches[0].pageY;
			this.slideY = flickList.position().top;
		},

		'touchmove': function(e) {
			this.slideX = this.slideX - (this.touchX - event.changedTouches[0].pageX );
			this.touchX = event.changedTouches[0].pageX;

			this.slideY = this.slideY - (this.touchY - event.changedTouches[0].pageY );
			this.touchY = event.changedTouches[0].pageY;

			slideMathX =  Math.abs(this.slideX);
			slideMathY =  Math.abs(this.slideY);

			if(slideMathX < slideMathY){ 				flickListImg.addEventListener('touchmove', scrollY); 			}else{ 				switch(true){	 					case(this.touchX > (this.touchX + this.slideX)):
							e.preventDefault();
							if(flickFlag == true){
								slideImg(slideCurrent + 1);
								flickFlag = false;
							}
							break;

					case(this.touchX < (this.touchX + this.slideX)):
							e.preventDefault();
							if(flickFlag == true){
								slideImg(slideCurrent - 1);
								flickFlag = false;
							}
							break;
					}
			};
		},

		'touchend': function(e) {
			flickFlag = true;
			flickListImg.removeEventListener('touchmove', scrollY);
		}
	});

JavaScriptの追加記述の解説

フリック判定が左右しかないので縦スクロールつまり上下のフリック座標を求めるためにtouchstartイベントとtouchmoveイベント内に横判定(X軸)と同様に縦判定変数this.touchYthis.slideYを追加し
  • 上フリック → マイナスY判定
  • 下フリック → プラスY判定
  • 右フリック → マイナスX判定
  • 左フリック → プラスX判定
をスライド判定の条件分岐にしたのですが、iPhoneでは何とか判定してもらえたのですが、
Androidの4.0以下では瞬間的な判定が厳しいらしく、上下左右を完全に意識した指操作でないと全然思い通り動作せずとても実用できる動作になりませんでした。

フリック判定の修正

まず縦フリックか横フリックかの判定をもっと単純にするためMath.abs()関数で値の絶対値だけを求め、指の移動値がXとYでどちらが大きいかで判定。
横フリック判定された場合は前項と同様の横スライド処理
縦フリック判定された場合は変数flickFlagと同様にaddEventListener()でイベントリスナを登録し、イベント名scrollYでイベントハンドラtouchmoveで実質は空のイベントを発生させる代わりにタッチイベントを中止させてスクロールを解除、touchend(指を離した)時にremoveEventListenerでイベントリスナを削除してタッチイベントを復帰させています。

その他にスライド時のアニメーションの処理にstop()関数の引き数にtrue, trueを指定し追加する事で現在処理中のスライドイベントが終了するまではキューを実行しない記述を追加し意図的に無茶苦茶な動作でバグを起こすユーザーに対抗してみました。

修正版デモ

前項の変数flickFlag同様、このイベント停止の対応に半ばノイローゼ気味に悩まされました。
以上の問題点を修正した完成形のデモがこちら

だいぶ長くなってしまったので、今回作成したスライダーのカスタマイズは次回で!!!
最後まで読んでくださった方、有難うございました。
フリックスライダーを自作しようという方に今回の制作過程が少しでも参考になればイイなぁ。

トップへ