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