You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
17 KiB

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