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