1/*! 2 * motio 2.2.2 - 7th Aug 2014 3 * https://github.com/darsain/motio 4 * 5 * Licensed under the MIT license. 6 * http://opensource.org/licenses/MIT 7 */ 8; 9(function (w, undefined) { 10 'use strict'; 11 12 var className = 'Motio'; 13 14 // Local WindowAnimationTiming interface polyfill 15 var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame; 16 var rAF = w.requestAnimationFrame; 17 (function () { 18 var vendors = ['moz', 'webkit', 'o']; 19 var lastTime = 0; 20 21 // For a more accurate WindowAnimationTiming interface implementation, ditch the native 22 // requestAnimationFrame when cancelAnimationFrame is not present (older versions of Firefox) 23 for (var i = 0, l = vendors.length; i < l && !cAF; ++i) { 24 cAF = w[vendors[i] + 'CancelAnimationFrame'] || w[vendors[i] + 'CancelRequestAnimationFrame']; 25 rAF = cAF && w[vendors[i] + 'RequestAnimationFrame']; 26 } 27 28 if (!cAF) { 29 rAF = function (callback) { 30 var currTime = +new Date(); 31 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 32 lastTime = currTime + timeToCall; 33 return w.setTimeout(function () { 34 callback(currTime + timeToCall); 35 }, timeToCall); 36 }; 37 38 cAF = function (id) { 39 clearTimeout(id); 40 }; 41 } 42 }()); 43 44 // Returns time in as precise manner as possible 45 var getTime = (function () { 46 var perf = w.performance; 47 if (perf && perf.now) 48 return perf.now.bind(perf); 49 return function () { 50 return +new Date(); 51 }; 52 }()); 53 54 /** 55 * Motio. 56 * 57 * @class 58 * 59 * @param {Element} element DOM element with animation background. 60 * @param {Object} options Object with plugin options. 61 * @param {Object} callbackMap Callbacks map. 62 */ 63 function Motio(element, options) { 64 // Options 65 var o = defaults(options); 66 67 // Private variables 68 var self = this; 69 var isPan = !o.frames; 70 var frames = []; 71 var callbacks = {}; 72 var animation = {}; 73 var active = 0; 74 var pos, bgPos, lastPos, frameID, i, l; 75 76 // Exposed properties 77 self.element = element; 78 self.width = o.width || element.clientWidth; 79 self.height = o.height || element.clientHeight; 80 self.options = o; 81 self.isPaused = true; 82 83 /** 84 * Pause animation. 85 * 86 * @return {Object} Motio instance. 87 */ 88 self.pause = function () { 89 cAF(frameID); 90 frameID = 0; 91 animation.lastFrame = 0; 92 if (!self.isPaused) { 93 self.isPaused = true; 94 trigger('pause'); 95 } 96 return self; 97 }; 98 99 /** 100 * Play animation. 101 * 102 * @param {Boolean} reversed Reversed animation. 103 * 104 * @return {Object} Motio instance. 105 */ 106 self.play = function (reversed) { 107 animation.finite = false; 108 animation.callback = undefined; 109 animation.immediate = false; 110 resume(reversed); 111 return self; 112 }; 113 114 /** 115 * Request rendering when paused. 116 * 117 * @param {Boolean} reversed Reversed animation. 118 * 119 * @return {Void} 120 */ 121 function resume(reversed) { 122 animation.reversed = reversed; 123 if (!frameID) { 124 self.isPaused = false; 125 trigger('play'); 126 frameID = rAF(render); 127 } 128 } 129 130 /** 131 * Toggle animation. 132 * 133 * @return {Object} Motio instance. 134 */ 135 self.toggle = function () { 136 self[frameID ? 'pause' : 'play'](); 137 return self; 138 }; 139 140 /** 141 * Animate to the first frame and pause. 142 * 143 * @param {Boolean} immediate Reposition immediately without animation. 144 * @param {Function} callback Execute a callback on arrival. 145 * 146 * @return {Object} Motio instance. 147 */ 148 self.toStart = function (immediate, callback) { 149 return self.to(0, immediate, callback); 150 }; 151 152 /** 153 * Animate to the last frame and pause. 154 * 155 * @param {Boolean} immediate Reposition immediately without animation. 156 * @param {Function} callback Execute a callback on arrival. 157 * 158 * @return {Object} Motio instance. 159 */ 160 self.toEnd = function (immediate, callback) { 161 return self.to(frames.length - 1, immediate, callback); 162 }; 163 164 /** 165 * Animate to a specified frame index and pause. 166 * 167 * @param {Integer} frame Frame index starting at 0. 168 * @param {Boolean} immediate Reposition immediately without animation. 169 * @param {Function} callback Execute a callback on arrival. 170 * 171 * @return {Object} Motio instance. 172 */ 173 self.to = function (frame, immediate, callback) { 174 if (isPan || !isNumber(frame) || frame < 0 || frame >= frames.length) { 175 return self; 176 } 177 178 // Handle optional argument 179 if (type(immediate) === 'function') { 180 callback = immediate; 181 immediate = false;
182 } 183 184 // Handle cases where the requested animation is already active 185 if (frame === active) { 186 if (frame === 0) { 187 active = frames.length; 188 } else if (frame === frames.length - 1) { 189 active = -1; 190 } else { 191 if (type(callback) === 'function') { 192 callback.call(self); 193 } 194 self.pause(); 195 return self; 196 } 197 } 198 199 // Update animation object 200 animation.finite = true; 201 animation.to = frame; 202 animation.immediate = !!immediate; 203 animation.callback = callback; 204 205 // Resume rendering if paused 206 resume(); 207 208 return self; 209 }; 210 211 /** 212 * Determine position for next frame. 213 * 214 * @return {Void} 215 */ 216 function positionTick() { 217 if (isPan) { 218 pos.x += o.speedX / o.fps; 219 pos.y += o.speedY / o.fps; 220 if (o.bgWidth && Math.abs(pos.x) > o.bgWidth) { 221 pos.x = pos.x % o.bgWidth; 222 } 223 if (o.bgHeight && Math.abs(pos.y) > o.bgHeight) { 224 pos.y = pos.y % o.bgHeight; 225 } 226 } else { 227 if (animation.finite) { 228 if (animation.immediate) { 229 active = animation.to; 230 } else { 231 active += active > animation.to ? -1 : 1; 232 } 233 } else { 234 if (animation.reversed) { 235 if (--active <= 0) { 236 active = frames.length - 1; 237 } 238 } else { 239 if (++active >= frames.length) { 240 active = 0; 241 } 242 } 243 } 244 // Update active frame property 245 self.frame = active; 246 } 247 } 248 249 /** 250 * Render animation frame. 251 * 252 * @return {Void} 253 */ 254 function render() { 255 frameID = rAF(render); 256 var time = getTime(); 257 258 // Don't render when it's not time for next frame yet 259 if (o.fps < 60 && animation.lastFrame && animation.lastFrame + (1000 / o.fps) + 1 > time) 260 return; 261 262 animation.lastFrame = time; 263 positionTick(); 264 265 // Prepare new background position 266 //bgPos = isPan ? Math.round(pos.x) + 'px ' + Math.round(pos.y) + 'px' : frames[active]; 267 bgPos = isPan ? Math.round(pos.x) + 'px' : frames[active]; 268 269 // Update the position only when there is a change 270 // to not cause redundant reflows & repaints 271 if (bgPos !== lastPos) { 272 //element.style.backgroundPosition = lastPos = bgPos; 273 element.style.backgroundPositionX = lastPos = bgPos; 274 } 275 276 // Trigger frame event 277 trigger('frame'); 278 279 // When arrived to a finite animation destination, pause & execute the callback 280 if (animation.finite && animation.to === active) { 281 self.pause(); 282 if (type(animation.callback) === 'function') { 283 animation.callback.call(self); 284 } 285 } 286 } 287 288 /** 289 * Update one of the dynamic option properties. 290 * 291 * Only these options can be updated: 292 * speed, fps 293 * 294 * @param {String} option Option name. 295 * @param {Mixed} value New option value. 296 * 297 * @return {Object} Motio instance. 298 */ 299 self.set = function (option, value) { 300 o[option] = value; 301 return self; 302 }; 303 304 /** 305 * Registers callbacks. 306 * 307 * @param {Mixed} name Event name, or callbacks map. 308 * @param {Mixed} fn Callback, or an array of callback functions. 309 * 310 * @return {Object} Motio instance. 311 */ 312 self.on = function (name, fn) { 313 // Callbacks map 314 if (type(name) === 'object') { 315 for (var key in name) { 316 if (name.hasOwnProperty(key)) { 317 self.on(key, name[key]); 318 } 319 } 320 // Callback 321 } else if (type(fn) === 'function') { 322 var names = name.split(' '); 323 for (var n = 0, nl = names.length; n < nl; n++) { 324 callbacks[names[n]] = callbacks[names[n]] || []; 325 if (callbackIndex(names[n], fn) === -1) { 326 callbacks[names[n]].push(fn); 327 } 328 } 329 // Callbacks array 330 } else if (type(fn) === 'array') { 331 for (var f = 0, fl = fn.length; f < fl; f++) { 332 self.on(name, fn[f]); 333 } 334 } 335 return self; 336 }; 337 338 /** 339 * Remove one or all callbacks. 340 * 341 * @param {String} name Event name. 342 * @param {Mixed} fn Callback, or an array of callback functions. Omit to remove all callbacks. 343 * 344 * @return {Object} Motio instance. 345 */ 346 self.off = function (name, fn) { 347 if (fn instanceof Array) { 348 for (var f = 0, fl = fn.length; f < fl; f++) { 349 self.off(name, fn[f]); 350 } 351 } else { 352 var names = name.split(' '); 353 for (var n = 0, nl = names.length; n < nl; n++) { 354 callbacks[names[n]] = callbacks[names[n]] || []; 355 if (type(fn) === 'undefined') { 356 callbacks[names[n]].length = 0; 357 } else { 358 var index = callbackIndex(names[n], fn); 359 if (index !== -1) { 360 callbacks[names[n]].splice(index, 1); 361 } 362 } 363 } 364 } 365 return self; 366 }; 367 368 /** 369 * Returns callback array index. 370 * 371 * @param {String} name Event name. 372 * @param {Function} fn Function 373 * 374 * @return {Int} Callback array index, or -1 if isn't registered. 375 */ 376 function callbackIndex(name, fn) { 377 for (i = 0, l = callbacks[name].length; i < l; i++) { 378 if (callbacks[name][i] === fn) { 379 return i; 380 } 381 } 382 return -1; 383 } 384 385 /** 386 * Trigger callbacks for event. 387 * 388 * @param {String} name Event name. 389 * @param {Mixed} argX Arguments passed to callbacks. 390 * 391 * @return {Void} 392 */ 393 function trigger(name, arg1) { 394 if (callbacks[name]) { 395 for (i = 0, l = callbacks[name].length; i < l; i++) { 396 callbacks[name][i].call(self, name, arg1); 397 } 398 } 399 } 400 401 /** 402 * Returns a computed style property of a frame element. 403 * 404 * @param {String} name Property name. 405 * 406 * @return {String} 407 */ 408 function getProp(name) { 409 return w.getComputedStyle ? w.getComputedStyle(element, null)[name] : element.currentStyle[name]; 410 } 411 412 /** 413 * Destroy plugin instance and reset backgroundPosition to its original state 414 * 415 * @public 416 */ 417 self.destroy = function () { 418 self.pause(); 419 element.style.backgroundPosition = ''; 420 return self; 421 }; 422 423 /** 424 * Construct. 425 */ 426 (function () {
427 // Background position 428 var posString = ( 429 getProp('backgroundPosition') || 430 getProp('backgroundPositionX') + ' ' + getProp('backgroundPositionY') 431 ).replace(/left|top/gi, 0).split(' '); 432 pos = { 433 x: getInt(posString[0]), 434 y: getInt(posString[1]) 435 }; 436 437 // Build frames array 438 if (!isPan) { 439 frames.length = 0; 440 for (var i = 0; i < o.frames; i++) { 441 if (o.vertical) { 442 pos.y = i * -self.height; 443 } else { 444 pos.x = i * -self.width; 445 } 446 frames.push(pos.x + 'px ' + pos.y + 'px'); 447 } 448 // Expose sprite mode properties 449 self.frames = frames.length; 450 self.frame = 0; 451 } else { 452 // Expose panning mode properties 453 self.pos = pos; 454 } 455 }()); 456 } 457 458 /** 459 * Return type of the value. 460 * 461 * @param {Mixed} value 462 * 463 * @return {String} 464 */ 465 function type(value) { 466 if (value == null) { 467 return String(value); 468 } 469 if (typeof value === 'object' || typeof value === 'function') { 470 return (value instanceof w.NodeList && 'nodelist') || 471 (value instanceof w.HTMLCollection && 'htmlcollection') || 472 Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase(); 473 } 474 return typeof value; 475 } 476 477 /** 478 * Check if variable is a number. 479 * 480 * @param {Mixed} value 481 * 482 * @return {Boolean} 483 */ 484 function isNumber(value) { 485 return !isNaN(parseFloat(value)) && isFinite(value); 486 } 487 488 /** 489 * Fills unassigned option properties with default values. Does 1 level deep object clone. 490 * 491 * @param {Object} options 492 * 493 * @return {Void} 494 */ 495 function defaults(options) { 496 var output = {}; 497 options = type(options) === 'object' ? options : {}; 498 for (var key in Motio.defaults) { 499 output[key] = (options.hasOwnProperty(key) ? options : Motio.defaults)[key]; 500 } 501 return output; 502 } 503 504 /** 505 * Parse integer out of a string. 506 * 507 * @param {Mixed} value 508 * 509 * @return {Int} 510 */ 511 function getInt(value) { 512 return 0 | parseInt(value, 10); 513 } 514 515 // Expose class globally 516 w[className] = Motio; 517 518 // Default options 519 Motio.defaults = { 520 fps: 15, // Frames per second. 521 522 // Sprite animation specific options 523 frames: 0, // Number of frames in sprite. 524 vertical: 0, // Tells Motio that you are using vertically stacked sprite image. 525 width: 0, // Set the frame width manually (optional). 526 height: 0, // Set the frame height manually (optional). 527 528 // Panning specific options 529 speedX: 0, // Horizontal panning speed in pixels per second. 530 speedY: 0, // Vertical panning speed in pixels per second. 531 bgWidth: 0, // Width of the background image (optional). 532 bgHeight: 0 // Height of the background image (optional). 533 }; 534})(window); 535/*global Motio */ 536(function ($) { 537 'use strict'; 538 539 // Names 540 var pluginName = 'motio'; 541 var namespace = pluginName; 542 543 // jQuery plugin 544 $.fn[pluginName] = function (options, callbackMap) { 545 var method, methodArgs; 546 547 // Attributes logic 548 if (!$.isPlainObject(options)) { 549 if (typeof options === 'string' || options === false) { 550 method = options === false ? 'destroy' : options; 551 methodArgs = Array.prototype.slice.call(arguments, 1); 552 } 553 options = {}; 554 } 555 556 // Apply plugin to all elements 557 return this.each(function (i, element) { 558 // Plugin call with prevention against multiple instantiations 559 var plugin = $.data(element, namespace); 560 561 if (!plugin && !method) { 562 // Create a new plugin object if it doesn't exist yet 563 plugin = $.data(element, namespace, new Motio(element, options)); 564 // Bind callbacks 565 plugin.on(callbackMap); 566 // Start playing when requested 567 if (!options.startPaused) { 568 plugin.play(); 569 } 570 } else if (plugin && method) { 571 // Call plugin method 572 if (plugin[method]) {
573 plugin[method].apply(plugin, methodArgs); 574 } 575 // Remove plugin from element data on destroy 576 if (method === 'destroy') { 577 $.removeData(element, namespace); 578 } 579 } 580 }); 581 }; 582}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.