POPSブログ

jQueryで画像を分割して表示する

192

  Category:  jquery2013/01/11 pops 

jQueryで、画像を分割して、delay()を利用して表示パターンを作り、エフェクト処理を行ってみます。簡単なサンプルでその仕組みと処理過程をみてみます。

 

jQueryで画像を分割して表示する

前ページの画像読み込み表示の構造を利用してみます。上層のBOXの中に分割したBOX要素(スライス要素)を挿入して構成します。画像分割の仕組みは非常に簡単です。


CSS3 transition()処理の回転について

通常jQueryでは回転は出来ませんが、プラグインである jquery.transit.js で、CSS3 transition()処理が可能になります。
現在はセキュリティの観点から「モダンブラウザ」にほぼ移行して来ましたので、CSS3 transition処理の「画像分割アニメーション」を作ってみました。まだ未対応のブラウザもありますが、CSS3処理のため軽く動作(格段の差有り)するのが特徴です。
将来的にはCSS3処理をせざるを得ないと思いますので、興味のある方は下記ページを参照ください。2015/07/29 追加

【参照】当方の記事: jQuery CSS3 transit.js使用の画像分割アニメーション


分割した要素に「背景画像」を挿入する


事前に「DIV要素BOX」を配置しておくと仮定すると、以下のようにCSSをJSで設定しますと画像が分割されます。
各々の「DIV要素BOX」の大きさの範囲で画像が表示されて、「背景画像」ですから範囲外は非表示になる仕組みです。画像<img>ならCSSで overflow:hiddin にすれば宜しい訳です。


「DIV要素」の背景ポジション値 (backgroundPosition) をマイナスにするだけです。つまりマイナス方向に移動させる。例では「背景画像」ですが、画像<img>であっても同じです(画像 position をマイナス補正)。
一般的には動かした時「背景画像」処理の方が軽くなりますから、こちらが使用されるのが多い様です。



HTML
<div>
[分割した要素][分割した要素][分割した要素]..............
</div>

分割した要素をポジションで並べて配置します
分割要素が2-4個なら直接htmlに書いてもよいが通常はJSで作ることが多い

分割した要素にjqueryで背景画像挿入
var slice_url=画像URL;

$("分割した要素").css({'background-image':'url('+ slice_url + ')'});
$("分割した要素").css({'backgroundPosition':(要素のXポジション値 * -1) + 'px ' + (要素のYポジション値 * -1) + 'px'});

以下、サンプルを元に、画像を分割して表示するJSの詳細を説明します。

注意、多少重くなりますので、マシン性能が良くない場合は、Chrome でご覧ください。


画像読み込み、分割表示 サンプル


Example

エフェクト中はボタンを押しても無効です。画像をロード出来ない場合「Loading画像」を「クリック」すれば解除されます。


  • PHOTO1
  • PHOTO2
  • PHOTO3
  • PHOTO4


 

画像分割エフェクトについて


サンプルは説明し易いように単純な構成にしています。


説明図


図のように、フェード表示用の作業層(#top-box)を利用して、画像分割したBOX要素を append して構成します。


HTMLの構成

下記のような構造があったと仮定して(サンプル)、#top-box階層に分割した画像を挿入するものとします。画像が食み出しても影響のないように、#top-boxは overflow:hidden にします。


<ul id="tab-menu">
	<li text="/main/images/toyota_car10.jpg">PHOTO1</li>
	<li text="/main/images/toyota_car11.jpg">PHOTO2</li>
	<li text="/main/images/toyota_car12.jpg">PHOTO3</li>
	<li text="/main/images/toyota_car13.jpg">PHOTO4</li>
</ul>
<div id="image-box-base">
	<div id="image-box"><img /></div>
	<div id="top-box"></div>
	<div id="loading"></div>
</div>

画像URLは当方の場合です。使用者の環境にあわせます。


サンプル JS CSS


JS imgload04.js


//imgload04.js
//日本語
//スライス

(function($){

	$(function () {

		//設定
		var select_no=0;//最初に開くメニューの位置
		var speed=600;//フェード速度
		var delay_speed = 100;//delay基準時間 100-200

		var box_W=640;//画像BOX幅
		var box_H=300;//画像BOX高さ

		var split_h=9;//横分割数
		var split_v=5;//縦分割数

		//------------------------------------------------------------

		var split_total=split_h*split_v;//分割合計
		var no=0;
		var keep_no=select_no;
		var img_url="";
		var load_url="";
		var keep_url="";

		//chip sliceオブジェクト
		var chipboxs = [];
		//chipの位置、大きさ保存容器
		var chip_pos_X = [],chip_pos_Y = [],chip_W = [],chip_H = [];
		//スライス変数
		var slice_W = 0,slice_H = 0,slice_Wb = 0,slice_Hb = 0;

		var sliceflag=0;
		var anime_move=false;

		//外枠jqueryオブジェクト
		var tabmenu=$("#tab-menu");
		var boxbase=$("#image-box-base");
		var imagebox=$("#image-box");
		var topbox=$("#top-box");
		var loading=$("#loading");

		//上層BOX欄外移動
		topbox.css({'left':box_W});
		//loadingを一旦けす
		loading.css({'display':'none'});
		//指定場所のOPEN
		tabmenu.find("li").eq(select_no).addClass("active");
		img_url=tabmenu.find("li").eq(select_no).attr('text');
		//指定画像を表示
		imagebox.children("img").attr({'src':img_url});
		//変数更新
		load_url=keep_url=img_url;

		//init、分割要素を作る
		init();

		//------------------------------------------------------------

		//init分割要素を作る
		function init() {

			//スライス要素数、分割合計
			var roop_v=split_total;

			//スライス要素を作る
			var sliceboxs="";
			for (var i=0; i < roop_v; i++) {
				sliceboxs += '<div id="'+ 'slice'+ i + '" class="slice_bk"></div>';
			}
			//append #top-box
 			$('#top-box').append(sliceboxs);

			//スライス要素にID名をつけてオブジェクト保存
			for (var i=0; i < roop_v; i++) {
				chipboxs[i] = $("#slice"+i);
			}

			//スライス要素の位置大きさ計算
			slice_set();

		}

		//スライス要素の位置大きさ決定、保存
		function slice_set() {

			//スライスの寸法
			slice_W=Math.round(box_W/split_h);//YOKO
			slice_H=Math.round(box_H/split_v);//TATE
			//等分にならない場合の寸法
			slice_Wb=box_W - slice_W * (split_h - 1);
			slice_Hb=box_H - slice_H * (split_v - 1);

			var k = 0;
			for (var i=0; i < split_h; i++) {
				for (var j=0; j < split_v; j++) {
					//大きさ保存
					chip_W[k]=slice_W;
					if (i == split_h - 1) {chip_W[k]=slice_Wb;}
					chip_H[k]=slice_H;
					if (j == split_v - 1) {chip_H[k]=slice_Hb;}
					//位置保存
					chip_pos_X[k]=slice_W * i;
					chip_pos_Y[k]=slice_H * j;

					k ++;
				}
			}
		}


		//メニューclick-action
		tabmenu.find("li").click(function() {

			//アニメ中はボタンを受け付けない
			if(anime_move) {return false}
			//表示中の場合も受け付けない
			no=tabmenu.find("li").index(this);
			if(no == keep_no) {return false}
			//active処理
			tabmenu.find("li").removeClass("active");
			$(this).addClass("active");

			//画像URL取得、画像の読み込み
			load_url=$(this).attr('text');
			imgload ();
			return false;

		});

		//loading-click-action
		loading.click(function() {

			//ローデング画像クリック処理
			loading.css({'display':'none'});
			//menuを前に戻す
			tabmenu.find("li").removeClass("active");
			tabmenu.find("li").eq(keep_no).addClass("active");

			return false;
		});


		//画像Preloader
		function imgload () {

			loading.css({'display':'block'});
			//imgPreloaderを作成
			 var imgPreloader=new Image();
			//イベントハンドラ
			imgPreloader.onload=function() {

				loading.css({'display':'none'});
				//画像表示に進む
				show_image();

			}
			//重要、最後に書く
			imgPreloader.src=load_url;
		}

		//画像表示処理
		function show_image() {

			//アニメ中を判定
			anime_move=true;

			//上層BOX欄外移動
			topbox.css({'left':box_W,'display':'block'});
			//slice要素を縮小、見えなくする
			$(".slice_bk").css({'top':0,'left':0,'width':0,'height':0});
			//上層BOX原点に戻す
			topbox.css({'left':0});
			//新画像URL
			var slice_url=load_url;
			//スライスカウントフラグ、クリア
			sliceflag=0;

			//処理ループ
			var k = 0;
			for (var i=0; i < split_h; i++) {
				for (var j=0; j < split_v; j++) {

					//背景画像挿入
					chipboxs[k].css({'background-image':'url('+ slice_url + ')'});
					chipboxs[k].css({'backgroundPosition':(chip_pos_X[k] * -1) + 'px ' + (chip_pos_Y[k] * -1) + 'px'});

					//初期の位置大きさ透明度など設定
					chipboxs[k].css({'left':chip_pos_X[k],'top':chip_pos_Y[k],'width':chip_W[k],'height':chip_H[k],'opacity':0});

					//遅延値delayを設定する
					var delay_v=(i+j) * delay_speed;

					//アニメ実行 IN
					chipboxs[k].delay(delay_v).animate({'left':chip_pos_X[k],'top':chip_pos_Y[k],'width':chip_W[k],'height':chip_H[k],'opacity':1},speed,function() {

						sliceflag ++;
						//アニメ終了処理に進む
						if (sliceflag == split_total) {slice_image_parts();}
					});

					k ++;
				}
			}


		}

		//アニメ終了処理
		function slice_image_parts() {

			//スライスフラグ
			sliceflag=0;
			//下層画像を取り替える
			img_url=load_url;
			imagebox.children("img").attr({'src':img_url}).css({'display':'block'});
			//上層を隠す
			topbox.css({'left':box_W,'display':'none'});

			//変数更新
			keep_url=load_url;
			keep_no=no;
			//アニメ終了
			anime_move = false;

		};

	});

})(jQuery);


CSS imgload04.css


/* imgload04.css 日本語 */

ul#tab-menu {
display:inline-block;
list-style:none;
width:100%;
height:20px;
margin:0;
padding:0;
}
#tab-menu li {
display:inline;
list-style:none;
width:60px;
height:20px;
line-height:20px;
font-size:11px;
font-weight:bold;
text-align:center;
margin-right:2px;
border-top-left-radius:5px;
border-top-right-radius:5px;
-moz-border-radius-topleft:5px;
-moz-border-radius-topright:5px;
-webkit-border-radius-top-left:5px;
-webkit-border-radius-top-right:5px;
background-color:#888888;
float:left;
cursor:pointer;/*default*/
}
#tab-menu li.active {
background-color:#EEEEEE;
}
#tab-menu li:hover {
color:#FFFFFF;
background-color:#FF1493;
}

/* image-box-base */
#image-box-base {
position:relative;
top:0;left:0;
display:block;
width:640px;
height:300px;
padding:0;
margin:0;
background-color:#000000;
overflow:hidden;
}

/* image-box */
#image-box {
position:absolute;
top:0;left:0;
display:block;
width:640px;
height:300px;
padding:0;
margin:0;
background-color:#000000;
}
#image-box img {
width:100%;
height:100%;
}

#top-box {
position:absolute;
top:0;left:0;
display:block;
width:640px;
height:300px;
padding:0;
margin:0;
}

#top-box .slice_bk {
position:absolute;
background-position: 0 0;
top:0;left:0;
overflow:hidden;
}
#top-box .slice_bk img {
position:absolute;
top:0;left:0;
}

/* loading */
#image-box-base #loading {
display:block;
position:absolute;
top:0;left:0;
width:100%;height:100%;
background:url("/main/images/loading.gif") no-repeat center center;
background-color:transparent;
}




簡単な画像分割の説明


説明図


1枚画像から、任意の分割でのスライドから、パターンを作ってのエフェクトなど、色々な形態のアニメが可能になります。また、途中で分割を変えて表示することも可能です。
Flashでの処理とほぼ同じで、同じ様な効果がえられます。(回転できないなど、多少の違いはある、、、)
テキスト(JS)の編集だけで出来ますので、Flash拠り簡単に設置できるのが強みです。


1. 画像は「背景画像」に埋め込んだ方が動作が軽い。
2. 画像<img>埋め込みと比較して、エフェクトは少ないが、あまり問題は無い。
3. スライス要素の拡大は、表示範囲が変わるだけだが、見た目には拡大しているように感じる。
4. 画像の大きさは、全て全体のサイズと同じものを使用します。「背景画像」埋め込みですから大きい場合は食み出し分カットされます。反対に小さい場合は其の部分は表示されません。「背景画像」は表示サイズ調整はできません。box_W x box_H の大きさ
5. 動作がマシン環境、性能に左右される。最大分割数は最大100前後までがベター。マシン環境が良くなればこの限りではない。


サンプルはスライドしない単純な動き

このサンプルは、処理の概略説明用ですので、スライドさせないで、その場で分割要素を透過処理させているだけです。Flashの動きと似通った動きになるよう工夫しています。スライドさせない場合は(分割したその位置で)、回転は出来ませんので、拡大、透過の選択しかありません。


分割された画像の個々の表示を時間差で制御する

分割した時点で、1枚画像にまとめる時の「各分割画像」の「位置と大きさ」は決定されています。delay()を利用して時間差で規則性の有る動作パターンを作り、一旦移動したりして元の位置(分割した時の位置)にもどします。
少しわかり難いと思いますが、「時間差で表示する」この点をよく理解して、自由に動作パターンを作れる様になる事がポイントになります。


1. 各分割画像の、大きさ、スライド、透明度変化はアニメ animate() で制御されます。
2. 各分割画像の出現タイミングは delay() で制御されます。
3. アレンジは、各分割画像の初期移動位置、初期の大きさ、から元に戻る変化になります。
4. 動作(表示)パターンとは、右から、下から、中央から、外側から、交互になどを言います。計算式は自分で作る外ありません。


内部処理の簡単な説明


スライス画像BOX(分割要素)を作る(背景画像用)

分割画像(背景画像)を挿入するための、BOXを作ります。個数は縦横の分割数で決まりますが、スクリプトで縦横の分割を変化させる場合は、より多くのBOXを作成してもかまいません。その都度分割数に応じ必要な数だけ使用すれば良い。(使用しない要素は、大きさ 0 にしておけば見えない)


//init分割要素を作る
function init() {

	//スライス要素数、分割合計
	var roop_v=split_total;

	//スライス要素を作る
	var sliceboxs="";
	for (var i=0; i < roop_v; i++) {
		sliceboxs += '<div id="'+ 'slice'+ i + '" class="slice_bk"></div>';
	}
	//append #top-box
 	$('#top-box').append(sliceboxs);

	//スライス要素にID名をつけてオブジェクト保存
	for (var i=0; i < roop_v; i++) {
		chipboxs[i] = $("#slice"+i);
	}

	略

}

ID名、#slice0 #slice1 #slice2..........、クラス名 slice_bk のBOXが作成され、#top-boxにappendされます。
変数配列、chipboxs[n] にjqueryオブジェクトとして保存されます。後からのアクセスが速くなります。この時点で位置、大きさ、は設定されません。


スライス画像BOX(分割要素)の位置、大きさを設定する

縦横の分割数に応じて、位置と大きさを決定します。画像表示処理から呼び出せば、其の都度分割を変更できます。(多少スクリプトの変更は必要)


//スライス要素の位置大きさ決定、保存
function slice_set() {

	//スライスの寸法
	slice_W=Math.round(box_W/split_h);//YOKO
	slice_H=Math.round(box_H/split_v);//TATE
	//等分にならない場合の寸法
	slice_Wb=box_W - slice_W * (split_h - 1);
	slice_Hb=box_H - slice_H * (split_v - 1);

	var k = 0;
	for (var i=0; i < split_h; i++) {
		for (var j=0; j < split_v; j++) {
			//大きさ保存
			chip_W[k]=slice_W;
			if (i == split_h - 1) {chip_W[k]=slice_Wb;}
			chip_H[k]=slice_H;
			if (j == split_v - 1) {chip_H[k]=slice_Hb;}
			//位置保存
			chip_pos_X[k]=slice_W * i;
			chip_pos_Y[k]=slice_H * j;

			k ++;
		}
	}
}

位置、大きさは整数化します。Math.round の方が分割はキレイになります。右、下の画像BOXの大きさが違いますが見た目ではわかりません。割り切れる場合は全て同じ寸法になります。


基本的な処理ループ

原則、下記のループを基本的な処理を行うのに使用します。1つのループで処理する場合は簡単な処理しか出来ません。細やかな処理の場合は複数のループを使用したほうがベターです。


var k = 0;
for (var i=0; i < split_h; i++) {
	for (var j=0; j < split_v; j++) {

		//各種処理の実行

		k ++;
	}
}

背景画像を挿入

スライス画像BOXの位置、大きさを設定して、背景画像を挿入します。画像BOXの位置、大きさは事前に計算済みですから、配列を利用してCSSで確定します。
背景画像ポジションは画像BOXの位置をマイナス方向に移動させるだけです。


//背景画像挿入
chipboxs[k].css({'background-image':'url('+ slice_url + ')'});
chipboxs[k].css({'backgroundPosition':(chip_pos_X[k] * -1) + 'px ' + (chip_pos_Y[k] * -1) + 'px'});

スライス画像BOXのラップである #top-box にキレイに収まった状態です。「IN」「OUT」の区別があるならば、アニメで移動先位置を指定すれば「OUT」のアニメが行える状態です。


アニメを実行させるため移動などを行う(IN)

アニメを実行させるため移動させる、または大きさなどを修正します。上記のサンプルは透明度だけの変更です。


//初期の位置大きさ透明度など設定、下記は透明度だけ
chipboxs[k].css({'left':chip_pos_X[k],'top':chip_pos_Y[k],'width':chip_W[k],'height':chip_H[k],'opacity':0});

//大きさを違わせる、拡大する
chipboxs[k].css({'left':chip_pos_X[k],'top':chip_pos_Y[k],'width':0,'height':0,'opacity':0});

//位置を違わせる、画像幅だけ移動させているので、右よりスライドする
chipboxs[k].css({'left':chip_pos_X[k]+box_W,'top':chip_pos_Y[k],'width':chip_W[k],'height':chip_H[k],'opacity':0});


要略すれば(IN)


移動することは、CSSを設定する行為
chipboxs[k].css({'left':移動先位置,'top':移動先位置,'width':移動先の大きさ,'height':移動先の大きさ,'opacity':移動先の透明度});

元に戻すは、分割した時のデータでアニメ実行する行為
chipboxs[k].delay(計算値).animate({'left':分割時の位置,'top':分割時の位置,'width':分割時の大きさ,'height':分割時の大きさ,'opacity':1},アニメ速度,function() {
	//
});

OUTは、データが逆になるだけ。(移動先データを配列に保存して置くと作業が効率的になる)


delayを設定する(遅延)

delay()を利用してパターン展開(時間差でパターンを表現する)する、表示までの時間差を計算します。setTimeout()を使用するのと同じことです。

Flashで応用されていますが、どのようにしたら「パターン展開」するかの資料は皆無に近いようです。個人で設計するほか方法はありません。


//遅延値delayを設定する
var delay_v=(i+j) * delay_speed;

delayパターンの例


対角に
delay_v = (i+j) * delay_speed;
並び順序、縦に
delay_v = k * delay_speed;
中央から横に
delay_v = Math.abs(k-split_total/2) * delay_speed;
全部同時に(適当な数値をいれる)
delay_v = delay_speed;

カウントk, i, j, split_h, split_v, split_total, などを利用して計算式を作ります

delay()は、内部的には、setTimeout()の処理であり、ミリ秒単位の設定になりますが、Flashのような高速処理能力はありませんので、計算数値に delay_speed を乗じて大きく修正しています。


アニメの実行

このアニメでは、左上より右下に向かって、スライス要素が拡大します。(サンプルでは透明度変化のみ)
複数回のアニメを実行していますので、全て終了のタイミングを sliceflag のカウントで判定して終了処理に進みます。


//アニメ実行 IN
chipboxs[k].delay(delay_v).animate({'left':chip_pos_X[k],'top':chip_pos_Y[k],'width':chip_W[k],'height':chip_H[k],'opacity':1},speed,function() {

	sliceflag ++;
	//アニメ終了処理に進む
	if (sliceflag == split_total) {slice_image_parts();}
});

--------------------------------------------

サンプルでの場合は
chipboxs[k].delay(delay_v).animate({'opacity':1},speed,.........
でも事足りることですが

サンプルでは「透明度変化」のみですが、通常は色々変化させていますので、標準的なアニメの書式と言えます。


終了処理

アニメが全て終了したら、下画像(下層)に新しい画像URLを挿入して、上画像(上層)を欄外に移動します。イワユルごまかし処理を行うわけです。


//アニメ終了処理
function slice_image_parts() {

	//スライスフラグ
	sliceflag=0;
	//下層画像を取り替える
	img_url=load_url;
	imagebox.children("img").attr({'src':img_url}).css({'display':'block'});
	//上層を隠す
	topbox.css({'left':box_W,'display':'none'});

	//変数更新
	keep_url=load_url;
	keep_no=no;
	//アニメ終了
	anime_move = false;

};

anime_moveは画像表示処理に入ったら anime_move=true にしてアニメ中はボタン操作などが出来ないようにするために利用します。

delay()を利用したアニメでは、途中でアニメを止めるとキレイに処理できないので完全に終了してから、他の処理を行います。stop()は使用しません。かえって処理が難しくなり、画面が乱れstop()使用の効果は得られません。


処理のし易い基本ループ

アニメ処理を分離した方が複雑な設定が可能であり、アニメの遅延処理がより正確に実行できる。
アニメ処理ループと分離しているため、データ受け渡しに配列を用意しなければなりませんが、拠り細やかな処理が出来る。実際にはこの構成を使用します。


//アニメ前の処理ループ
var k = 0;
for (var i=0; i < split_h; i++) {
	for (var j=0; j < split_v; j++) {

		//各種処理の実行

		k ++;
	}
}

//アニメ処理ループ
var k = 0;
for (var i=0; i < split_h; i++) {
	for (var j=0; j < split_v; j++) {

		//アニメ処理の実行

		k ++;
	}
}

実際のエフェクトなどの組み合わせ

サンプルは説明し易いように単純な構成にしていますが、実際のプラグインなどの場合は、エフェクト数が多数あります。条件に合わせて分割画像の「初期の位置」をかえているだけです。それらについては、次回に解説します。


背景画像ではなく、画像を挿入する

一般的には、背景画像を使用しているようです。このほうが処理が軽い。
画像を挿入するとエフェクトの種類を若干ふやせます。(分割 1x1 の場合、画像に対しての処理可能)
背景画像使用と比較して、少々動作が重くなります。またIEで透過処理の際にバグが出易い等の欠点もあります。


スライス要素を作る際に、<img /> を挿入する

//スライス要素を作る
for (var i=0; i < roop_v; i++) {
	sliceboxs += '<div id="'+ 'slice'+ i + '" class="slice_bk"><img /></div>';
}

--------------------------------------------

背景画像挿入部分が次ぎのように代わります(通常)
chipboxs[k].children("img").attr({'src':slice_url});
chipboxs[k].children("img").css({'left':chip_pos_X[k] * -1,'top':chip_pos_Y[k] * -1});

CSSを追加
#top-box .slice_bk img {
position:absolute;
top:0;left:0;
}

画像の大きさをJS設定値にあわせる場合(特殊)、画像の大きさも変化させますので、拠り一層負荷がかかります。また画像はブラウザにより「表示品質」が違います。


実際の設計

上記説明は基本的な概念です。実際、色々と制作して行くには「かなりの手間隙」と、アイデアが必要になります。
ほぼ、FlashをJSに書き換えたようなものです。スクリプトも個人それぞれで違うとは思いますが、一例です。


Loading画像など

使用画像は原則、使用者がご用意ください。Loading画像は好きなものが使用可能です。

loading.gif 31x31 :


応用などは次回にします、今回は以上です。

 


[ この記事のURL ]


タグ:slice , memo , photo , jquery

 

ブログ記事一覧

年別アーカイブ一覧



[1]