1 /*!
  2  * Version: 0.1
  3  * http://blog.phantom4.org/files/javascript/anime/
  4  *
  5  * Copyright(c) 2012 ngi <blog.phantom4.org> All rights reserved.
  6  * MIT Licensed
  7  */
  8 
  9 var
 10 	ANIMATE_APP = ANIMATE_APP || {};
 11 
 12 ANIMATE_APP.classes = ANIMATE_APP.classes || {};
 13 
 14 
 15 /**
 16  * photoのアニメーション
 17  *
 18  * @require jQuery 1.7+
 19  * @param target {Object} 対象の要素
 20  * @param [option] {Object} オプション
 21  * @param [option.repeat = -1] {Number} 再生回数(-1: 制限なし)
 22  * @param [option.interval = 166.6] {Number} アニメーション間隔(166.6=約6fps)
 23  * @param [option.immediatePlay = true] {Boolean} 即時再生するか
 24  * @param [option.vertical = false] {Boolean} スプライト、またはインナーが縦向きか
 25  * @param [option.viewSize] {Number} 表示する幅を指定(hidden属性などで幅が正確に取得できない場合など)
 26  * @description targetのimg0番目のwidth()を取得するので、非表示になっていないことが前提。非表示に鳴っている場合はoption.viewSizeで指定する。
 27  * @class
 28  */
 29 ANIMATE_APP.classes.Animation = function(target, option) {
 30 	var
 31 		o = option || {};
 32 
 33 	this.target = target;	//アニメーションする対象
 34 	this.$inner = $(target).children().eq(0);	//アニメーションする対象
 35 	this.$img = $("img:eq(0)", this.target);	//画像
 36 	this.vertical = o.vertical || false;	//spriteの方向
 37 	this.interval = o.interval || 166.6;	//アニメーションのfps
 38 	this.repeat = !isNaN(o.repeat) ? o.repeat : -1;
 39 	this.useRepeat = Boolean(this.repeat >= 0);	//ループ回数を制限する
 40 	this.immediatePlay = Boolean(String(o.immediatePlay) !== "false");
 41 
 42 	//spriteの方向
 43 	if(!this.vertical) {
 44 		this.frameSize = o.viewSize || $(target).width();	//表示する幅
 45 		this.changeProp = "marginLeft";
 46 	}
 47 	else {
 48 		this.frameSize = o.viewSize || $(target).height();	//表示する高さ
 49 		this.changeProp = "marginTop";
 50 	}
 51 
 52 	this._init();
 53 };
 54 
 55 ANIMATE_APP.classes.Animation.prototype = {
 56 
 57 	FINISH: "finish",	//アニメーションが終了した
 58 	FINISH_REVERSE: "finisReverse",	//アニメーションの逆再生が終了した
 59 
 60 	isPlaying: false,	//アニメーション中か
 61 	contentSize: 0,	//画像の幅
 62 	timerId: null,	//アニメーションのタイマー
 63 	frameCount: 0,	//カウント
 64 	isImgLoaded: false,	//画像の読み込みが終わっているか
 65 	totalFrames: 1,	//枚数
 66 	_imgLoadTimer: null,	//画像の読み込み完了を待つタイマー
 67 	_reverse: 0,	//再生方向(0: 通常、1: 逆再生)
 68 
 69 
 70 	/**
 71 	 * 開始
 72 	 *
 73 	 * @param [option] {Object} オプション
 74 	 * @param [option.repeat = -1] {Number} 再生回数(-1: 制限なし)
 75 	 * @param [option.interval = 166.6] {Number} アニメーション間隔(166.6=約6fps)
 76 	 * @param [option.reverse = 0] {int} 再生方向(0: 通常、1: 逆再生)
 77 	 */
 78 	play: function (option) {
 79 		var
 80 			me = this,
 81 			o = option || {};
 82 
 83 		//再生方向の指定があれば
 84 		if (!isNaN(o.reverse)) {
 85 			me._reverse = o.reverse;
 86 		}
 87 
 88 		if(me.totalFrames > 1) {
 89 			me.isPlaying = true;
 90 			me.next();	//次のコマへ
 91 			me._interval();	//タイマーで連続してアニメ
 92 		}
 93 	},
 94 
 95 	/**
 96 	 * 停止
 97 	 *
 98 	 */
 99 	pause: function () {
100 		var me = this;
101 
102 		if(me.totalFrames > 1) {
103 			me._clearTimer();
104 		}
105 	},
106 
107 	/**
108 	 * 先頭に戻る
109 	 *
110 	 */
111 	forward: function () {
112 		var me = this;
113 
114 		if (me._reverse == 0) {
115 			//通常再生
116 			me.frameCount = 0;
117 			me.$inner
118 				.css(me.changeProp, 0);
119 		}
120 		else {
121 			//逆再生
122 			me.frameCount = me.totalFrames - 1;
123 			me.$inner
124 				.css(me.changeProp, me.frameSize * me.frameCount * -1);
125 		}
126 	},
127 
128 	/**
129 	 * 次のコマを表示
130 	 *
131 	 */
132 	next: function () {
133 		var me = this;
134 		if (me._reverse == 0) {
135 			me._next();
136 		}
137 		else {
138 			me._prev();
139 		}
140 	},
141 
142 	/**
143 	 * 前のコマを表示
144 	 *
145 	 */
146 	prev: function () {
147 		var me = this;
148 		if (me._reverse == 0) {
149 			me._prev();
150 		}
151 		else {
152 			me._next();
153 		}
154 	},
155 
156 	/**
157 	 * 指定のコマを表示
158 	 *
159 	 * @param destination {int} コマID(0~)
160 	 */
161 	to: function (destination) {
162 		var me = this;
163 
164 		if(destination >= me.totalFrames) {
165 			destination = me.totalFrames - 1;
166 		}
167 		else if (destination < 0) {
168 			destination = 0;
169 		}
170 		me.frameCount = destination;
171 		me.$inner.css(me.changeProp, me.frameSize * me.frameCount * -1);
172 	},
173 
174 	/**
175 	 * 停止して戻る
176 	 *
177 	 */
178 	stop: function () {
179 		var me = this;
180 
181 		me.isPlaying = false;
182 		me.pause();
183 		me.forward();
184 	},
185 
186 	/**
187 	 * アニメの枚数を取得
188 	 *
189 	 * @return {Number}
190 	 */
191 	getTotalFrames: function () {
192 		return this.totalFrames;
193 	},
194 
195 	/**
196 	 * 再生方向を変える
197 	 *
198 	 * @param reverse {int} 0: 通常、1: 逆再生
199 	 */
200 	changeDirection: function (reverse) {
201 		var me = this;
202 
203 		if (reverse < 0) {
204 			me._reverse = 1;
205 		}
206 		else {
207 			me._reverse = 0;
208 		}
209 	},
210 
211 	/**
212 	 * 逆再生か
213 	 *
214 	 * @returns {Boolean} 逆再生ならtrue
215 	 */
216 	getIsReverse: function () {
217 		return Boolean(this._reverse);
218 	},
219 
220 	/**
221 	 * 初期化
222 	 *
223 	 * @private
224 	 */
225 	_init: function () {
226 		var me = this;
227 
228 		if(me.$img.length > 0) {
229 			//アニメーション対象が画像なら
230 
231 			//画像の読み込みを待つ
232 			if(me._imgLoadTimer) {
233 				clearInterval(me._imgLoadTimer);
234 			}
235 
236 			me._imgLoadTimer = setInterval(function() {
237 				me._imgLoadObserver.apply(me);
238 			}, 0);
239 		}
240 		else {
241 			//アニメーション対象が画像以外なら
242 			me._onImgLoadComplete.apply(me);
243 		}
244 	},
245 
246 	/**
247 	 * 画像の読み込みを待つ
248 	 *
249 	 * @private
250 	 */
251 	_imgLoadObserver: function () {
252 		var me = this;
253 
254 		if(me.$img && typeof(me.$img.width) == "function" && me.$img.width() > 0) {
255 			clearInterval(me._imgLoadTimer);
256 			me._onImgLoadComplete.apply(me);
257 		}
258 	},
259 
260 	/**
261 	 * 画像の読み込みが完了した
262 	 *
263 	 * @private
264 	 */
265 	_onImgLoadComplete: function () {
266 		var
267 			me = this;
268 
269 		me.isImgLoaded = true;
270 		me.contentSize = (!me.vertical) ? me.$inner.outerWidth() : me.$inner.outerHeight();
271 		me.totalFrames = Math.ceil(me.contentSize / me.frameSize);	//枚数を算出
272 
273 		if(me.immediatePlay) {
274 			me.play();
275 		}
276 	},
277 
278 	/**
279 	 * 次のコマを表示
280 	 *
281 	 * @private
282 	 */
283 	_next: function () {
284 		var me = this;
285 
286 		if(me.frameCount + 1 < me.totalFrames - 1) {
287 			me.frameCount++;
288 		}
289 		else {
290 			me.frameCount = 0;
291 			if(me.useRepeat) {
292 				me.repeat--;
293 			}
294 			//最後まで到達した
295 			me.target.trigger(me.FINISH)
296 		}
297 
298 		me.$inner.css(me.changeProp, me.frameSize * me.frameCount * -1);
299 	},
300 
301 	/**
302 	 * 前のコマへ
303 	 *
304 	 * @private
305 	 */
306 	_prev: function () {
307 		var me = this;
308 
309 		if(0 < me.frameCount) {
310 			me.frameCount--;
311 		}
312 		else {
313 			me.frameCount = me.totalFrames - 1;
314 			if(me.useRepeat) {
315 				me.repeat--;
316 			}
317 			//最後まで到達した
318 			me.target.trigger(me.FINISH_REVERSE)
319 		}
320 
321 		me.$inner.css(me.changeProp, me.frameSize * me.frameCount * -1);
322 	},
323 
324 	/**
325 	 * アニメを繰り返す
326 	 *
327 	 * @private
328 	 */
329 	_interval: function () {
330 		var me = this;
331 
332 		me._clearTimer();
333 		me.timerId = setTimeout(function () {
334 			me.next.apply(me);
335 			if(!me.useRepeat || me.repeat >= 0) {
336 				me._interval();
337 			}
338 			else {
339 				me.isPlaying = false;
340 			}
341 		}, me.interval);
342 	},
343 
344 	/**
345 	 * タイマーをクリアする
346 	 *
347 	 * @private
348 	 */
349 	_clearTimer: function () {
350 		var
351 			me = this;
352 
353 		if(!isNaN(me.timerId)) {
354 			clearTimeout(me.timerId);
355 		}
356 	}
357 };
358 
359 
360