1;/** 2 * Parallax.js 3 * 4 * @author Matthew Wagerfield - @wagerfield 5 * @description Creates a parallax effect between an array of layers, 6 * driving the motion from the gyroscope output of a smartdevice. 7 * If no gyroscope is available, the cursor position is used. 8 */(function(window, document, undefined) { 9 10 // Strict Mode 11 'use strict'; 12 13 // Constants 14 var NAME = 'Parallax'; 15 var MAGIC_NUMBER = 30; 16 var DEFAULTS = { 17 relativeInput: false, 18 clipRelativeInput: false, 19 calibrationThreshold: 100, 20 calibrationDelay: 500, 21 supportDelay: 500, 22 calibrateX: false, 23 calibrateY: true, 24 invertX: true, 25 invertY: true, 26 limitX: false, 27 limitY: false, 28 scalarX: 10.0, 29 scalarY: 10.0, 30 frictionX: 0.1, 31 frictionY: 0.1, 32 originX: 0.5, 33 originY: 0.5 34 }; 35 36 function Parallax(element, options) { 37 38 // DOM Context 39 this.element = element; 40 this.layers = element.getElementsByClassName( 'layer' ); 41 42 // Data Extraction 43 var data = { 44 calibrateX: this.data( this.element, 'calibrate-x' ), 45 calibrateY: this.data( this.element, 'calibrate-y' ), 46 invertX: this.data( this.element, 'invert-x' ), 47 invertY: this.data( this.element, 'invert-y' ), 48 limitX: this.data( this.element, 'limit-x' ), 49 limitY: this.data( this.element, 'limit-y' ), 50 scalarX: this.data( this.element, 'scalar-x' ), 51 scalarY: this.data( this.element, 'scalar-y' ), 52 frictionX: this.data( this.element, 'friction-x' ), 53 frictionY: this.data( this.element, 'friction-y' ), 54 originX: this.data( this.element, 'origin-x' ), 55 originY: this.data( this.element, 'origin-y' ) 56 }; 57 58 // Delete Null Data Values 59 for (var key in data) { 60 if (data[key] === null) { 61 delete data[key]; 62 } 63 } 64 65 // Compose Settings Object 66 this.extend( this, DEFAULTS, options, data ); 67 68 // States 69 this.calibrationTimer = null; 70 this.calibrationFlag = true; 71 this.enabled = false; 72 this.depths = []; 73 this.raf = null; 74 75 // Element Bounds 76 this.bounds = null; 77 this.ex = 0; 78 this.ey = 0; 79 this.ew = 0; 80 this.eh = 0; 81 82 // Element Center 83 this.ecx = 0; 84 this.ecy = 0; 85 86 // Element Range 87 this.erx = 0; 88 this.ery = 0; 89 90 // Calibration 91 this.cx = 0; 92 this.cy = 0; 93 94 // Input 95 this.ix = 0; 96 this.iy = 0; 97 98 // Motion 99 this.mx = 0; 100 this.my = 0; 101 102 // Velocity 103 this.vx = 0; 104 this.vy = 0; 105 106 // Callbacks 107 this.onMouseMove = this.onMouseMove.bind( this ); 108 this.onDeviceOrientation = this.onDeviceOrientation.bind( this ); 109 this.onOrientationTimer = this.onOrientationTimer.bind( this ); 110 this.onCalibrationTimer = this.onCalibrationTimer.bind( this ); 111 this.onAnimationFrame = this.onAnimationFrame.bind( this ); 112 this.onWindowResize = this.onWindowResize.bind( this ); 113 114 // Initialise 115 this.initialise(); 116 } 117 118 Parallax.prototype.extend = function() { 119 if (arguments.length > 1) { 120 var master = arguments[0]; 121 for (var i = 1, l = arguments.length; i < l; i++) { 122 var object = arguments[i]; 123 for (var key in object) { 124 master[key] = object[key]; 125 } 126 } 127 } 128 }; 129 130 Parallax.prototype.data = function(element, name) { 131 return this.deserialize( element.getAttribute( 'data-' + name ) ); 132 }; 133 134 Parallax.prototype.deserialize = function(value) { 135 if (value === 'true') { 136 return true; 137 } else if (value === 'false') { 138 return false; 139 } else if (value === 'null') { 140 return null; 141 } else if ( ! isNaN( parseFloat( value ) ) && isFinite( value )) { 142 return parseFloat( value ); 143 } else { 144 return value; 145 } 146 }; 147 148 Parallax.prototype.camelCase = function(value) { 149 return value.replace( 150 /-+(.)?/g, function(match, character){ 151 return character ? character.toUpperCase() : ''; 152 } 153 ); 154 }; 155 156 Parallax.prototype.transformSupport = function(value) { 157 var element = document.createElement( 'div' ); 158 var propertySupport = false; 159 var propertyValue = null; 160 var featureSupport = false; 161 var cssProperty = null; 162 var jsProperty = null; 163 for (var i = 0, l = this.vendors.length; i < l; i++) { 164 if (this.vendors[i] !== null) { 165 cssProperty = this.vendors[i][0] + 'transform'; 166 jsProperty = this.vendors[i][1] + 'Transform'; 167 } else { 168 cssProperty = 'transform'; 169 jsProperty = 'transform'; 170 } 171 if (element.style[jsProperty] !== undefined) { 172 propertySupport = true; 173 break; 174 } 175 } 176 switch (value) { 177 case '2D': 178 featureSupport = propertySupport; 179 break; 180 case '3D': 181 if (propertySupport) { 182 var body = document.body || document.createElement( 'body' ); 183 var documentElement = document.documentElement; 184 var documentOverflow = documentElement.style.overflow; 185 if ( ! document.body) { 186 documentElement.style.overflow = 'hidden'; 187 documentElement.appendChild( body ); 188 body.style.overflow = 'hidden'; 189 body.style.background = ''; 190 } 191 body.appendChild( element ); 192 element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 193 propertyValue = window.getComputedStyle( element ).getPropertyValue( cssProperty ); 194 featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== 'none'; 195 documentElement.style.overflow = documentOverflow; 196 body.removeChild( element ); 197 } 198 break; 199 } 200 return featureSupport; 201 }; 202 203 Parallax.prototype.ww = null; 204 Parallax.prototype.wh = null; 205 Parallax.prototype.wcx = null; 206 Parallax.prototype.wcy = null; 207 Parallax.prototype.wrx = null; 208 Parallax.prototype.wry = null; 209 Parallax.prototype.portrait = null; 210 Parallax.prototype.desktop = ! navigator.userAgent.match( /(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i );
211 Parallax.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 212 Parallax.prototype.motionSupport = ! ! window.DeviceMotionEvent; 213 Parallax.prototype.orientationSupport = ! ! window.DeviceOrientationEvent; 214 Parallax.prototype.orientationStatus = 0; 215 Parallax.prototype.transform2DSupport = Parallax.prototype.transformSupport( '2D' ); 216 Parallax.prototype.transform3DSupport = Parallax.prototype.transformSupport( '3D' ); 217 Parallax.prototype.propertyCache = {}; 218 219 Parallax.prototype.initialise = function() { 220 221 // Configure Context Styles 222 if (this.transform3DSupport) { 223 this.accelerate( this.element ); 224 } 225 var style = window.getComputedStyle( this.element ); 226 if (style.getPropertyValue( 'position' ) === 'static') { 227 this.element.style.position = 'relative'; 228 } 229 230 // Setup 231 this.updateLayers(); 232 this.updateDimensions(); 233 this.enable(); 234 this.queueCalibration( this.calibrationDelay ); 235 }; 236 237 Parallax.prototype.updateLayers = function() { 238 239 // Cache Layer Elements 240 this.layers = this.element.getElementsByClassName( 'layer' ); 241 this.depths = []; 242 243 // Configure Layer Styles 244 for (var i = 0, l = this.layers.length; i < l; i++) { 245 var layer = this.layers[i]; 246 if (this.transform3DSupport) { 247 this.accelerate( layer ); 248 } 249 layer.style.position = i ? 'absolute' : 'relative'; 250 layer.style.display = 'block'; 251 layer.style.left = 0; 252 layer.style.top = 0; 253 254 // Cache Layer Depth 255 this.depths.push( this.data( layer, 'depth' ) || 0 ); 256 } 257 }; 258 259 Parallax.prototype.updateDimensions = function() { 260 this.ww = window.innerWidth; 261 this.wh = window.innerHeight; 262 this.wcx = this.ww * this.originX; 263 this.wcy = this.wh * this.originY; 264 this.wrx = Math.max( this.wcx, this.ww - this.wcx ); 265 this.wry = Math.max( this.wcy, this.wh - this.wcy ); 266 }; 267 268 Parallax.prototype.updateBounds = function() { 269 this.bounds = this.element.getBoundingClientRect(); 270 this.ex = this.bounds.left; 271 this.ey = this.bounds.top; 272 this.ew = this.bounds.width; 273 this.eh = this.bounds.height; 274 this.ecx = this.ew * this.originX; 275 this.ecy = this.eh * this.originY; 276 this.erx = Math.max( this.ecx, this.ew - this.ecx ); 277 this.ery = Math.max( this.ecy, this.eh - this.ecy ); 278 }; 279 280 Parallax.prototype.queueCalibration = function(delay) { 281 clearTimeout( this.calibrationTimer ); 282 this.calibrationTimer = setTimeout( this.onCalibrationTimer, delay ); 283 }; 284 285 Parallax.prototype.enable = function() { 286 if ( ! this.enabled) { 287 this.enabled = true; 288 if (this.orientationSupport) { 289 this.portrait = null; 290 window.addEventListener( 'deviceorientation', this.onDeviceOrientation ); 291 setTimeout( this.onOrientationTimer, this.supportDelay ); 292 } else { 293 this.cx = 0; 294 this.cy = 0; 295 this.portrait = false; 296 window.addEventListener( 'mousemove', this.onMouseMove ); 297 } 298 window.addEventListener( 'resize', this.onWindowResize ); 299 this.raf = requestAnimationFrame( this.onAnimationFrame ); 300 } 301 }; 302 303 Parallax.prototype.disable = function() { 304 if (this.enabled) { 305 this.enabled = false; 306 if (this.orientationSupport) { 307 window.removeEventListener( 'deviceorientation', this.onDeviceOrientation ); 308 } else { 309 window.removeEventListener( 'mousemove', this.onMouseMove ); 310 } 311 window.removeEventListener( 'resize', this.onWindowResize ); 312 cancelAnimationFrame( this.raf ); 313 } 314 }; 315 316 Parallax.prototype.calibrate = function(x, y) { 317 this.calibrateX = x === undefined ? this.calibrateX : x; 318 this.calibrateY = y === undefined ? this.calibrateY : y; 319 }; 320 321 Parallax.prototype.invert = function(x, y) { 322 this.invertX = x === undefined ? this.invertX : x; 323 this.invertY = y === undefined ? this.invertY : y; 324 }; 325 326 Parallax.prototype.friction = function(x, y) { 327 this.frictionX = x === undefined ? this.frictionX : x; 328 this.frictionY = y === undefined ? this.frictionY : y; 329 }; 330 331 Parallax.prototype.scalar = function(x, y) { 332 this.scalarX = x === undefined ? this.scalarX : x; 333 this.scalarY = y === undefined ? this.scalarY : y; 334 }; 335 336 Parallax.prototype.limit = function(x, y) { 337 this.limitX = x === undefined ? this.limitX : x; 338 this.limitY = y === undefined ? this.limitY : y; 339 }; 340 341 Parallax.prototype.origin = function(x, y) { 342 this.originX = x === undefined ? this.originX : x; 343 this.originY = y === undefined ? this.originY : y; 344 }; 345 346 Parallax.prototype.clamp = function(value, min, max) { 347 value = Math.max( value, min ); 348 value = Math.min( value, max ); 349 return value; 350 }; 351 352 Parallax.prototype.css = function(element, property, value) { 353 var jsProperty = this.propertyCache[property]; 354 if ( ! jsProperty) { 355 for (var i = 0, l = this.vendors.length; i < l; i++) { 356 if (this.vendors[i] !== null) { 357 jsProperty = this.camelCase( this.vendors[i][1] + '-' + property ); 358 } else { 359 jsProperty = property; 360 } 361 if (element.style[jsProperty] !== undefined) { 362 this.propertyCache[property] = jsProperty; 363 break; 364 } 365 } 366 } 367 element.style[jsProperty] = value; 368 }; 369 370 Parallax.prototype.accelerate = function(element) {
371 this.css( element, 'transform', 'translate3d(0,0,0)' ); 372 this.css( element, 'transform-style', 'preserve-3d' ); 373 this.css( element, 'backface-visibility', 'hidden' ); 374 }; 375 376 Parallax.prototype.setPosition = function(element, x, y) { 377 x += 'px'; 378 y += 'px'; 379 if (this.transform3DSupport) { 380 this.css( element, 'transform', 'translate3d(' + x + ',' + y + ',0)' ); 381 } else if (this.transform2DSupport) { 382 this.css( element, 'transform', 'translate(' + x + ',' + y + ')' ); 383 } else { 384 element.style.left = x; 385 element.style.top = y; 386 } 387 }; 388 389 Parallax.prototype.onOrientationTimer = function() { 390 if (this.orientationSupport && this.orientationStatus === 0) { 391 this.disable(); 392 this.orientationSupport = false; 393 this.enable(); 394 } 395 }; 396 397 Parallax.prototype.onCalibrationTimer = function() { 398 this.calibrationFlag = true; 399 }; 400 401 Parallax.prototype.onWindowResize = function() { 402 this.updateDimensions(); 403 }; 404 405 Parallax.prototype.onAnimationFrame = function() { 406 this.updateBounds(); 407 var dx = this.ix - this.cx; 408 var dy = this.iy - this.cy; 409 if ((Math.abs( dx ) > this.calibrationThreshold) || (Math.abs( dy ) > this.calibrationThreshold)) { 410 this.queueCalibration( 0 ); 411 } 412 if (this.portrait) { 413 this.mx = this.calibrateX ? dy : this.iy; 414 this.my = this.calibrateY ? dx : this.ix; 415 } else { 416 this.mx = this.calibrateX ? dx : this.ix; 417 this.my = this.calibrateY ? dy : this.iy; 418 } 419 this.mx *= this.ew * (this.scalarX / 100); 420 this.my *= this.eh * (this.scalarY / 100); 421 if ( ! isNaN( parseFloat( this.limitX ) )) { 422 this.mx = this.clamp( this.mx, -this.limitX, this.limitX ); 423 } 424 if ( ! isNaN( parseFloat( this.limitY ) )) { 425 this.my = this.clamp( this.my, -this.limitY, this.limitY ); 426 } 427 this.vx += (this.mx - this.vx) * this.frictionX; 428 this.vy += (this.my - this.vy) * this.frictionY; 429 for (var i = 0, l = this.layers.length; i < l; i++) { 430 var layer = this.layers[i]; 431 var depth = this.depths[i]; 432 var xOffset = this.vx * depth * (this.invertX ? -1 : 1); 433 var yOffset = this.vy * depth * (this.invertY ? -1 : 1); 434 this.setPosition( layer, xOffset, yOffset ); 435 } 436 this.raf = requestAnimationFrame( this.onAnimationFrame ); 437 }; 438 439 Parallax.prototype.onDeviceOrientation = function(event) { 440 441 // Validate environment and event properties. 442 if ( ! this.desktop && event.beta !== null && event.gamma !== null) { 443 444 // Set orientation status. 445 this.orientationStatus = 1; 446 447 // Extract Rotation 448 var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 449 var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 450 451 // Detect Orientation Change 452 var portrait = this.wh > this.ww; 453 if (this.portrait !== portrait) { 454 this.portrait = portrait; 455 this.calibrationFlag = true; 456 } 457 458 // Set Calibration 459 if (this.calibrationFlag) { 460 this.calibrationFlag = false; 461 this.cx = x; 462 this.cy = y; 463 } 464 465 // Set Input 466 this.ix = x; 467 this.iy = y; 468 } 469 }; 470 471 Parallax.prototype.onMouseMove = function(event) { 472 473 // Cache mouse coordinates. 474 var clientX = event.clientX; 475 var clientY = event.clientY; 476 477 // Calculate Mouse Input 478 if ( ! this.orientationSupport && this.relativeInput) { 479 480 // Clip mouse coordinates inside element bounds. 481 if (this.clipRelativeInput) { 482 clientX = Math.max( clientX, this.ex ); 483 clientX = Math.min( clientX, this.ex + this.ew ); 484 clientY = Math.max( clientY, this.ey ); 485 clientY = Math.min( clientY, this.ey + this.eh ); 486 } 487 488 // Calculate input relative to the element.
489 this.ix = (clientX - this.ex - this.ecx) / this.erx; 490 this.iy = (clientY - this.ey - this.ecy) / this.ery; 491 492 } else { 493 494 // Calculate input relative to the window. 495 this.ix = (clientX - this.wcx) / this.wrx; 496 this.iy = (clientY - this.wcy) / this.wry; 497 } 498 }; 499 500 // Expose Parallax 501 window[NAME] = Parallax; 502 503})( window, document );
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.