1//============================================================ 2// 3// The MIT License 4// 5// Copyright (C) 2014 Matthew Wagerfield - @wagerfield 6// 7// Permission is hereby granted, free of charge, to any 8// person obtaining a copy of this software and associated 9// documentation files (the "Software"), to deal in the 10// Software without restriction, including without limitation 11// the rights to use, copy, modify, merge, publish, distribute, 12// sublicense, and/or sell copies of the Software, and to 13// permit persons to whom the Software is furnished to do 14// so, subject to the following conditions: 15// 16// The above copyright notice and this permission notice 17// shall be included in all copies or substantial portions 18// of the Software. 19// 20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 21// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 22// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 24// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 25// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 28// OR OTHER DEALINGS IN THE SOFTWARE. 29// 30//============================================================ 31 32/** 33 * jQuery || Zepto Parallax Plugin 34 * @author Matthew Wagerfield - @wagerfield 35 * @description Creates a parallax effect between an array of layers, 36 * driving the motion from the gyroscope output of a smartdevice. 37 * If no gyroscope is available, the cursor position is used. 38 */ 39 ;(function($, window, document, undefined) { 40 41 // Strict Mode 42 'use strict'; 43 44 // Constants 45 var NAME = 'parallax'; 46 var MAGIC_NUMBER = 30; 47 var DEFAULTS = { 48 relativeInput: false, 49 clipRelativeInput: false, 50 calibrationThreshold: 100, 51 calibrationDelay: 500, 52 supportDelay: 500, 53 calibrateX: false, 54 calibrateY: true, 55 invertX: true, 56 invertY: true, 57 limitX: false, 58 limitY: false, 59 scalarX: 10.0, 60 scalarY: 10.0, 61 frictionX: 0.1, 62 frictionY: 0.1, 63 originX: 0.5, 64 originY: 0.5, 65 pointerEvents: true, 66 precision: 1 67 }; 68 69 function Plugin(element, options) { 70 71 // DOM Context 72 this.element = element; 73 74 // Selections 75 this.$context = $(element).data('api', this); 76 this.$layers = this.$context.find('.layer'); 77 78 // Data Extraction 79 var data = { 80 calibrateX: this.$context.data('calibrate-x') || null, 81 calibrateY: this.$context.data('calibrate-y') || null, 82 invertX: this.$context.data('invert-x') || null, 83 invertY: this.$context.data('invert-y') || null, 84 limitX: parseFloat(this.$context.data('limit-x')) || null, 85 limitY: parseFloat(this.$context.data('limit-y')) || null, 86 scalarX: parseFloat(this.$context.data('scalar-x')) || null, 87 scalarY: parseFloat(this.$context.data('scalar-y')) || null, 88 frictionX: parseFloat(this.$context.data('friction-x')) || null, 89 frictionY: parseFloat(this.$context.data('friction-y')) || null, 90 originX: parseFloat(this.$context.data('origin-x')) || null, 91 originY: parseFloat(this.$context.data('origin-y')) || null, 92 pointerEvents: this.$context.data('pointer-events') || true, 93 precision: parseFloat(this.$context.data('precision')) || 1 94 }; 95 96 // Delete Null Data Values 97 for (var key in data) { 98 if (data[key] === null) delete data[key]; 99 } 100 101 // Compose Settings Object 102 $.extend(this, DEFAULTS, options, data); 103 104 // States 105 this.calibrationTimer = null; 106 this.calibrationFlag = true; 107 this.enabled = false; 108 this.depthsX = []; 109 this.depthsY = []; 110 this.raf = null; 111 112 // Element Bounds 113 this.bounds = null; 114 this.ex = 0; 115 this.ey = 0; 116 this.ew = 0; 117 this.eh = 0; 118 119 // Element Center 120 this.ecx = 0; 121 this.ecy = 0; 122 123 // Element Range 124 this.erx = 0; 125 this.ery = 0; 126 127 // Calibration 128 this.cx = 0; 129 this.cy = 0; 130
131 // Input 132 this.ix = 0; 133 this.iy = 0; 134 135 // Motion 136 this.mx = 0; 137 this.my = 0; 138 139 // Velocity 140 this.vx = 0; 141 this.vy = 0; 142 143 // Callbacks 144 this.onMouseMove = this.onMouseMove.bind(this); 145 this.onDeviceOrientation = this.onDeviceOrientation.bind(this); 146 this.onOrientationTimer = this.onOrientationTimer.bind(this); 147 this.onCalibrationTimer = this.onCalibrationTimer.bind(this); 148 this.onAnimationFrame = this.onAnimationFrame.bind(this); 149 this.onWindowResize = this.onWindowResize.bind(this); 150 151 // Initialise 152 this.initialise(); 153 } 154 155 Plugin.prototype.transformSupport = function(value) { 156 var element = document.createElement('div'); 157 var propertySupport = false; 158 var propertyValue = null; 159 var featureSupport = false; 160 var cssProperty = null; 161 var jsProperty = null; 162 for (var i = 0, l = this.vendors.length; i < l; i++) { 163 if (this.vendors[i] !== null) { 164 cssProperty = this.vendors[i][0] + 'transform'; 165 jsProperty = this.vendors[i][1] + 'Transform'; 166 } else { 167 cssProperty = 'transform'; 168 jsProperty = 'transform'; 169 } 170 if (element.style[jsProperty] !== undefined) { 171 propertySupport = true; 172 break; 173 } 174 } 175 switch(value) { 176 case '2D': 177 featureSupport = propertySupport; 178 break; 179 case '3D': 180 if (propertySupport) { 181 var body = document.body || document.createElement('body'); 182 var documentElement = document.documentElement; 183 var documentOverflow = documentElement.style.overflow; 184 var isCreatedBody = false; 185 if (!document.body) { 186 isCreatedBody = true; 187 documentElement.style.overflow = 'hidden'; 188 documentElement.appendChild(body); 189 body.style.overflow = 'hidden'; 190 body.style.background = ''; 191 } 192 body.appendChild(element); 193 element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 194 propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty); 195 featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== "none"; 196 documentElement.style.overflow = documentOverflow; 197 body.removeChild(element); 198 if ( isCreatedBody ) { 199 body.removeAttribute('style'); 200 body.parentNode.removeChild(body); 201 } 202 } 203 break; 204 } 205 return featureSupport; 206 }; 207 208 Plugin.prototype.ww = null; 209 Plugin.prototype.wh = null; 210 Plugin.prototype.wcx = null; 211 Plugin.prototype.wcy = null; 212 Plugin.prototype.wrx = null; 213 Plugin.prototype.wry = null; 214 Plugin.prototype.portrait = null; 215 Plugin.prototype.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i); 216 Plugin.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 217 Plugin.prototype.motionSupport = !!window.DeviceMotionEvent; 218 Plugin.prototype.orientationSupport = !!window.DeviceOrientationEvent; 219 Plugin.prototype.orientationStatus = 0; 220 Plugin.prototype.transform2DSupport = Plugin.prototype.transformSupport('2D'); 221 Plugin.prototype.transform3DSupport = Plugin.prototype.transformSupport('3D'); 222 Plugin.prototype.propertyCache = {}; 223 224 Plugin.prototype.initialise = function() { 225 226 // Configure Styles 227 if (this.$context.css('position') === 'static') { 228 this.$context.css({ 229 position:'relative' 230 }); 231 } 232 233 // Pointer events 234 if(!this.pointerEvents){ 235 this.$context.css({ 236 pointerEvents: 'none' 237 }); 238 } 239 240 // Hardware Accelerate Context 241 this.accelerate(this.$context); 242 243 // Setup 244 this.updateLayers(); 245 this.updateDimensions(); 246 this.enable(); 247 this.queueCalibration(this.calibrationDelay); 248 }; 249 250 Plugin.prototype.updateLayers = function() { 251 252 // Cache Layer Elements 253 this.$layers = this.$context.find('.layer'); 254 this.depthsX = []; 255 this.depthsY = []; 256 257 // Configure Layer Styles 258 this.$layers.css({ 259 position:'absolute', 260 display:'block', 261 left: 0, 262 top: 0 263 });
264 this.$layers.first().css({ 265 position:'relative' 266 }); 267 268 // Hardware Accelerate Layers 269 this.accelerate(this.$layers); 270 271 // Cache Depths 272 this.$layers.each($.proxy(function(index, element) { 273 //Graceful fallback on depth if depth-x or depth-y is absent 274 var depth = $(element).data('depth') || 0; 275 this.depthsX.push($(element).data('depth-x') || depth); 276 this.depthsY.push($(element).data('depth-y') || depth); 277 }, this)); 278 }; 279 280 Plugin.prototype.updateDimensions = function() { 281 this.ww = window.innerWidth; 282 this.wh = window.innerHeight; 283 this.wcx = this.ww * this.originX; 284 this.wcy = this.wh * this.originY; 285 this.wrx = Math.max(this.wcx, this.ww - this.wcx); 286 this.wry = Math.max(this.wcy, this.wh - this.wcy); 287 }; 288 289 Plugin.prototype.updateBounds = function() { 290 this.bounds = this.element.getBoundingClientRect(); 291 this.ex = this.bounds.left; 292 this.ey = this.bounds.top; 293 this.ew = this.bounds.width; 294 this.eh = this.bounds.height; 295 this.ecx = this.ew * this.originX; 296 this.ecy = this.eh * this.originY; 297 this.erx = Math.max(this.ecx, this.ew - this.ecx); 298 this.ery = Math.max(this.ecy, this.eh - this.ecy); 299 }; 300 301 Plugin.prototype.queueCalibration = function(delay) { 302 clearTimeout(this.calibrationTimer); 303 this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay); 304 }; 305 306 Plugin.prototype.enable = function() { 307 if (!this.enabled) { 308 this.enabled = true; 309 if (this.orientationSupport) { 310 this.portrait = null; 311 window.addEventListener('deviceorientation', this.onDeviceOrientation); 312 setTimeout(this.onOrientationTimer, this.supportDelay); 313 } else { 314 this.cx = 0; 315 this.cy = 0; 316 this.portrait = false; 317 window.addEventListener('mousemove', this.onMouseMove); 318 } 319 window.addEventListener('resize', this.onWindowResize); 320 this.raf = requestAnimationFrame(this.onAnimationFrame); 321 } 322 }; 323 324 Plugin.prototype.disable = function() { 325 if (this.enabled) { 326 this.enabled = false; 327 if (this.orientationSupport) { 328 window.removeEventListener('deviceorientation', this.onDeviceOrientation); 329 } else { 330 window.removeEventListener('mousemove', this.onMouseMove); 331 } 332 window.removeEventListener('resize', this.onWindowResize); 333 cancelAnimationFrame(this.raf); 334 } 335 }; 336 337 Plugin.prototype.calibrate = function(x, y) { 338 this.calibrateX = x === undefined ? this.calibrateX : x; 339 this.calibrateY = y === undefined ? this.calibrateY : y; 340 }; 341 342 Plugin.prototype.invert = function(x, y) { 343 this.invertX = x === undefined ? this.invertX : x; 344 this.invertY = y === undefined ? this.invertY : y; 345 }; 346 347 Plugin.prototype.friction = function(x, y) { 348 this.frictionX = x === undefined ? this.frictionX : x; 349 this.frictionY = y === undefined ? this.frictionY : y; 350 }; 351 352 Plugin.prototype.scalar = function(x, y) { 353 this.scalarX = x === undefined ? this.scalarX : x; 354 this.scalarY = y === undefined ? this.scalarY : y; 355 }; 356 357 Plugin.prototype.limit = function(x, y) { 358 this.limitX = x === undefined ? this.limitX : x; 359 this.limitY = y === undefined ? this.limitY : y; 360 }; 361 362 Plugin.prototype.origin = function(x, y) { 363 this.originX = x === undefined ? this.originX : x; 364 this.originY = y === undefined ? this.originY : y; 365 }; 366 367 Plugin.prototype.clamp = function(value, min, max) { 368 value = Math.max(value, min); 369 value = Math.min(value, max); 370 return value; 371 }; 372 373 Plugin.prototype.css = function(element, property, value) { 374 var jsProperty = this.propertyCache[property]; 375 if (!jsProperty) { 376 for (var i = 0, l = this.vendors.length; i < l; i++) { 377 if (this.vendors[i] !== null) { 378 jsProperty = $.camelCase(this.vendors[i][1] + '-' + property); 379 } else { 380 jsProperty = property; 381 } 382 if (element.style[jsProperty] !== undefined) { 383 this.propertyCache[property] = jsProperty; 384 break; 385 } 386 } 387 } 388 element.style[jsProperty] = value; 389 }; 390 391 Plugin.prototype.accelerate = function($element) { 392 for (var i = 0, l = $element.length; i < l; i++) { 393 var element = $element[i];
394 this.css(element, 'transform', 'translate3d(0,0,0)'); 395 this.css(element, 'transform-style', 'preserve-3d'); 396 this.css(element, 'backface-visibility', 'hidden'); 397 } 398 }; 399 400 Plugin.prototype.setPosition = function(element, x, y) { 401 x += 'px'; 402 y += 'px'; 403 if (this.transform3DSupport) { 404 this.css(element, 'transform', 'translate3d('+x+','+y+',0)'); 405 } else if (this.transform2DSupport) { 406 this.css(element, 'transform', 'translate('+x+','+y+')'); 407 } else { 408 element.style.left = x; 409 element.style.top = y; 410 } 411 }; 412 413 Plugin.prototype.onOrientationTimer = function(event) { 414 if (this.orientationSupport && this.orientationStatus === 0) { 415 this.disable(); 416 this.orientationSupport = false; 417 this.enable(); 418 } 419 }; 420 421 Plugin.prototype.onCalibrationTimer = function(event) { 422 this.calibrationFlag = true; 423 }; 424 425 Plugin.prototype.onWindowResize = function(event) { 426 this.updateDimensions(); 427 }; 428 429 Plugin.prototype.onAnimationFrame = function() { 430 this.updateBounds(); 431 var dx = this.ix - this.cx; 432 var dy = this.iy - this.cy; 433 if ((Math.abs(dx) > this.calibrationThreshold) || (Math.abs(dy) > this.calibrationThreshold)) { 434 this.queueCalibration(0); 435 } 436 if (this.portrait) { 437 this.mx = this.calibrateX ? dy : this.iy; 438 this.my = this.calibrateY ? dx : this.ix; 439 } else { 440 this.mx = this.calibrateX ? dx : this.ix; 441 this.my = this.calibrateY ? dy : this.iy; 442 } 443 this.mx *= this.ew * (this.scalarX / 100); 444 this.my *= this.eh * (this.scalarY / 100); 445 if (!isNaN(parseFloat(this.limitX))) { 446 this.mx = this.clamp(this.mx, -this.limitX, this.limitX); 447 } 448 if (!isNaN(parseFloat(this.limitY))) { 449 this.my = this.clamp(this.my, -this.limitY, this.limitY); 450 } 451 this.vx += (this.mx - this.vx) * this.frictionX; 452 this.vy += (this.my - this.vy) * this.frictionY; 453 for (var i = 0, l = this.$layers.length; i < l; i++) { 454 var depthX = this.depthsX[i]; 455 var depthY = this.depthsY[i]; 456 var layer = this.$layers[i]; 457 var xOffset = this.vx * (depthX * (this.invertX ? -1 : 1)); 458 var yOffset = this.vy * (depthY * (this.invertY ? -1 : 1)); 459 this.setPosition(layer, xOffset, yOffset); 460 } 461 this.raf = requestAnimationFrame(this.onAnimationFrame); 462 }; 463 464 Plugin.prototype.onDeviceOrientation = function(event) { 465 466 // Validate environment and event properties. 467 if (!this.desktop && event.beta !== null && event.gamma !== null) { 468 469 // Set orientation status. 470 this.orientationStatus = 1; 471 472 // Extract Rotation 473 var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 474 var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 475 476 // Detect Orientation Change 477 var portrait = window.innerHeight > window.innerWidth; 478 if (this.portrait !== portrait) { 479 this.portrait = portrait; 480 this.calibrationFlag = true; 481 } 482 483 // Set Calibration 484 if (this.calibrationFlag) { 485 this.calibrationFlag = false; 486 this.cx = x; 487 this.cy = y; 488 } 489 490 // Set Input 491 this.ix = x; 492 this.iy = y; 493 } 494 }; 495 496 Plugin.prototype.onMouseMove = function(event) { 497 498 // Cache mouse coordinates. 499 var clientX = event.clientX; 500 var clientY = event.clientY; 501 502 // Calculate Mouse Input 503 if (!this.orientationSupport && this.relativeInput) { 504 505 // Clip mouse coordinates inside element bounds. 506 if (this.clipRelativeInput) { 507 clientX = Math.max(clientX, this.ex); 508 clientX = Math.min(clientX, this.ex + this.ew); 509 clientY = Math.max(clientY, this.ey); 510 clientY = Math.min(clientY, this.ey + this.eh); 511 } 512 513 // Calculate input relative to the element.
514 this.ix = (clientX - this.ex - this.ecx) / this.erx; 515 this.iy = (clientY - this.ey - this.ecy) / this.ery; 516 517 } else { 518 519 // Calculate input relative to the window. 520 this.ix = (clientX - this.wcx) / this.wrx; 521 this.iy = (clientY - this.wcy) / this.wry; 522 } 523 }; 524 525 var API = { 526 enable: Plugin.prototype.enable, 527 disable: Plugin.prototype.disable, 528 updateLayers: Plugin.prototype.updateLayers, 529 calibrate: Plugin.prototype.calibrate, 530 friction: Plugin.prototype.friction, 531 invert: Plugin.prototype.invert, 532 scalar: Plugin.prototype.scalar, 533 limit: Plugin.prototype.limit, 534 origin: Plugin.prototype.origin 535 }; 536 537 $.fn[NAME] = function (value) { 538 var args = arguments; 539 return this.each(function () { 540 var $this = $(this); 541 var plugin = $this.data(NAME); 542 if (!plugin) { 543 plugin = new Plugin(this, value); 544 $this.data(NAME, plugin); 545 } 546 if (API[value]) { 547 plugin[value].apply(plugin, Array.prototype.slice.call(args, 1)); 548 } 549 }); 550 }; 551 552})(window.jQuery || window.Zepto, window, document); 553 554/** 555 * Request Animation Frame Polyfill. 556 * @author Tino Zijdel 557 * @author Paul Irish 558 * @see https://gist.github.com/paulirish/1579671 559 */ 560;(function() { 561 562 var lastTime = 0; 563 var vendors = ['ms', 'moz', 'webkit', 'o']; 564 565 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 566 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 567 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 568 } 569 570 if (!window.requestAnimationFrame) { 571 window.requestAnimationFrame = function(callback, element) { 572 var currTime = new Date().getTime(); 573 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 574 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 575 timeToCall); 576 lastTime = currTime + timeToCall; 577 return id; 578 }; 579 } 580 581 if (!window.cancelAnimationFrame) { 582 window.cancelAnimationFrame = function(id) { 583 clearTimeout(id); 584 }; 585 } 586 587}());
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.