{"version":3,"names":[],"mappings":"","sources":["parallax.js"],"sourcesContent":["/**\r\n* Parallax.js\r\n* @author Matthew Wagerfield - @wagerfield, René Roth - mail@reneroth.org\r\n* @description Creates a parallax effect between an array of layers,\r\n* driving the motion from the gyroscope output of a smartdevice.\r\n* If no gyroscope is available, the cursor position is used.\r\n*/\r\n\r\nconst rqAnFr = require('raf')\r\nconst objectAssign = require('object-assign')\r\n\r\nconst helpers = {\r\n propertyCache: {},\r\n vendors: [null, ['-webkit-','webkit'], ['-moz-','Moz'], ['-o-','O'], ['-ms-','ms']],\r\n\r\n clamp(value, min, max) {\r\n return min < max\r\n ? (value < min ? min : value > max ? max : value)\r\n : (value < max ? max : value > min ? min : value)\r\n },\r\n\r\n data(element, name) {\r\n return helpers.deserialize(element.getAttribute('data-'+name))\r\n },\r\n\r\n deserialize(value) {\r\n if (value === 'true') {\r\n return true\r\n } else if (value === 'false') {\r\n return false\r\n } else if (value === 'null') {\r\n return null\r\n } else if (!isNaN(parseFloat(value)) && isFinite(value)) {\r\n return parseFloat(value)\r\n } else {\r\n return value\r\n }\r\n },\r\n\r\n camelCase(value) {\r\n return value.replace(/-+(.)?/g, (match, character) => {\r\n return character ? character.toUpperCase() : ''\r\n })\r\n },\r\n\r\n accelerate(element) {\r\n helpers.css(element, 'transform', 'translate3d(0,0,0) rotate(0.0001deg)')\r\n helpers.css(element, 'transform-style', 'preserve-3d')\r\n helpers.css(element, 'backface-visibility', 'hidden')\r\n },\r\n\r\n transformSupport(value) {\r\n let element = document.createElement('div'),\r\n propertySupport = false,\r\n propertyValue = null,\r\n featureSupport = false,\r\n cssProperty = null,\r\n jsProperty = null\r\n for (let i = 0, l = helpers.vendors.length; i < l; i++) {\r\n if (helpers.vendors[i] !== null) {\r\n cssProperty = helpers.vendors[i][0] + 'transform'\r\n jsProperty = helpers.vendors[i][1] + 'Transform'\r\n } else {\r\n cssProperty = 'transform'\r\n jsProperty = 'transform'\r\n }\r\n if (element.style[jsProperty] !== undefined) {\r\n propertySupport = true\r\n break\r\n }\r\n }\r\n switch(value) {\r\n case '2D':\r\n featureSupport = propertySupport\r\n break\r\n case '3D':\r\n if (propertySupport) {\r\n let body = document.body || document.createElement('body'),\r\n documentElement = document.documentElement,\r\n documentOverflow = documentElement.style.overflow,\r\n isCreatedBody = false\r\n\r\n if (!document.body) {\r\n isCreatedBody = true\r\n documentElement.style.overflow = 'hidden'\r\n documentElement.appendChild(body)\r\n body.style.overflow = 'hidden'\r\n body.style.background = ''\r\n }\r\n\r\n body.appendChild(element)\r\n element.style[jsProperty] = 'translate3d(1px,1px,1px)'\r\n propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty)\r\n featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== 'none'\r\n documentElement.style.overflow = documentOverflow\r\n body.removeChild(element)\r\n\r\n if ( isCreatedBody ) {\r\n body.removeAttribute('style')\r\n body.parentNode.removeChild(body)\r\n }\r\n }\r\n break\r\n }\r\n return featureSupport\r\n },\r\n\r\n css(element, property, value) {\r\n let jsProperty = helpers.propertyCache[property]\r\n if (!jsProperty) {\r\n for (let i = 0, l = helpers.vendors.length; i < l; i++) {\r\n if (helpers.vendors[i] !== null) {\r\n jsProperty = helpers.camelCase(helpers.vendors[i][1] + '-' + property)\r\n } else {\r\n jsProperty = property\r\n }\r\n if (element.style[jsProperty] !== undefined) {\r\n helpers.propertyCache[property] = jsProperty\r\n break\r\n }\r\n }\r\n }\r\n element.style[jsProperty] = value\r\n }\r\n\r\n}\r\n\r\nconst MAGIC_NUMBER = 30,\r\n DEFAULTS = {\r\n relativeInput: false,\r\n clipRelativeInput: false,\r\n inputElement: null,\r\n hoverOnly: false,\r\n calibrationThreshold: 100,\r\n calibrationDelay: 500,\r\n supportDelay: 500,\r\n calibrateX: false,\r\n calibrateY: true,\r\n invertX: true,\r\n invertY: true,\r\n limitX: false,\r\n limitY: false,\r\n scalarX: 10.0,\r\n scalarY: 10.0,\r\n frictionX: 0.1,\r\n frictionY: 0.1,\r\n originX: 0.5,\r\n originY: 0.5,\r\n pointerEvents: false,\r\n precision: 1,\r\n onReady: null,\r\n selector: null\r\n }\r\n\r\nclass Parallax {\r\n constructor(element, options) {\r\n\r\n this.element = element\r\n\r\n const data = {\r\n calibrateX: helpers.data(this.element, 'calibrate-x'),\r\n calibrateY: helpers.data(this.element, 'calibrate-y'),\r\n invertX: helpers.data(this.element, 'invert-x'),\r\n invertY: helpers.data(this.element, 'invert-y'),\r\n limitX: helpers.data(this.element, 'limit-x'),\r\n limitY: helpers.data(this.element, 'limit-y'),\r\n scalarX: helpers.data(this.element, 'scalar-x'),\r\n scalarY: helpers.data(this.element, 'scalar-y'),\r\n frictionX: helpers.data(this.element, 'friction-x'),\r\n frictionY: helpers.data(this.element, 'friction-y'),\r\n originX: helpers.data(this.element, 'origin-x'),\r\n originY: helpers.data(this.element, 'origin-y'),\r\n pointerEvents: helpers.data(this.element, 'pointer-events'),\r\n precision: helpers.data(this.element, 'precision'),\r\n relativeInput: helpers.data(this.element, 'relative-input'),\r\n clipRelativeInput: helpers.data(this.element, 'clip-relative-input'),\r\n hoverOnly: helpers.data(this.element, 'hover-only'),\r\n inputElement: document.querySelector(helpers.data(this.element, 'input-element')),\r\n selector: helpers.data(this.element, 'selector')\r\n }\r\n\r\n for (let key in data) {\r\n if (data[key] === null) {\r\n delete data[key]\r\n }\r\n }\r\n\r\n objectAssign(this, DEFAULTS, data, options)\r\n\r\n if(!this.inputElement) {\r\n this.inputElement = this.element\r\n }\r\n\r\n this.calibrationTimer = null\r\n this.calibrationFlag = true\r\n this.enabled = false\r\n this.depthsX = []\r\n this.depthsY = []\r\n this.raf = null\r\n\r\n this.bounds = null\r\n this.elementPositionX = 0\r\n this.elementPositionY = 0\r\n this.elementWidth = 0\r\n this.elementHeight = 0\r\n\r\n this.elementCenterX = 0\r\n this.elementCenterY = 0\r\n\r\n this.elementRangeX = 0\r\n this.elementRangeY = 0\r\n\r\n this.calibrationX = 0\r\n this.calibrationY = 0\r\n\r\n this.inputX = 0\r\n this.inputY = 0\r\n\r\n this.motionX = 0\r\n this.motionY = 0\r\n\r\n this.velocityX = 0\r\n this.velocityY = 0\r\n\r\n this.onMouseMove = this.onMouseMove.bind(this)\r\n this.onDeviceOrientation = this.onDeviceOrientation.bind(this)\r\n this.onDeviceMotion = this.onDeviceMotion.bind(this)\r\n this.onOrientationTimer = this.onOrientationTimer.bind(this)\r\n this.onMotionTimer = this.onMotionTimer.bind(this)\r\n this.onCalibrationTimer = this.onCalibrationTimer.bind(this)\r\n this.onAnimationFrame = this.onAnimationFrame.bind(this)\r\n this.onWindowResize = this.onWindowResize.bind(this)\r\n\r\n this.windowWidth = null\r\n this.windowHeight = null\r\n this.windowCenterX = null\r\n this.windowCenterY = null\r\n this.windowRadiusX = null\r\n this.windowRadiusY = null\r\n this.portrait = false\r\n this.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i)\r\n this.motionSupport = !!window.DeviceMotionEvent && !this.desktop\r\n this.orientationSupport = !!window.DeviceOrientationEvent && !this.desktop\r\n this.orientationStatus = 0\r\n this.motionStatus = 0\r\n\r\n this.initialise()\r\n }\r\n\r\n initialise() {\r\n if (this.transform2DSupport === undefined) {\r\n this.transform2DSupport = helpers.transformSupport('2D')\r\n this.transform3DSupport = helpers.transformSupport('3D')\r\n }\r\n\r\n // Configure Context Styles\r\n if (this.transform3DSupport) {\r\n helpers.accelerate(this.element)\r\n }\r\n\r\n let style = window.getComputedStyle(this.element)\r\n if (style.getPropertyValue('position') === 'static') {\r\n this.element.style.position = 'relative'\r\n }\r\n\r\n // Pointer events\r\n if(!this.pointerEvents) {\r\n this.element.style.pointerEvents = 'none'\r\n }\r\n\r\n // Setup\r\n this.updateLayers()\r\n this.updateDimensions()\r\n this.enable()\r\n this.queueCalibration(this.calibrationDelay)\r\n }\r\n\r\n doReadyCallback() {\r\n if(this.onReady) {\r\n this.onReady()\r\n }\r\n }\r\n\r\n updateLayers() {\r\n if(this.selector) {\r\n this.layers = this.element.querySelectorAll(this.selector)\r\n } else {\r\n this.layers = this.element.children\r\n }\r\n\r\n if(!this.layers.length) {\r\n console.warn('ParallaxJS: Your scene does not have any layers.')\r\n }\r\n\r\n this.depthsX = []\r\n this.depthsY = []\r\n\r\n for (let index = 0; index < this.layers.length; index++) {\r\n let layer = this.layers[index]\r\n\r\n if (this.transform3DSupport) {\r\n helpers.accelerate(layer)\r\n }\r\n\r\n layer.style.position = index ? 'absolute' : 'relative'\r\n layer.style.display = 'block'\r\n layer.style.left = 0\r\n layer.style.top = 0\r\n\r\n let depth = helpers.data(layer, 'depth') || 0\r\n this.depthsX.push(helpers.data(layer, 'depth-x') || depth)\r\n this.depthsY.push(helpers.data(layer, 'depth-y') || depth)\r\n }\r\n }\r\n\r\n updateDimensions() {\r\n this.windowWidth = window.innerWidth\r\n this.windowHeight = window.innerHeight\r\n this.windowCenterX = this.windowWidth * this.originX\r\n this.windowCenterY = this.windowHeight * this.originY\r\n this.windowRadiusX = Math.max(this.windowCenterX, this.windowWidth - this.windowCenterX)\r\n this.windowRadiusY = Math.max(this.windowCenterY, this.windowHeight - this.windowCenterY)\r\n }\r\n\r\n updateBounds() {\r\n this.bounds = this.inputElement.getBoundingClientRect()\r\n this.elementPositionX = this.bounds.left\r\n this.elementPositionY = this.bounds.top\r\n this.elementWidth = this.bounds.width\r\n this.elementHeight = this.bounds.height\r\n this.elementCenterX = this.elementWidth * this.originX\r\n this.elementCenterY = this.elementHeight * this.originY\r\n this.elementRangeX = Math.max(this.elementCenterX, this.elementWidth - this.elementCenterX)\r\n this.elementRangeY = Math.max(this.elementCenterY, this.elementHeight - this.elementCenterY)\r\n }\r\n\r\n queueCalibration(delay) {\r\n clearTimeout(this.calibrationTimer)\r\n this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay)\r\n }\r\n\r\n enable() {\r\n if (this.enabled) {\r\n return\r\n }\r\n this.enabled = true\r\n\r\n if (this.orientationSupport) {\r\n this.portrait = false\r\n window.addEventListener('deviceorientation', this.onDeviceOrientation)\r\n this.detectionTimer = setTimeout(this.onOrientationTimer, this.supportDelay)\r\n } else if (this.motionSupport) {\r\n this.portrait = false\r\n window.addEventListener('devicemotion', this.onDeviceMotion)\r\n this.detectionTimer = setTimeout(this.onMotionTimer, this.supportDelay)\r\n } else {\r\n this.calibrationX = 0\r\n this.calibrationY = 0\r\n this.portrait = false\r\n window.addEventListener('mousemove', this.onMouseMove)\r\n this.doReadyCallback()\r\n }\r\n\r\n window.addEventListener('resize', this.onWindowResize)\r\n this.raf = rqAnFr(this.onAnimationFrame)\r\n }\r\n\r\n disable() {\r\n if (!this.enabled) {\r\n return\r\n }\r\n this.enabled = false\r\n\r\n if (this.orientationSupport) {\r\n window.removeEventListener('deviceorientation', this.onDeviceOrientation)\r\n } else if (this.motionSupport) {\r\n window.removeEventListener('devicemotion', this.onDeviceMotion)\r\n } else {\r\n window.removeEventListener('mousemove', this.onMouseMove)\r\n }\r\n\r\n window.removeEventListener('resize', this.onWindowResize)\r\n rqAnFr.cancel(this.raf)\r\n }\r\n\r\n calibrate(x, y) {\r\n this.calibrateX = x === undefined ? this.calibrateX : x\r\n this.calibrateY = y === undefined ? this.calibrateY : y\r\n }\r\n\r\n invert(x, y) {\r\n this.invertX = x === undefined ? this.invertX : x\r\n this.invertY = y === undefined ? this.invertY : y\r\n }\r\n\r\n friction(x, y) {\r\n this.frictionX = x === undefined ? this.frictionX : x\r\n this.frictionY = y === undefined ? this.frictionY : y\r\n }\r\n\r\n scalar(x, y) {\r\n this.scalarX = x === undefined ? this.scalarX : x\r\n this.scalarY = y === undefined ? this.scalarY : y\r\n }\r\n\r\n limit(x, y) {\r\n this.limitX = x === undefined ? this.limitX : x\r\n this.limitY = y === undefined ? this.limitY : y\r\n }\r\n\r\n origin(x, y) {\r\n this.originX = x === undefined ? this.originX : x\r\n this.originY = y === undefined ? this.originY : y\r\n }\r\n\r\n setInputElement(element) {\r\n this.inputElement = element\r\n this.updateDimensions()\r\n }\r\n\r\n setPosition(element, x, y) {\r\n x = x.toFixed(this.precision) + 'px'\r\n y = y.toFixed(this.precision) + 'px'\r\n if (this.transform3DSupport) {\r\n helpers.css(element, 'transform', 'translate3d(' + x + ',' + y + ',0)')\r\n } else if (this.transform2DSupport) {\r\n helpers.css(element, 'transform', 'translate(' + x + ',' + y + ')')\r\n } else {\r\n element.style.left = x\r\n element.style.top = y\r\n }\r\n }\r\n\r\n onOrientationTimer() {\r\n if (this.orientationSupport && this.orientationStatus === 0) {\r\n this.disable()\r\n this.orientationSupport = false\r\n this.enable()\r\n } else {\r\n this.doReadyCallback()\r\n }\r\n }\r\n\r\n onMotionTimer() {\r\n if (this.motionSupport && this.motionStatus === 0) {\r\n this.disable()\r\n this.motionSupport = false\r\n this.enable()\r\n } else {\r\n this.doReadyCallback()\r\n }\r\n }\r\n\r\n onCalibrationTimer() {\r\n this.calibrationFlag = true\r\n }\r\n\r\n onWindowResize() {\r\n this.updateDimensions()\r\n }\r\n\r\n onAnimationFrame() {\r\n this.updateBounds()\r\n let calibratedInputX = this.inputX - this.calibrationX,\r\n calibratedInputY = this.inputY - this.calibrationY\r\n if ((Math.abs(calibratedInputX) > this.calibrationThreshold) || (Math.abs(calibratedInputY) > this.calibrationThreshold)) {\r\n this.queueCalibration(0)\r\n }\r\n if (this.portrait) {\r\n this.motionX = this.calibrateX ? calibratedInputY : this.inputY\r\n this.motionY = this.calibrateY ? calibratedInputX : this.inputX\r\n } else {\r\n this.motionX = this.calibrateX ? calibratedInputX : this.inputX\r\n this.motionY = this.calibrateY ? calibratedInputY : this.inputY\r\n }\r\n this.motionX *= this.elementWidth * (this.scalarX / 100)\r\n this.motionY *= this.elementHeight * (this.scalarY / 100)\r\n if (!isNaN(parseFloat(this.limitX))) {\r\n this.motionX = helpers.clamp(this.motionX, -this.limitX, this.limitX)\r\n }\r\n if (!isNaN(parseFloat(this.limitY))) {\r\n this.motionY = helpers.clamp(this.motionY, -this.limitY, this.limitY)\r\n }\r\n this.velocityX += (this.motionX - this.velocityX) * this.frictionX\r\n this.velocityY += (this.motionY - this.velocityY) * this.frictionY\r\n for (let index = 0; index < this.layers.length; index++) {\r\n let layer = this.layers[index],\r\n depthX = this.depthsX[index],\r\n depthY = this.depthsY[index],\r\n xOffset = this.velocityX * (depthX * (this.invertX ? -1 : 1)),\r\n yOffset = this.velocityY * (depthY * (this.invertY ? -1 : 1))\r\n this.setPosition(layer, xOffset, yOffset)\r\n }\r\n this.raf = rqAnFr(this.onAnimationFrame)\r\n }\r\n\r\n rotate(beta, gamma){\r\n // Extract Rotation\r\n let x = (beta || 0) / MAGIC_NUMBER, // -90 :: 90\r\n y = (gamma || 0) / MAGIC_NUMBER // -180 :: 180\r\n\r\n // Detect Orientation Change\r\n let portrait = this.windowHeight > this.windowWidth\r\n if (this.portrait !== portrait) {\r\n this.portrait = portrait\r\n this.calibrationFlag = true\r\n }\r\n\r\n if (this.calibrationFlag) {\r\n this.calibrationFlag = false\r\n this.calibrationX = x\r\n this.calibrationY = y\r\n }\r\n\r\n this.inputX = x\r\n this.inputY = y\r\n }\r\n\r\n onDeviceOrientation(event) {\r\n let beta = event.beta\r\n let gamma = event.gamma\r\n if (beta !== null && gamma !== null) {\r\n this.orientationStatus = 1\r\n this.rotate(beta, gamma)\r\n }\r\n }\r\n\r\n onDeviceMotion(event) {\r\n let beta = event.rotationRate.beta\r\n let gamma = event.rotationRate.gamma\r\n if (beta !== null && gamma !== null) {\r\n this.motionStatus = 1\r\n this.rotate(beta, gamma)\r\n }\r\n }\r\n\r\n onMouseMove(event) {\r\n let clientX = event.clientX,\r\n clientY = event.clientY\r\n\r\n // reset input to center if hoverOnly is set and we're not hovering the element\r\n if(this.hoverOnly &&\r\n ((clientX < this.elementPositionX || clientX > this.elementPositionX + this.elementWidth) ||\r\n (clientY < this.elementPositionY || clientY > this.elementPositionY + this.elementHeight))) {\r\n this.inputX = 0\r\n this.inputY = 0\r\n return\r\n }\r\n\r\n if (this.relativeInput) {\r\n // Clip mouse coordinates inside element bounds.\r\n if (this.clipRelativeInput) {\r\n clientX = Math.max(clientX, this.elementPositionX)\r\n clientX = Math.min(clientX, this.elementPositionX + this.elementWidth)\r\n clientY = Math.max(clientY, this.elementPositionY)\r\n clientY = Math.min(clientY, this.elementPositionY + this.elementHeight)\r\n }\r\n // Calculate input relative to the element.\r\n if(this.elementRangeX && this.elementRangeY) {\r\n this.inputX = (clientX - this.elementPositionX - this.elementCenterX) / this.elementRangeX\r\n this.inputY = (clientY - this.elementPositionY - this.elementCenterY) / this.elementRangeY\r\n }\r\n } else {\r\n // Calculate input relative to the window.\r\n if(this.windowRadiusX && this.windowRadiusY) {\r\n this.inputX = (clientX - this.windowCenterX) / this.windowRadiusX\r\n this.inputY = (clientY - this.windowCenterY) / this.windowRadiusY\r\n }\r\n }\r\n }\r\n\r\n destroy() {\r\n this.disable()\r\n\r\n clearTimeout(this.calibrationTimer)\r\n clearTimeout(this.detectionTimer)\r\n\r\n this.element.removeAttribute('style')\r\n for (let index = 0; index < this.layers.length; index++) {\r\n this.layers[index].removeAttribute('style')\r\n }\r\n\r\n delete this.element\r\n delete this.layers\r\n }\r\n\r\n version() {\r\n return '3.1.0'\r\n }\r\n\r\n}\r\n\r\nmodule.exports = Parallax\r\n"],"file":"../../../../../parallax.js"}