PageSourceSearch

https://event.unitedindiversity.org/assets/js/parallax.js

js unitedindiversity.org collected 2026-09-26 00:45:46 UTC 15,991 bytes, 521 lines download raw bytes

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