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