├── .gitignore ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── build ├── build.js └── package.json ├── deploy ├── headtrackr.js ├── headtrackr.min.js ├── jquery.parallax.js ├── jquery.parallax.min.js ├── parallax.js └── parallax.min.js ├── examples ├── images │ ├── layer1.png │ ├── layer2.png │ ├── layer3.png │ ├── layer4.png │ ├── layer5.png │ ├── layer6.png │ └── layers.psd ├── jquery.headtrackr.html ├── jquery.html ├── scripts │ ├── jquery.js │ └── zepto.js ├── simple.headtrackr.html ├── simple.html └── styles │ └── styles.css ├── package.json └── source ├── jquery.parallax.js ├── parallax.js └── requestAnimationFrame.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | build/node_modules 3 | assets 4 | 5 | /node_modules 6 | /nbproject -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Load Grunt tasks declared in the package.json file 4 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 5 | 6 | // Configure Grunt 7 | grunt.initConfig({ 8 | 9 | // grunt-contrib-connect will serve the files of the project 10 | // on specified port and hostname 11 | connect: { 12 | all: { 13 | options: { 14 | port: 9000, 15 | hostname: "0.0.0.0", 16 | keepalive: true, 17 | base: "" 18 | } 19 | } 20 | }, 21 | 22 | // grunt-open will open your browser at the project's URL 23 | open: { 24 | debug: { 25 | path: 'http://localhost:<%= connect.all.options.port%>/examples/simple.headtrackr.html' 26 | }, 27 | release: { 28 | path: 'http://localhost:<%= connect.all.options.port%>/' 29 | } 30 | } 31 | 32 | }); 33 | 34 | grunt.registerTask('server', ['open:debug', 'connect:all']); 35 | grunt.registerTask('server-debug', ['open:debug', 'connect:all']); 36 | grunt.registerTask('server-release', ['open:release', 'connect:all']); 37 | 38 | }; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2013 Christophe Rosset - @topheman 6 | // Copyright (C) 2013 Matthew Wagerfield - @mwagerfield 7 | // 8 | // Permission is hereby granted, free of charge, to any 9 | // person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the 11 | // Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, 13 | // sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do 15 | // so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice 18 | // shall be included in all copies or substantial portions 19 | // of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 22 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 26 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 27 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 29 | // OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | //============================================================ 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parallax.js - *now with headtracking support* 2 | 3 | Simple, lightweight **Parallax Engine** that reacts to the orientation of a smart device. Where no gyroscope or motion detection hardware is available, the position of the cursor is used instead. 4 | 5 | This version is a fork by [Christophe Rosset (@topheman)](https://github.com/topheman), from the original [Parallax.js by Matthew Wagerfield](https://github.com/wagerfield/parallax). It adds **headtracking support** to the original library. See **[Headtracking Support](#headtracking-support)** section for more infos. 6 | 7 | Check out this **[demo](https://topheman.github.io/parallax/)** to see it in action ! 8 | 9 | ## Setup 10 | 11 | Simply create a list of elements giving each item that you want to move within 12 | your parallax scene a class of `layer` and a `data-depth` attribute specifying 13 | its depth within the scene. A depth of **0** will cause the layer to remain 14 | stationary, and a depth of **1** will cause the layer to move by the total 15 | effect of the calculated motion. Values inbetween **0** and **1** will cause the 16 | layer to move by an amount relative to the supplied ratio. 17 | 18 | ```html 19 | 27 | ``` 28 | 29 | To kickoff a **Parallax** scene, simply select your parent DOM Element and pass 30 | it to the **Parallax** constructor. 31 | 32 | ```javascript 33 | var scene = document.getElementById('scene'); 34 | var parallax = new Parallax(scene); 35 | ``` 36 | 37 | ## Behaviours 38 | 39 | There are a number of behaviours that you can setup for any given **Parallax** 40 | instance. These behaviours can either be specified in the markup via data 41 | attributes or in JavaScript via the constructor and API. 42 | 43 | | Behavior | Values | Description | 44 | | ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------ | 45 | | `calibrate-x` | `true` or `false` | Specifies whether or not to cache & calculate the motion relative to the initial `x` axis value on initialisation. | 46 | | `calibrate-y` | `true` or `false` | Specifies whether or not to cache & calculate the motion relative to the initial `y` axis value on initialisation. | 47 | | `invert-x` | `true` or `false` | `true` moves layers in opposition to the device motion, `false` slides them away. | 48 | | `invert-y` | `true` or `false` | `true` moves layers in opposition to the device motion, `false` slides them away. | 49 | | `limit-x` | `number` or `false` | A numeric value limits the total range of motion in `x`, `false` allows layers to move with complete freedom. | 50 | | `limit-y` | `number` or `false` | A numeric value limits the total range of motion in `y`, `false` allows layers to move with complete freedom. | 51 | | `scalar-x` | `number` | Multiplies the input motion by this value, increasing or decreasing the sensitivity of the layer motion. | 52 | | `scalar-y` | `number` | Multiplies the input motion by this value, increasing or decreasing the sensitivity of the layer motion. | 53 | | `friction-x` | `number` `0 - 1` | The amount of friction the layers experience. This essentially adds some easing to the layer motion. | 54 | | `friction-y` | `number` `0 - 1` | The amount of friction the layers experience. This essentially adds some easing to the layer motion. | 55 | 56 | In addition to the behaviours described above, there are **two** methods `enable()` 57 | and `disable()` that *activate* and *deactivate* the **Parallax** instance respectively. 58 | 59 | ### Behaviors: Data Attributes Example 60 | 61 | ```html 62 | 80 | ``` 81 | 82 | ### Behaviors: Constructor Object Example 83 | 84 | ```javascript 85 | var scene = document.getElementById('scene'); 86 | var parallax = new Parallax(scene, { 87 | calibrateX: false, 88 | calibrateY: true, 89 | invertX: false, 90 | invertY: true, 91 | limitX: false, 92 | limitY: 10, 93 | scalarX: 2, 94 | scalarY: 8, 95 | frictionX: 0.2, 96 | frictionY: 0.8 97 | }); 98 | ``` 99 | 100 | ### Behaviors: API Example 101 | 102 | ```javascript 103 | var scene = document.getElementById('scene'); 104 | var parallax = new Parallax(scene); 105 | parallax.enable(); 106 | parallax.disable(); 107 | parallax.calibrate(false, true); 108 | parallax.invert(false, true); 109 | parallax.limit(false, 10); 110 | parallax.scalar(2, 8); 111 | parallax.friction(0.2, 0.8); 112 | ``` 113 | 114 | ## jQuery 115 | 116 | If you're using **[jQuery][jquery]** or **[Zepto][zepto]** and would prefer to 117 | use **Parallax.js** as a plugin, you're in luck! 118 | 119 | ```javascript 120 | $('#scene').parallax(); 121 | ``` 122 | 123 | ### jQuery: Passing Options 124 | 125 | ```javascript 126 | $('#scene').parallax({ 127 | calibrateX: false, 128 | calibrateY: true, 129 | invertX: false, 130 | invertY: true, 131 | limitX: false, 132 | limitY: 10, 133 | scalarX: 2, 134 | scalarY: 8, 135 | frictionX: 0.2, 136 | frictionY: 0.8 137 | }); 138 | ``` 139 | ### jQuery: API 140 | 141 | ```javascript 142 | var $scene = $('#scene').parallax(); 143 | $scene.parallax('enable'); 144 | $scene.parallax('disable'); 145 | $scene.parallax('calibrate', false, true); 146 | $scene.parallax('invert', false, true); 147 | $scene.parallax('limit', false, 10); 148 | $scene.parallax('scalar', 2, 8); 149 | $scene.parallax('friction', 0.2, 0.8); 150 | ``` 151 | 152 | ## Headtracking support 153 | 154 | Use Parallax.js just as you would. But rather than devicemotion, use headtracking with your webcam. Thanks to Audun Mathias Øygard for his [headtrackr.js](https://github.com/auduno/headtrackr) library. 155 | 156 | Don't forget to insert the `headtrackr.js` or `headtrackr.min.js` script in your page or specify it in the options in `headtrackr-script-location` (for lazy load). 157 | 158 | Some getUserMedia feature detection is done, if you're not on firefox or chrome (which implement the feature at the moment), the headtrackr script won't even be downloaded and you'll have a message telling you that headtracking won't work and falls back to normal behaviour. 159 | 160 | You can test the headtracking version of the simple demo here : 161 | 162 | * [with Parallax](https://rawgithub.com/topheman/parallax/master/examples/simple.headtrackr.html) 163 | * [with the Parallax jQuery plugin](https://rawgithub.com/topheman/parallax/master/examples/jquery.headtrackr.html) 164 | 165 | See also the real **[demo](https://topheman.github.io/parallax/)**. 166 | 167 | 168 | ### Headtracking - Behaviors: Constructor Object Example 169 | 170 | ```javascript 171 | var scene = document.getElementById('scene'); 172 | var parallax = new Parallax(scene, { 173 | // … set any of the usual parallax behaviours, then set the headtrackr ones 174 | headtrackr:true, 175 | headtrackrPreferDeviceMotion:false,//if on a device that supports both getUserMedia and accelerometer, false -> will use headtrackr, true -> will use DeviceMotion (true by default to keep with parallax.js) 176 | scalarX: 15.0, 177 | scalarY: 15.0, 178 | // headtrackrDisplayVideo:true, //no need if you set headtrackrDebugView at true 179 | headtrackrDebugView: true, 180 | invertX:false, 181 | invertY:false, 182 | headtrackrScriptLocation: "../deploy/headtrackr.min.js", 183 | headtrackrOnBeforeCameraAccess: function(){ 184 | console.log('Trying to access to your webcam - this is where you could display a "please allow your webcam" message for example'); 185 | }, 186 | headtrackrOnCameraFound: function(){ 187 | var headtrackerMessageDiv = document.getElementById('headtrackerMessageDiv'); 188 | //at this time, you're sure the user clicked on "allow" and the webcam is rolling (you can override the inline styles of the headtrackr div message) 189 | headtrackerMessageDiv.style.top = "20%"; 190 | }, 191 | headtrackrNoGetUserMediaCallback: function(){ 192 | console.log('Write your own message function like a modal or anything better than the ugly message I made up … ;-)'); 193 | } 194 | }); 195 | ``` 196 | 197 | ### Headtracking - Behaviors: API Example 198 | 199 | ```javascript 200 | var scene = document.getElementById('scene'); 201 | var parallax = new Parallax(scene); 202 | parallax.headtrackrScalar(2, 8); 203 | ``` 204 | 205 | ### Headtracking - jQuery: Passing Options 206 | 207 | ```javascript 208 | $('#scene').parallax({ 209 | // … set any of the usual parallax behaviours, then set the headtrackr ones 210 | headtrackr:true, 211 | headtrackrPreferDeviceMotion:false,//if on a device that supports both getUserMedia and accelerometer, false -> will use headtrackr, true -> will use DeviceMotion (true by default to keep with parallax.js) 212 | scalarX: 15.0, 213 | scalarY: 15.0, 214 | // headtrackrDisplayVideo:true, //no need if you set headtrackrDebugView at true 215 | headtrackrDebugView: true, 216 | invertX:false, 217 | invertY:false, 218 | headtrackrScriptLocation: "../deploy/headtrackr.min.js", 219 | headtrackrOnBeforeCameraAccess: function(){ 220 | console.log('Trying to access to your webcam - this is where you could display a "please allow your webcam" message for example'); 221 | }, 222 | headtrackrOnCameraFound: function(){ 223 | var headtrackerMessageDiv = document.getElementById('headtrackerMessageDiv'); 224 | //at this time, you're sure the user clicked on "allow" and the webcam is rolling (you can override the inline styles of the headtrackr div message) 225 | headtrackerMessageDiv.style.top = "20%"; 226 | }, 227 | headtrackrNoGetUserMediaCallback: function(){ 228 | console.log('Write your own message function like a modal or anything better than the ugly message I made up … ;-)'); 229 | } 230 | }); 231 | ``` 232 | 233 | ### Headtracking - jQuery: API Example 234 | 235 | ```javascript 236 | var $scene = $('#scene').parallax(); 237 | $scene.parallax('headtrackrScalar', 2, 8); 238 | ``` 239 | 240 | ### Headtracking behaviours 241 | 242 | Keep in mind that to activate headtracking **you only have to put `headtrackr` option to `true`**, the other parameters are optional (except `headtrackr-script-location`, unless you already have included the `headtrackr.js` script). 243 | 244 | | Behavior | Values | Description | 245 | | -------------------------------- | ------------------- | -----------------------------------------------------------------------------------------------------------------------------------------------------------------| 246 | | `headtrackr` | `true` or `false` | Activates headtracking in parallax (you will be asked to allow the access to your webcam) - it disables devicemotion events and uses mouse if headtracking fails | 247 | | `headtrackr-display-video` | `true` or `false` | Specifies whether or not to display the webcam video stream (`false` by default) | 248 | | `headtrackr-scalar-x` | `number` | Multiplies the input motion given by headtrackr by this value (will be affected by scalar-x) | 249 | | `headtrackr-scalar-y` | `number` | Multiplies the input motion given by headtrackr by this value (will be affected by scalar-y) | 250 | | `headtrackr-debug-view` | `true` or `false` | Specifies whether or not to display the webcam video stream, with a box over your head (great for debugging) (`false` by default) | 251 | | `headtrackr-script-location` | `string` | Location of the `headtrackr.js` or `headtrackr.min.js` script (lazy load for feature detection) | 252 | | `headtrackr-prefer-device-motion`| `true` or `false` | If both accelerometer and headtracking are supported (like on tablets with chrome that support getUserMedia), choose which one use, `true` by default | 253 | 254 | ## iOS 255 | 256 | If you are writing a **native iOS application** and would like to use **parallax.js** 257 | within a `UIWebView`, you will need to do a little bit of work to get it running. 258 | 259 | `UIWebView` no longer automatically receives the `deviceorientation` event, so 260 | your native application must intercept the events from the gyroscope and reroute 261 | them to the `UIWebView`: 262 | 263 | 1. Include the **CoreMotion** framework `#import ` 264 | and create a reference to the **UIWebView** `@property(nonatomic, strong) IBOutlet UIWebView *parallaxWebView;` 265 | 2. Add a property to the app delegate (or controller that will own the **UIWebView**) 266 | `@property(nonatomic, strong) CMMotionManager *motionManager;` 267 | 3. Finally, make the following calls: 268 | 269 | ```Objective-C 270 | self.motionManager = [[CMMotionManager alloc] init]; 271 | 272 | if (self.motionManager.isGyroAvailable && !self.motionManager.isGyroActive) { 273 | 274 | [self.motionManager setGyroUpdateInterval:0.5f]; // Set the event update frequency (in seconds) 275 | 276 | [self.motionManager startGyroUpdatesToQueue:NSOperationQueue.mainQueue 277 | withHandler:^(CMGyroData *gyroData, NSError *error) { 278 | 279 | NSString *js = [NSString stringWithFormat:@"parallax.onDeviceOrientation({beta:%f, gamma:%f})", gyroData.rotationRate.x, gyroData.rotationRate.y]; 280 | 281 | [self.parallaxWebView stringByEvaluatingJavaScriptFromString:js]; 282 | 283 | }]; 284 | } 285 | ``` 286 | 287 | ## Build 288 | 289 | ``` 290 | cd build 291 | npm install 292 | node build.js 293 | ``` 294 | 295 | ## Authors 296 | 297 | * Matthew Wagerfield: [@mwagerfield](http://twitter.com/mwagerfield) 298 | * Christophe Rosset: [@topheman](http://twitter.com/topheman) 299 | 300 | ## License 301 | 302 | Licensed under [MIT][mit]. Enjoy. 303 | 304 | [demo]: https://topheman.github.io/parallax/ 305 | [mit]: http://www.opensource.org/licenses/mit-license.php 306 | [jquery]: http://jquery.com/ 307 | [zepto]: http://zeptojs.com/ 308 | [headtrackr]: https://github.com/auduno/headtrackr 309 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | var fs = require('fs'); 3 | var uglify = require('uglify-js'); 4 | 5 | // Settings 6 | var FILE_ENCODING = 'utf-8', 7 | LICENSE = '../LICENSE.md', 8 | SOURCE_DIR = '../source', 9 | OUTPUT_DIR = '../deploy', 10 | MAIN_SCRIPTS = [ 11 | 'parallax.js', 12 | 'requestAnimationFrame.js' 13 | ], 14 | JQUERY_SCRIPTS = [ 15 | 'jquery.parallax.js', 16 | 'requestAnimationFrame.js' 17 | ]; 18 | 19 | // Returns a path string from a list of path segments 20 | function getPath() { 21 | return [].join.call(arguments, '/'); 22 | } 23 | 24 | // Processes the specified files, creating a concatenated and a concatenated and minified output 25 | function process(scripts, name) { 26 | var joined, license, unminified, minified; 27 | 28 | // Read the license 29 | license = fs.readFileSync(LICENSE, FILE_ENCODING); 30 | 31 | // Join the contents of all sources files into a single string 32 | joined = scripts.map(function(file) { 33 | return fs.readFileSync(getPath(SOURCE_DIR, file), FILE_ENCODING); 34 | }).join('\n'); 35 | 36 | // Unminified 37 | unminified = license + '\n' + joined; 38 | 39 | // Minified 40 | minified = license + uglify.minify(joined, {fromString: true}).code; 41 | 42 | // Write out the concatenated file 43 | fs.writeFileSync(getPath(OUTPUT_DIR, name + '.js'), unminified, FILE_ENCODING); 44 | 45 | // Write out the minfied file 46 | fs.writeFileSync(getPath(OUTPUT_DIR, name + '.min.js'), minified, FILE_ENCODING); 47 | 48 | console.log('build complete'); 49 | } 50 | 51 | // GO! 52 | process(MAIN_SCRIPTS, 'parallax'); 53 | process(JQUERY_SCRIPTS, 'jquery.parallax'); 54 | -------------------------------------------------------------------------------- /build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallax" 3 | , "description": "Simple, lightweight parallax engine that reacts to the orientation of a smart device" 4 | , "author": "Matthew Wagerfield <@mwagerfield>" 5 | , "version": "1.0.0" 6 | , "homepage": "http://wagerfield.github.io/parallax/index.html" 7 | , "repository": { 8 | "type": "git" 9 | , "url": "https://github.com/wagerfield/parallax.git" 10 | } 11 | , "licenses": [ 12 | { 13 | "type": "MIT" 14 | , "url": "https://raw.github.com/wagerfield/parallax/master/LICENSE.md" 15 | } 16 | ] 17 | , "devDependencies": { 18 | "uglify-js": "2.2.x" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /deploy/jquery.parallax.js: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2013 Christophe Rosset - @topheman 6 | // Copyright (C) 2013 Matthew Wagerfield - @mwagerfield 7 | // 8 | // Permission is hereby granted, free of charge, to any 9 | // person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the 11 | // Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, 13 | // sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do 15 | // so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice 18 | // shall be included in all copies or substantial portions 19 | // of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 22 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 26 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 27 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 29 | // OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | //============================================================ 32 | 33 | /** 34 | * jQuery/Zepto Parallax Plugin 35 | * @author Matthew Wagerfield - @mwagerfield 36 | * @description Creates a parallax effect between an array of layers, 37 | * driving the motion from the gyroscope output of a smartdevice. 38 | * If no gyroscope is available, the cursor position is used. 39 | */ 40 | ;(function($, window, document, undefined) { 41 | 42 | var NAME = 'parallax'; 43 | var MAGIC_NUMBER = 30; 44 | var DEFAULTS = { 45 | calibrationThreshold: 100, 46 | calibrationDelay: 500, 47 | supportDelay: 500, 48 | calibrateX: false, 49 | calibrateY: true, 50 | invertX: true, 51 | invertY: true, 52 | limitX: false, 53 | limitY: false, 54 | scalarX: 10.0, 55 | scalarY: 10.0, 56 | frictionX: 0.1, 57 | frictionY: 0.1, 58 | headtrackr: false, 59 | headtrackrPreferDeviceMotion: true, 60 | headtrackrDisplayVideo: false, 61 | headtrackrDebugView: false, 62 | headtrackrScalarX: 3, 63 | headtrackrScalarY: 3, 64 | headtrackrScriptLocation: null 65 | }; 66 | 67 | function Plugin(element, options) { 68 | 69 | // DOM Context 70 | this.element = element; 71 | 72 | // Selections 73 | this.$context = $(element).data('api', this); 74 | this.$layers = this.$context.find('.layer'); 75 | 76 | // Data Extraction 77 | var data = { 78 | calibrateX: this.$context.data('calibrate-x') || null, 79 | calibrateY: this.$context.data('calibrate-y') || null, 80 | invertX: this.$context.data('invert-x') || null, 81 | invertY: this.$context.data('invert-y') || null, 82 | limitX: parseFloat(this.$context.data('limit-x')) || null, 83 | limitY: parseFloat(this.$context.data('limit-y')) || null, 84 | scalarX: parseFloat(this.$context.data('scalar-x')) || null, 85 | scalarY: parseFloat(this.$context.data('scalar-y')) || null, 86 | frictionX: parseFloat(this.$context.data('friction-x')) || null, 87 | frictionY: parseFloat(this.$context.data('friction-y')) || null, 88 | headtrackr: this.$context.data('headtrackr') || null, 89 | headtrackrPreferDeviceMotion: this.$context.data('headtrackr-prefer-device-motion') || null, 90 | headtrackrDisplayVideo: this.$context.data('headtrackr-display-video') || null, 91 | headtrackrDebugView: this.$context.data('headtrackr-debug-view') || null, 92 | headtrackrScalarX: parseFloat(this.$context.data('headtrackr-scalar-x')) || null, 93 | headtrackrScalarY: parseFloat(this.$context.data('headtrackr-scalar-y')) || null, 94 | headtrackrScriptLocation: this.$context.data('headtrackr-script-location') || null 95 | }; 96 | 97 | // Delete Null Data Values 98 | for (var key in data) { 99 | if (data[key] === null) delete data[key]; 100 | } 101 | 102 | // Compose Settings Object 103 | $.extend(this, DEFAULTS, options, data); 104 | 105 | // States 106 | this.calibrationTimer = null; 107 | this.calibrationFlag = true; 108 | this.enabled = false; 109 | this.depths = []; 110 | this.raf = null; 111 | 112 | // Offset 113 | this.ox = 0; 114 | this.oy = 0; 115 | this.ow = 0; 116 | this.oh = 0; 117 | 118 | // Calibration 119 | this.cx = 0; 120 | this.cy = 0; 121 | 122 | // Input 123 | this.ix = 0; 124 | this.iy = 0; 125 | 126 | // Motion 127 | this.mx = 0; 128 | this.my = 0; 129 | 130 | // Velocity 131 | this.vx = 0; 132 | this.vy = 0; 133 | 134 | // Callbacks 135 | this.onFaceTracking = this.onFaceTracking.bind(this); 136 | this.onMouseMove = this.onMouseMove.bind(this); 137 | this.onDeviceOrientation = this.onDeviceOrientation.bind(this); 138 | this.onOrientationTimer = this.onOrientationTimer.bind(this); 139 | this.onCalibrationTimer = this.onCalibrationTimer.bind(this); 140 | this.onAnimationFrame = this.onAnimationFrame.bind(this); 141 | this.onWindowResize = this.onWindowResize.bind(this); 142 | 143 | // Initialise 144 | this.initialise(); 145 | } 146 | 147 | Plugin.prototype.transformSupport = function(value) { 148 | var element = document.createElement('div'); 149 | var propertySupport = false; 150 | var propertyValue = null; 151 | var featureSupport = false; 152 | var cssProperty = null; 153 | var jsProperty = null; 154 | for (var i = 0, l = this.vendors.length; i < l; i++) { 155 | if (this.vendors[i] !== null) { 156 | cssProperty = this.vendors[i][0] + 'transform'; 157 | jsProperty = this.vendors[i][1] + 'Transform'; 158 | } else { 159 | cssProperty = 'transform'; 160 | jsProperty = 'transform'; 161 | } 162 | if (element.style[jsProperty] !== undefined) { 163 | propertySupport = true; 164 | break; 165 | } 166 | } 167 | switch(value) { 168 | case '2D': 169 | featureSupport = propertySupport; 170 | break; 171 | case '3D': 172 | if (propertySupport) { 173 | document.body.appendChild(element); 174 | element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 175 | propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty); 176 | featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== "none"; 177 | document.body.removeChild(element); 178 | } 179 | break; 180 | } 181 | return featureSupport; 182 | }; 183 | 184 | Plugin.prototype.ww = null; 185 | Plugin.prototype.wh = null; 186 | Plugin.prototype.hw = null; 187 | Plugin.prototype.hh = null; 188 | Plugin.prototype.portrait = null; 189 | Plugin.prototype.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i); 190 | Plugin.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 191 | Plugin.prototype.motionSupport = !!window.DeviceMotionEvent; 192 | Plugin.prototype.orientationSupport = !!window.DeviceOrientationEvent; 193 | Plugin.prototype.getUserMediaSupport = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); 194 | Plugin.prototype.headtrackrEnabled = false; //will turn to true if getUserMediaSupport and no DeviceMotion (or headtrackrPreferDeviceMotion = false) 195 | Plugin.prototype.orientationStatus = 0; 196 | Plugin.prototype.transform2DSupport = Plugin.prototype.transformSupport('2D'); 197 | Plugin.prototype.transform3DSupport = Plugin.prototype.transformSupport('3D'); 198 | 199 | /** 200 | * Method to be called when the headtrackr feature was asked for, there is getUserMedia support and no DeviceMotion or headtrackrPreferDeviceMotion = false 201 | */ 202 | Plugin.prototype.headtrackrPrepare = function(){ 203 | if(typeof headtrackr === "undefined"){ 204 | if(this.headtrackrScriptLocation !== null){ 205 | var headTrackrScript = document.createElement('script'), 206 | self = this; 207 | headTrackrScript.onload = function(script){ 208 | if(typeof headtrackr !== "undefined"){ 209 | self.headtrackrPrepare(); 210 | } 211 | else{ 212 | throw new Error("Wrong path to headtrackr script"); 213 | } 214 | }; 215 | headTrackrScript.src = this.headtrackrScriptLocation; 216 | document.getElementsByTagName('body')[0].appendChild(headTrackrScript); 217 | return false; 218 | } 219 | else{ 220 | throw new Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation"); 221 | } 222 | } 223 | 224 | var inputVideo = document.createElement('video'), 225 | canvasInput = document.createElement('canvas'), 226 | canvasDebug = null, 227 | videoWidth = "320", 228 | videoHeight = "240", 229 | headtrackrOptions = {}, 230 | self; 231 | 232 | //add the mousemove listener while connecting the camera 233 | //we'll remove it when the face is detected to plug trackr 234 | //then readd it when the trackr fails 235 | window.addEventListener('mousemove', this.onMouseMove); 236 | 237 | inputVideo.autoplay = true; 238 | inputVideo.loop = true; 239 | inputVideo.style.display = "none"; 240 | inputVideo.width = videoWidth; 241 | inputVideo.height = videoHeight; 242 | 243 | canvasInput.id = "headtrackr-display-video"; 244 | canvasInput.style.position = "fixed"; 245 | canvasInput.style.bottom = "0px"; 246 | canvasInput.style.right = "0px"; 247 | canvasInput.width = videoWidth; 248 | canvasInput.height = videoHeight; 249 | 250 | if(this.headtrackrDisplayVideo === true || this.headtrackrDebugView === true){ 251 | canvasInput.style.display = "block"; 252 | if(this.headtrackrDebugView === true){ 253 | canvasDebug = document.createElement('canvas'); 254 | canvasDebug.id = "headtrackr-debug-view"; 255 | canvasDebug.style.display = "block"; 256 | canvasDebug.style.position = "fixed"; 257 | canvasDebug.style.bottom = "0px"; 258 | canvasDebug.style.right = "0px"; 259 | canvasDebug.width = videoWidth; 260 | canvasDebug.height = videoHeight; 261 | headtrackrOptions.calcAngles = true; 262 | } 263 | } 264 | else{ 265 | this.headtrackrDebugView = false; 266 | canvasInput.style.display = "none"; 267 | } 268 | 269 | this.htrackr = new headtrackr.Tracker(headtrackrOptions); 270 | 271 | document.getElementsByTagName('body')[0].appendChild(inputVideo); 272 | document.getElementsByTagName('body')[0].appendChild(canvasInput); 273 | if(canvasDebug !== null){ 274 | document.getElementsByTagName('body')[0].appendChild(canvasDebug); 275 | this.htrackr.canvasDebug = canvasDebug; 276 | this.htrackr.ctxDebug = canvasDebug.getContext('2d'); 277 | } 278 | 279 | //callback to be used to display a "please allow your webcam" for example 280 | if(typeof(this.headtrackrOnBeforeCameraAccess) === "function"){ 281 | this.headtrackrOnBeforeCameraAccess(); 282 | } 283 | 284 | this.htrackr.init(inputVideo, canvasInput); 285 | this.htrackr.start(); 286 | this.htrackr.canvasInputInfos = { 287 | ww : canvasInput.width, 288 | wh : canvasInput.height, 289 | hw : canvasInput.width / 2, 290 | hh : canvasInput.height / 2 291 | }; 292 | 293 | self = this; 294 | document.addEventListener('headtrackrStatus', function(e){ 295 | console.log(e.status,e.type,e.timeStamp); 296 | if(e.status === "camera found"){ 297 | if(typeof(self.headtrackrOnCameraFound) === "function"){ 298 | self.headtrackrOnCameraFound(); 299 | } 300 | } 301 | else if(e.status === "found"){ 302 | window.removeEventListener('mousemove', self.onMouseMove); 303 | document.addEventListener("facetrackingEvent", self.onFaceTracking, false); 304 | } 305 | else if(e.status === "redetecting"){ 306 | window.addEventListener('mousemove', self.onMouseMove); 307 | document.removeEventListener("facetrackingEvent", self.onFaceTracking, false); 308 | } 309 | }); 310 | }; 311 | 312 | /** 313 | * Method to be called when the headtrackr was to be initiated but there was no getUserMediaSupport for 314 | */ 315 | Plugin.prototype.headtrackrFail = function(){ 316 | //if no callback is set, set the default callback 317 | if(typeof(this.headtrackrNoGetUserMediaCallback) !== "function"){ 318 | this.headtrackrNoGetUserMediaCallback = function(){ 319 | var message = "Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox"; 320 | console.log(message.replace(/
/g,' ').replace(/(<([^>]+)>)/ig,'')); 321 | 322 | var timeout = 10000; 323 | // create element and attach to body 324 | var d = document.createElement('div'), 325 | d2 = document.createElement('div'), 326 | p = document.createElement('p'); 327 | d.setAttribute('id', 'headtrackerMessageDiv'); 328 | 329 | d.style.left = "10%"; 330 | d.style.right = "10%"; 331 | d.style.top = "10%"; 332 | d.style.fontSize = "150%"; 333 | d.style.color = "#777"; 334 | d.style.position = "absolute"; 335 | d.style.fontFamily = "Helvetica, Arial, sans-serif"; 336 | d.style.zIndex = '100002'; 337 | 338 | d2.style.marginLeft = "auto"; 339 | d2.style.marginRight = "auto"; 340 | d2.style.width = "100%"; 341 | d2.style.textAlign = "center"; 342 | d2.style.color = "#fff"; 343 | d2.style.backgroundColor = "#444"; 344 | d2.style.opacity = "0.8"; 345 | 346 | p.setAttribute('id', 'parallaxHeadtrackerNoGetUserMediaMessage'); 347 | p.innerHTML = message; 348 | d2.appendChild(p); 349 | d.appendChild(d2); 350 | document.body.appendChild(d); 351 | 352 | setTimeout(function(){ 353 | d.parentNode.removeChild(d); 354 | },timeout); 355 | 356 | }; 357 | } 358 | this.headtrackrNoGetUserMediaCallback(); 359 | //back to normal behaviour 360 | this.headtrackr = false; 361 | }; 362 | 363 | Plugin.prototype.initialise = function() { 364 | 365 | // Configure Styles 366 | if (this.$context.css('position') === 'static') { 367 | this.$context.css({ 368 | position:'relative' 369 | }); 370 | } 371 | this.$layers.css({ 372 | position:'absolute', 373 | display:'block', 374 | height:'100%', 375 | width:'100%', 376 | left: 0, 377 | top: 0 378 | }); 379 | this.$layers.first().css({ 380 | position:'relative' 381 | }); 382 | 383 | // Cache Depths 384 | this.$layers.each($.proxy(function(index, element) { 385 | this.depths.push($(element).data('depth') || 0); 386 | }, this)); 387 | 388 | // Hardware Accelerate Elements 389 | this.accelerate(this.$context); 390 | this.accelerate(this.$layers); 391 | 392 | // Setup 393 | 394 | //if headtrackr was asked, and can be supported (via getUserMedia), no matter if there is DeviceMotion on the device, enable it 395 | //if headtracker was asked but with headtrackrPreferDeviceMotion === true, the headtrackrPrepare() is made in the onOrientationTimer (where we make sure there is - or not - DeviceMotion support) 396 | if(this.headtrackr === true && this.getUserMediaSupport === true && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 397 | this.headtrackrPrepare(); 398 | this.headtrackrEnabled = true; 399 | } 400 | else if(this.headtrackr === true && this.getUserMediaSupport === false && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 401 | this.headtrackrFail(); 402 | } 403 | this.updateDimensions(); 404 | this.enable(); 405 | this.queueCalibration(this.calibrationDelay); 406 | }; 407 | 408 | Plugin.prototype.updateDimensions = function() { 409 | 410 | // Cache Context Dimensions 411 | this.ox = this.$context.offset().left; 412 | this.oy = this.$context.offset().top; 413 | this.ow = this.$context.width(); 414 | this.oh = this.$context.height(); 415 | 416 | // Cache Window Dimensions 417 | this.ww = window.innerWidth; 418 | this.wh = window.innerHeight; 419 | this.hw = this.ww / 2; 420 | this.hh = this.wh / 2; 421 | }; 422 | 423 | Plugin.prototype.queueCalibration = function(delay) { 424 | clearTimeout(this.calibrationTimer); 425 | this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay); 426 | }; 427 | 428 | Plugin.prototype.enable = function() { 429 | if (!this.enabled) { 430 | this.enabled = true; 431 | if (this.headtrackrEnabled === false && this.orientationSupport) { 432 | this.portrait = null; 433 | window.addEventListener('deviceorientation', this.onDeviceOrientation); 434 | setTimeout(this.onOrientationTimer, this.supportDelay); 435 | } 436 | else if (this.headtrackrEnabled === false) { 437 | this.cx = 0; 438 | this.cy = 0; 439 | this.portrait = false; 440 | window.addEventListener('mousemove', this.onMouseMove); 441 | } 442 | else { 443 | document.addEventListener("facetrackingEvent", this.onFaceTracking, false); 444 | } 445 | window.addEventListener('resize', this.onWindowResize); 446 | this.raf = requestAnimationFrame(this.onAnimationFrame); 447 | } 448 | }; 449 | 450 | Plugin.prototype.disable = function() { 451 | if (this.enabled) { 452 | this.enabled = false; 453 | if (this.headtrackrEnabled === false && this.orientationSupport) { 454 | window.removeEventListener('deviceorientation', this.onDeviceOrientation); 455 | } 456 | else if (this.headtrackrEnabled === false) { 457 | window.removeEventListener('mousemove', this.onMouseMove); 458 | } 459 | else { 460 | document.removeEventListener("facetrackingEvent", this.onFaceTracking, false); 461 | } 462 | window.removeEventListener('resize', this.onWindowResize); 463 | cancelAnimationFrame(this.raf); 464 | } 465 | }; 466 | 467 | Plugin.prototype.calibrate = function(x, y) { 468 | this.calibrateX = x === undefined ? this.calibrateX : x; 469 | this.calibrateY = y === undefined ? this.calibrateY : y; 470 | }; 471 | 472 | Plugin.prototype.invert = function(x, y) { 473 | this.invertX = x === undefined ? this.invertX : x; 474 | this.invertY = y === undefined ? this.invertY : y; 475 | }; 476 | 477 | Plugin.prototype.friction = function(x, y) { 478 | this.frictionX = x === undefined ? this.frictionX : x; 479 | this.frictionY = y === undefined ? this.frictionY : y; 480 | }; 481 | 482 | Plugin.prototype.scalar = function(x, y) { 483 | this.scalarX = x === undefined ? this.scalarX : x; 484 | this.scalarY = y === undefined ? this.scalarY : y; 485 | }; 486 | 487 | Plugin.prototype.headtrackrScalar = function(x, y) { 488 | this.headtrackrScalarX = x === undefined ? this.headtrackrScalarX : x; 489 | this.headtrackrScalarY = y === undefined ? this.headtrackrScalarY : y; 490 | }; 491 | 492 | Plugin.prototype.limit = function(x, y) { 493 | this.limitX = x === undefined ? this.limitX : x; 494 | this.limitY = y === undefined ? this.limitY : y; 495 | }; 496 | 497 | Plugin.prototype.clamp = function(value, min, max) { 498 | value = Math.max(value, min); 499 | value = Math.min(value, max); 500 | return value; 501 | }; 502 | 503 | Plugin.prototype.css = function(element, property, value) { 504 | var jsProperty = null; 505 | for (var i = 0, l = this.vendors.length; i < l; i++) { 506 | if (this.vendors[i] !== null) { 507 | jsProperty = $.camelCase(this.vendors[i][1] + '-' + property); 508 | } else { 509 | jsProperty = property; 510 | } 511 | if (element.style[jsProperty] !== undefined) { 512 | element.style[jsProperty] = value; 513 | break; 514 | } 515 | } 516 | }; 517 | 518 | Plugin.prototype.accelerate = function($element) { 519 | for (var i = 0, l = $element.length; i < l; i++) { 520 | var element = $element[i]; 521 | this.css(element, 'transform', 'translate3d(0,0,0)'); 522 | this.css(element, 'transform-style', 'preserve-3d'); 523 | this.css(element, 'backface-visibility', 'hidden'); 524 | } 525 | }; 526 | 527 | Plugin.prototype.setPosition = function(element, x, y) { 528 | x += '%'; 529 | y += '%'; 530 | if (this.transform3DSupport) { 531 | this.css(element, 'transform', 'translate3d('+x+','+y+',0)'); 532 | } else if (this.transform2DSupport) { 533 | this.css(element, 'transform', 'translate('+x+','+y+')'); 534 | } else { 535 | element.style.left = x; 536 | element.style.top = y; 537 | } 538 | }; 539 | 540 | Plugin.prototype.onOrientationTimer = function(event) { 541 | if (this.orientationSupport && this.orientationStatus === 0) { 542 | this.disable(); 543 | this.orientationSupport = false; 544 | //only at this point we are sure there is no orientationSupport (can't rely on !!window.DeviceOrientationEvent, beacause we may be on a desktop that may expose the API but doesn't have any accelerometer) 545 | //so, we launch the headtrackr in fallback to deviceMotion as asked in the options as headtrackrPreferDeviceMotion = true 546 | if(this.headtrackr === true && this.getUserMediaSupport === true && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 547 | this.headtrackrPrepare(); 548 | this.headtrackrEnabled = true; 549 | } 550 | //in case there is no getUserMedia support but it was asked to use headtrackr 551 | else if(this.headtrackr === true && this.getUserMediaSupport === false && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 552 | this.headtrackrFail(); 553 | } 554 | this.enable(); 555 | } 556 | }; 557 | 558 | Plugin.prototype.onCalibrationTimer = function(event) { 559 | this.calibrationFlag = true; 560 | }; 561 | 562 | Plugin.prototype.onWindowResize = function(event) { 563 | this.updateDimensions(); 564 | }; 565 | 566 | Plugin.prototype.onAnimationFrame = function() { 567 | var dx = this.ix - this.cx; 568 | var dy = this.iy - this.cy; 569 | if ((Math.abs(dx) > this.calibrationThreshold) || (Math.abs(dy) > this.calibrationThreshold)) { 570 | this.queueCalibration(0); 571 | } 572 | if (this.portrait) { 573 | this.mx = (this.calibrateX ? dy : this.iy) * this.scalarX; 574 | this.my = (this.calibrateY ? dx : this.ix) * this.scalarY; 575 | } else { 576 | this.mx = (this.calibrateX ? dx : this.ix) * this.scalarX; 577 | this.my = (this.calibrateY ? dy : this.iy) * this.scalarY; 578 | } 579 | if (!isNaN(parseFloat(this.limitX))) { 580 | this.mx = this.clamp(this.mx, -this.limitX, this.limitX); 581 | } 582 | if (!isNaN(parseFloat(this.limitY))) { 583 | this.my = this.clamp(this.my, -this.limitY, this.limitY); 584 | } 585 | this.vx += (this.mx - this.vx) * this.frictionX; 586 | this.vy += (this.my - this.vy) * this.frictionY; 587 | for (var i = 0, l = this.$layers.length; i < l; i++) { 588 | var depth = this.depths[i]; 589 | var layer = this.$layers[i]; 590 | var xOffset = this.vx * depth * (this.invertX ? -1 : 1); 591 | var yOffset = this.vy * depth * (this.invertY ? -1 : 1); 592 | this.setPosition(layer, xOffset, yOffset); 593 | } 594 | this.raf = requestAnimationFrame(this.onAnimationFrame); 595 | }; 596 | 597 | Plugin.prototype.onDeviceOrientation = function(event) { 598 | 599 | // Validate environment and event properties. 600 | if (!this.desktop && event.beta !== null && event.gamma !== null) { 601 | 602 | // Set orientation status. 603 | this.orientationStatus = 1; 604 | 605 | // Extract Rotation 606 | var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 607 | var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 608 | 609 | // Detect Orientation Change 610 | var portrait = window.innerHeight > window.innerWidth; 611 | if (this.portrait !== portrait) { 612 | this.portrait = portrait; 613 | this.calibrationFlag = true; 614 | } 615 | 616 | // Set Calibration 617 | if (this.calibrationFlag) { 618 | this.calibrationFlag = false; 619 | this.cx = x; 620 | this.cy = y; 621 | } 622 | 623 | // Set Input 624 | this.ix = x; 625 | this.iy = y; 626 | } 627 | }; 628 | 629 | Plugin.prototype.onMouseMove = function(event) { 630 | 631 | // Calculate Input 632 | this.ix = (event.pageX - this.hw) / this.hw; 633 | this.iy = (event.pageY - this.hh) / this.hh; 634 | }; 635 | 636 | Plugin.prototype.onFaceTracking = function(event) { 637 | 638 | // Calculate Input 639 | if(event.detection === "CS"){ 640 | this.ix = -this.headtrackrScalarX*(event.x - this.htrackr.canvasInputInfos.hw) / this.htrackr.canvasInputInfos.hw; 641 | this.iy = this.headtrackrScalarY*(event.y - this.htrackr.canvasInputInfos.hh) / this.htrackr.canvasInputInfos.hh; 642 | if(this.headtrackrDebugView === true){ 643 | this.htrackr.canvasDebug.width = this.htrackr.canvasDebug.width; 644 | this.htrackr.ctxDebug.translate(event.x, event.y); 645 | this.htrackr.ctxDebug.rotate(event.angle-(Math.PI/2)); 646 | this.htrackr.ctxDebug.strokeStyle = "#00CC00"; 647 | this.htrackr.ctxDebug.strokeRect((-(event.width/2)) >> 0, (-(event.height/2)) >> 0, event.width, event.height); 648 | this.htrackr.ctxDebug.rotate((Math.PI/2)-event.angle); 649 | this.htrackr.ctxDebug.translate(-event.x, -event.y); 650 | } 651 | } 652 | 653 | }; 654 | 655 | var API = { 656 | enable: Plugin.prototype.enable, 657 | disable: Plugin.prototype.disable, 658 | calibrate: Plugin.prototype.calibrate, 659 | friction: Plugin.prototype.friction, 660 | invert: Plugin.prototype.invert, 661 | scalar: Plugin.prototype.scalar, 662 | headtrackrScalar : Plugin.prototype.headtrackrScalar, 663 | limit: Plugin.prototype.limit 664 | }; 665 | 666 | $.fn[NAME] = function (value) { 667 | var args = arguments; 668 | return this.each(function () { 669 | var $this = $(this); 670 | var plugin = $this.data(NAME); 671 | if (!plugin) { 672 | plugin = new Plugin(this, value); 673 | $this.data(NAME, plugin); 674 | } 675 | if (API[value]) { 676 | plugin[value].apply(plugin, Array.prototype.slice.call(args, 1)); 677 | } 678 | }); 679 | }; 680 | 681 | })(window.jQuery || window.Zepto, window, document); 682 | 683 | /** 684 | * Request Animation Frame Polyfill. 685 | * @author Tino Zijdel 686 | * @author Paul Irish 687 | * @see https://gist.github.com/paulirish/1579671 688 | */ 689 | ;(function() { 690 | 691 | var lastTime = 0; 692 | var vendors = ['ms', 'moz', 'webkit', 'o']; 693 | 694 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 695 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 696 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 697 | } 698 | 699 | if (!window.requestAnimationFrame) { 700 | window.requestAnimationFrame = function(callback, element) { 701 | var currTime = new Date().getTime(); 702 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 703 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 704 | timeToCall); 705 | lastTime = currTime + timeToCall; 706 | return id; 707 | }; 708 | } 709 | 710 | if (!window.cancelAnimationFrame) { 711 | window.cancelAnimationFrame = function(id) { 712 | clearTimeout(id); 713 | }; 714 | } 715 | 716 | }()); 717 | -------------------------------------------------------------------------------- /deploy/jquery.parallax.min.js: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2013 Christophe Rosset - @topheman 6 | // Copyright (C) 2013 Matthew Wagerfield - @mwagerfield 7 | // 8 | // Permission is hereby granted, free of charge, to any 9 | // person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the 11 | // Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, 13 | // sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do 15 | // so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice 18 | // shall be included in all copies or substantial portions 19 | // of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 22 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 26 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 27 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 29 | // OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | //============================================================ 32 | (function(t,e,i,a){function r(e,i){this.element=e,this.$context=t(e).data("api",this),this.$layers=this.$context.find(".layer");var a={calibrateX:this.$context.data("calibrate-x")||null,calibrateY:this.$context.data("calibrate-y")||null,invertX:this.$context.data("invert-x")||null,invertY:this.$context.data("invert-y")||null,limitX:parseFloat(this.$context.data("limit-x"))||null,limitY:parseFloat(this.$context.data("limit-y"))||null,scalarX:parseFloat(this.$context.data("scalar-x"))||null,scalarY:parseFloat(this.$context.data("scalar-y"))||null,frictionX:parseFloat(this.$context.data("friction-x"))||null,frictionY:parseFloat(this.$context.data("friction-y"))||null,headtrackr:this.$context.data("headtrackr")||null,headtrackrPreferDeviceMotion:this.$context.data("headtrackr-prefer-device-motion")||null,headtrackrDisplayVideo:this.$context.data("headtrackr-display-video")||null,headtrackrDebugView:this.$context.data("headtrackr-debug-view")||null,headtrackrScalarX:parseFloat(this.$context.data("headtrackr-scalar-x"))||null,headtrackrScalarY:parseFloat(this.$context.data("headtrackr-scalar-y"))||null,headtrackrScriptLocation:this.$context.data("headtrackr-script-location")||null};for(var r in a)null===a[r]&&delete a[r];t.extend(this,o,i,a),this.calibrationTimer=null,this.calibrationFlag=!0,this.enabled=!1,this.depths=[],this.raf=null,this.ox=0,this.oy=0,this.ow=0,this.oh=0,this.cx=0,this.cy=0,this.ix=0,this.iy=0,this.mx=0,this.my=0,this.vx=0,this.vy=0,this.onFaceTracking=this.onFaceTracking.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.onDeviceOrientation=this.onDeviceOrientation.bind(this),this.onOrientationTimer=this.onOrientationTimer.bind(this),this.onCalibrationTimer=this.onCalibrationTimer.bind(this),this.onAnimationFrame=this.onAnimationFrame.bind(this),this.onWindowResize=this.onWindowResize.bind(this),this.initialise()}var s="parallax",n=30,o={calibrationThreshold:100,calibrationDelay:500,supportDelay:500,calibrateX:!1,calibrateY:!0,invertX:!0,invertY:!0,limitX:!1,limitY:!1,scalarX:10,scalarY:10,frictionX:.1,frictionY:.1,headtrackr:!1,headtrackrPreferDeviceMotion:!0,headtrackrDisplayVideo:!1,headtrackrDebugView:!1,headtrackrScalarX:3,headtrackrScalarY:3,headtrackrScriptLocation:null};r.prototype.transformSupport=function(t){for(var r=i.createElement("div"),s=!1,n=null,o=!1,h=null,c=null,l=0,d=this.vendors.length;d>l;l++)if(null!==this.vendors[l]?(h=this.vendors[l][0]+"transform",c=this.vendors[l][1]+"Transform"):(h="transform",c="transform"),r.style[c]!==a){s=!0;break}switch(t){case"2D":o=s;break;case"3D":s&&(i.body.appendChild(r),r.style[c]="translate3d(1px,1px,1px)",n=e.getComputedStyle(r).getPropertyValue(h),o=n!==a&&n.length>0&&"none"!==n,i.body.removeChild(r))}return o},r.prototype.ww=null,r.prototype.wh=null,r.prototype.hw=null,r.prototype.hh=null,r.prototype.portrait=null,r.prototype.desktop=!navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i),r.prototype.vendors=[null,["-webkit-","webkit"],["-moz-","Moz"],["-o-","O"],["-ms-","ms"]],r.prototype.motionSupport=!!e.DeviceMotionEvent,r.prototype.orientationSupport=!!e.DeviceOrientationEvent,r.prototype.getUserMediaSupport=!!(navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia),r.prototype.headtrackrEnabled=!1,r.prototype.orientationStatus=0,r.prototype.transform2DSupport=r.prototype.transformSupport("2D"),r.prototype.transform3DSupport=r.prototype.transformSupport("3D"),r.prototype.headtrackrPrepare=function(){if("undefined"==typeof headtrackr){if(null!==this.headtrackrScriptLocation){var t=i.createElement("script"),a=this;return t.onload=function(){if("undefined"==typeof headtrackr)throw Error("Wrong path to headtrackr script");a.headtrackrPrepare()},t.src=this.headtrackrScriptLocation,i.getElementsByTagName("body")[0].appendChild(t),!1}throw Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation")}var a,r=i.createElement("video"),s=i.createElement("canvas"),n=null,o="320",h="240",c={};e.addEventListener("mousemove",this.onMouseMove),r.autoplay=!0,r.loop=!0,r.style.display="none",r.width=o,r.height=h,s.id="headtrackr-display-video",s.style.position="fixed",s.style.bottom="0px",s.style.right="0px",s.width=o,s.height=h,this.headtrackrDisplayVideo===!0||this.headtrackrDebugView===!0?(s.style.display="block",this.headtrackrDebugView===!0&&(n=i.createElement("canvas"),n.id="headtrackr-debug-view",n.style.display="block",n.style.position="fixed",n.style.bottom="0px",n.style.right="0px",n.width=o,n.height=h,c.calcAngles=!0)):(this.headtrackrDebugView=!1,s.style.display="none"),this.htrackr=new headtrackr.Tracker(c),i.getElementsByTagName("body")[0].appendChild(r),i.getElementsByTagName("body")[0].appendChild(s),null!==n&&(i.getElementsByTagName("body")[0].appendChild(n),this.htrackr.canvasDebug=n,this.htrackr.ctxDebug=n.getContext("2d")),"function"==typeof this.headtrackrOnBeforeCameraAccess&&this.headtrackrOnBeforeCameraAccess(),this.htrackr.init(r,s),this.htrackr.start(),this.htrackr.canvasInputInfos={ww:s.width,wh:s.height,hw:s.width/2,hh:s.height/2},a=this,i.addEventListener("headtrackrStatus",function(t){console.log(t.status,t.type,t.timeStamp),"camera found"===t.status?"function"==typeof a.headtrackrOnCameraFound&&a.headtrackrOnCameraFound():"found"===t.status?(e.removeEventListener("mousemove",a.onMouseMove),i.addEventListener("facetrackingEvent",a.onFaceTracking,!1)):"redetecting"===t.status&&(e.addEventListener("mousemove",a.onMouseMove),i.removeEventListener("facetrackingEvent",a.onFaceTracking,!1))})},r.prototype.headtrackrFail=function(){"function"!=typeof this.headtrackrNoGetUserMediaCallback&&(this.headtrackrNoGetUserMediaCallback=function(){var t="Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox";console.log(t.replace(/
/g," ").replace(/(<([^>]+)>)/gi,""));var e=1e4,a=i.createElement("div"),r=i.createElement("div"),s=i.createElement("p");a.setAttribute("id","headtrackerMessageDiv"),a.style.left="10%",a.style.right="10%",a.style.top="10%",a.style.fontSize="150%",a.style.color="#777",a.style.position="absolute",a.style.fontFamily="Helvetica, Arial, sans-serif",a.style.zIndex="100002",r.style.marginLeft="auto",r.style.marginRight="auto",r.style.width="100%",r.style.textAlign="center",r.style.color="#fff",r.style.backgroundColor="#444",r.style.opacity="0.8",s.setAttribute("id","parallaxHeadtrackerNoGetUserMediaMessage"),s.innerHTML=t,r.appendChild(s),a.appendChild(r),i.body.appendChild(a),setTimeout(function(){a.parentNode.removeChild(a)},e)}),this.headtrackrNoGetUserMediaCallback(),this.headtrackr=!1},r.prototype.initialise=function(){"static"===this.$context.css("position")&&this.$context.css({position:"relative"}),this.$layers.css({position:"absolute",display:"block",height:"100%",width:"100%",left:0,top:0}),this.$layers.first().css({position:"relative"}),this.$layers.each(t.proxy(function(e,i){this.depths.push(t(i).data("depth")||0)},this)),this.accelerate(this.$context),this.accelerate(this.$layers),this.headtrackr!==!0||this.getUserMediaSupport!==!0||this.headtrackrPreferDeviceMotion!==!1&&this.orientationSupport!==!1||this.headtrackrEnabled!==!1?this.headtrackr!==!0||this.getUserMediaSupport!==!1||this.headtrackrPreferDeviceMotion!==!1&&this.orientationSupport!==!1||this.headtrackrEnabled!==!1||this.headtrackrFail():(this.headtrackrPrepare(),this.headtrackrEnabled=!0),this.updateDimensions(),this.enable(),this.queueCalibration(this.calibrationDelay)},r.prototype.updateDimensions=function(){this.ox=this.$context.offset().left,this.oy=this.$context.offset().top,this.ow=this.$context.width(),this.oh=this.$context.height(),this.ww=e.innerWidth,this.wh=e.innerHeight,this.hw=this.ww/2,this.hh=this.wh/2},r.prototype.queueCalibration=function(t){clearTimeout(this.calibrationTimer),this.calibrationTimer=setTimeout(this.onCalibrationTimer,t)},r.prototype.enable=function(){this.enabled||(this.enabled=!0,this.headtrackrEnabled===!1&&this.orientationSupport?(this.portrait=null,e.addEventListener("deviceorientation",this.onDeviceOrientation),setTimeout(this.onOrientationTimer,this.supportDelay)):this.headtrackrEnabled===!1?(this.cx=0,this.cy=0,this.portrait=!1,e.addEventListener("mousemove",this.onMouseMove)):i.addEventListener("facetrackingEvent",this.onFaceTracking,!1),e.addEventListener("resize",this.onWindowResize),this.raf=requestAnimationFrame(this.onAnimationFrame))},r.prototype.disable=function(){this.enabled&&(this.enabled=!1,this.headtrackrEnabled===!1&&this.orientationSupport?e.removeEventListener("deviceorientation",this.onDeviceOrientation):this.headtrackrEnabled===!1?e.removeEventListener("mousemove",this.onMouseMove):i.removeEventListener("facetrackingEvent",this.onFaceTracking,!1),e.removeEventListener("resize",this.onWindowResize),cancelAnimationFrame(this.raf))},r.prototype.calibrate=function(t,e){this.calibrateX=t===a?this.calibrateX:t,this.calibrateY=e===a?this.calibrateY:e},r.prototype.invert=function(t,e){this.invertX=t===a?this.invertX:t,this.invertY=e===a?this.invertY:e},r.prototype.friction=function(t,e){this.frictionX=t===a?this.frictionX:t,this.frictionY=e===a?this.frictionY:e},r.prototype.scalar=function(t,e){this.scalarX=t===a?this.scalarX:t,this.scalarY=e===a?this.scalarY:e},r.prototype.headtrackrScalar=function(t,e){this.headtrackrScalarX=t===a?this.headtrackrScalarX:t,this.headtrackrScalarY=e===a?this.headtrackrScalarY:e},r.prototype.limit=function(t,e){this.limitX=t===a?this.limitX:t,this.limitY=e===a?this.limitY:e},r.prototype.clamp=function(t,e,i){return t=Math.max(t,e),t=Math.min(t,i)},r.prototype.css=function(e,i,r){for(var s=null,n=0,o=this.vendors.length;o>n;n++)if(s=null!==this.vendors[n]?t.camelCase(this.vendors[n][1]+"-"+i):i,e.style[s]!==a){e.style[s]=r;break}},r.prototype.accelerate=function(t){for(var e=0,i=t.length;i>e;e++){var a=t[e];this.css(a,"transform","translate3d(0,0,0)"),this.css(a,"transform-style","preserve-3d"),this.css(a,"backface-visibility","hidden")}},r.prototype.setPosition=function(t,e,i){e+="%",i+="%",this.transform3DSupport?this.css(t,"transform","translate3d("+e+","+i+",0)"):this.transform2DSupport?this.css(t,"transform","translate("+e+","+i+")"):(t.style.left=e,t.style.top=i)},r.prototype.onOrientationTimer=function(){this.orientationSupport&&0===this.orientationStatus&&(this.disable(),this.orientationSupport=!1,this.headtrackr===!0&&this.getUserMediaSupport===!0&&this.headtrackrPreferDeviceMotion===!0&&this.headtrackrEnabled===!1?(this.headtrackrPrepare(),this.headtrackrEnabled=!0):this.headtrackr===!0&&this.getUserMediaSupport===!1&&this.headtrackrPreferDeviceMotion===!0&&this.headtrackrEnabled===!1&&this.headtrackrFail(),this.enable())},r.prototype.onCalibrationTimer=function(){this.calibrationFlag=!0},r.prototype.onWindowResize=function(){this.updateDimensions()},r.prototype.onAnimationFrame=function(){var t=this.ix-this.cx,e=this.iy-this.cy;(Math.abs(t)>this.calibrationThreshold||Math.abs(e)>this.calibrationThreshold)&&this.queueCalibration(0),this.portrait?(this.mx=(this.calibrateX?e:this.iy)*this.scalarX,this.my=(this.calibrateY?t:this.ix)*this.scalarY):(this.mx=(this.calibrateX?t:this.ix)*this.scalarX,this.my=(this.calibrateY?e:this.iy)*this.scalarY),isNaN(parseFloat(this.limitX))||(this.mx=this.clamp(this.mx,-this.limitX,this.limitX)),isNaN(parseFloat(this.limitY))||(this.my=this.clamp(this.my,-this.limitY,this.limitY)),this.vx+=(this.mx-this.vx)*this.frictionX,this.vy+=(this.my-this.vy)*this.frictionY;for(var i=0,a=this.$layers.length;a>i;i++){var r=this.depths[i],s=this.$layers[i],n=this.vx*r*(this.invertX?-1:1),o=this.vy*r*(this.invertY?-1:1);this.setPosition(s,n,o)}this.raf=requestAnimationFrame(this.onAnimationFrame)},r.prototype.onDeviceOrientation=function(t){if(!this.desktop&&null!==t.beta&&null!==t.gamma){this.orientationStatus=1;var i=(t.beta||0)/n,a=(t.gamma||0)/n,r=e.innerHeight>e.innerWidth;this.portrait!==r&&(this.portrait=r,this.calibrationFlag=!0),this.calibrationFlag&&(this.calibrationFlag=!1,this.cx=i,this.cy=a),this.ix=i,this.iy=a}},r.prototype.onMouseMove=function(t){this.ix=(t.pageX-this.hw)/this.hw,this.iy=(t.pageY-this.hh)/this.hh},r.prototype.onFaceTracking=function(t){"CS"===t.detection&&(this.ix=-this.headtrackrScalarX*(t.x-this.htrackr.canvasInputInfos.hw)/this.htrackr.canvasInputInfos.hw,this.iy=this.headtrackrScalarY*(t.y-this.htrackr.canvasInputInfos.hh)/this.htrackr.canvasInputInfos.hh,this.headtrackrDebugView===!0&&(this.htrackr.canvasDebug.width=this.htrackr.canvasDebug.width,this.htrackr.ctxDebug.translate(t.x,t.y),this.htrackr.ctxDebug.rotate(t.angle-Math.PI/2),this.htrackr.ctxDebug.strokeStyle="#00CC00",this.htrackr.ctxDebug.strokeRect(-(t.width/2)>>0,-(t.height/2)>>0,t.width,t.height),this.htrackr.ctxDebug.rotate(Math.PI/2-t.angle),this.htrackr.ctxDebug.translate(-t.x,-t.y)))};var h={enable:r.prototype.enable,disable:r.prototype.disable,calibrate:r.prototype.calibrate,friction:r.prototype.friction,invert:r.prototype.invert,scalar:r.prototype.scalar,headtrackrScalar:r.prototype.headtrackrScalar,limit:r.prototype.limit};t.fn[s]=function(e){var i=arguments;return this.each(function(){var a=t(this),n=a.data(s);n||(n=new r(this,e),a.data(s,n)),h[e]&&n[e].apply(n,Array.prototype.slice.call(i,1))})}})(window.jQuery||window.Zepto,window,document),function(){for(var t=0,e=["ms","moz","webkit","o"],i=0;e.length>i&&!window.requestAnimationFrame;++i)window.requestAnimationFrame=window[e[i]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[i]+"CancelAnimationFrame"]||window[e[i]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(e){var i=(new Date).getTime(),a=Math.max(0,16-(i-t)),r=window.setTimeout(function(){e(i+a)},a);return t=i+a,r}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(t){clearTimeout(t)})}(); -------------------------------------------------------------------------------- /deploy/parallax.js: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2013 Christophe Rosset - @topheman 6 | // Copyright (C) 2013 Matthew Wagerfield - @mwagerfield 7 | // 8 | // Permission is hereby granted, free of charge, to any 9 | // person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the 11 | // Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, 13 | // sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do 15 | // so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice 18 | // shall be included in all copies or substantial portions 19 | // of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 22 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 26 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 27 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 29 | // OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | //============================================================ 32 | 33 | /** 34 | * Parallax.js 35 | * @author Matthew Wagerfield - @mwagerfield 36 | * @description Creates a parallax effect between an array of layers, 37 | * driving the motion from the gyroscope output of a smartdevice. 38 | * If no gyroscope is available, the cursor position is used. 39 | */ 40 | ;(function(window, document, undefined) { 41 | 42 | var NAME = 'Parallax'; 43 | var MAGIC_NUMBER = 30; 44 | var DEFAULTS = { 45 | calibrationThreshold: 100, 46 | calibrationDelay: 500, 47 | supportDelay: 500, 48 | calibrateX: false, 49 | calibrateY: true, 50 | invertX: true, 51 | invertY: true, 52 | limitX: false, 53 | limitY: false, 54 | scalarX: 10.0, 55 | scalarY: 10.0, 56 | frictionX: 0.1, 57 | frictionY: 0.1, 58 | headtrackr: false, 59 | headtrackrPreferDeviceMotion: true, 60 | headtrackrDisplayVideo: false, 61 | headtrackrDebugView: false, 62 | headtrackrScalarX: 3, 63 | headtrackrScalarY: 3, 64 | headtrackrScriptLocation: null 65 | }; 66 | 67 | function Parallax(element, options) { 68 | 69 | // DOM Context 70 | this.element = element; 71 | this.layers = element.getElementsByClassName('layer'); 72 | 73 | // Data Extraction 74 | var data = { 75 | calibrateX: this.data(this.element, 'calibrate-x'), 76 | calibrateY: this.data(this.element, 'calibrate-y'), 77 | invertX: this.data(this.element, 'invert-x'), 78 | invertY: this.data(this.element, 'invert-y'), 79 | limitX: this.data(this.element, 'limit-x'), 80 | limitY: this.data(this.element, 'limit-y'), 81 | scalarX: this.data(this.element, 'scalar-x'), 82 | scalarY: this.data(this.element, 'scalar-y'), 83 | frictionX: this.data(this.element, 'friction-x'), 84 | frictionY: this.data(this.element, 'friction-y'), 85 | headtrackr: this.data(this.element, 'headtrackr'), 86 | headtrackrPreferDeviceMotion: this.data(this.element, 'headtrackr-prefer-device-motion'), 87 | headtrackrDisplayVideo: this.data(this.element, 'headtrackr-display-video'), 88 | headtrackrDebugView: this.data(this.element, 'headtrackr-debug-view'), 89 | headtrackrScalarX: this.data(this.element, 'headtrackr-scalar-x'), 90 | headtrackrScalarY: this.data(this.element, 'headtrackr-scalar-y'), 91 | headtrackrScriptLocation: this.data(this.element, 'headtrackr-script-location') 92 | }; 93 | 94 | // Delete Null Data Values 95 | for (var key in data) { 96 | if (data[key] === null) delete data[key]; 97 | } 98 | 99 | // Compose Settings Object 100 | this.extend(this, DEFAULTS, options, data); 101 | 102 | // States 103 | this.calibrationTimer = null; 104 | this.calibrationFlag = true; 105 | this.enabled = false; 106 | this.depths = []; 107 | this.raf = null; 108 | 109 | // Offset 110 | this.ox = 0; 111 | this.oy = 0; 112 | this.ow = 0; 113 | this.oh = 0; 114 | 115 | // Calibration 116 | this.cx = 0; 117 | this.cy = 0; 118 | 119 | // Input 120 | this.ix = 0; 121 | this.iy = 0; 122 | 123 | // Motion 124 | this.mx = 0; 125 | this.my = 0; 126 | 127 | // Velocity 128 | this.vx = 0; 129 | this.vy = 0; 130 | 131 | // Callbacks 132 | this.onFaceTracking = this.onFaceTracking.bind(this); 133 | this.onMouseMove = this.onMouseMove.bind(this); 134 | this.onDeviceOrientation = this.onDeviceOrientation.bind(this); 135 | this.onOrientationTimer = this.onOrientationTimer.bind(this); 136 | this.onCalibrationTimer = this.onCalibrationTimer.bind(this); 137 | this.onAnimationFrame = this.onAnimationFrame.bind(this); 138 | this.onWindowResize = this.onWindowResize.bind(this); 139 | 140 | // Initialise 141 | this.initialise(); 142 | } 143 | 144 | Parallax.prototype.extend = function() { 145 | if (arguments.length > 1) { 146 | var master = arguments[0]; 147 | for (var i = 1, l = arguments.length; i < l; i++) { 148 | var object = arguments[i]; 149 | for (var key in object) { 150 | master[key] = object[key]; 151 | } 152 | } 153 | } 154 | }; 155 | 156 | Parallax.prototype.data = function(element, name) { 157 | return this.deserialize(element.getAttribute('data-'+name)); 158 | }; 159 | 160 | Parallax.prototype.deserialize = function(value) { 161 | if (value === "true") { 162 | return true; 163 | } else if (value === "false") { 164 | return false; 165 | } else if (value === "null") { 166 | return null; 167 | } else if (!isNaN(parseFloat(value)) && isFinite(value)) { 168 | return parseFloat(value); 169 | } else { 170 | return value; 171 | } 172 | }; 173 | 174 | Parallax.prototype.offset = function(element) { 175 | var x = 0, y = 0; 176 | while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { 177 | x += element.offsetLeft - element.scrollLeft; 178 | y += element.offsetTop - element.scrollTop; 179 | element = element.offsetParent; 180 | } 181 | return {top:y, left:x}; 182 | }; 183 | 184 | Parallax.prototype.camelCase = function(value) { 185 | return value.replace(/-+(.)?/g, function(match, character){ 186 | return character ? character.toUpperCase() : ''; 187 | }); 188 | }; 189 | 190 | Parallax.prototype.transformSupport = function(value) { 191 | var element = document.createElement('div'); 192 | var propertySupport = false; 193 | var propertyValue = null; 194 | var featureSupport = false; 195 | var cssProperty = null; 196 | var jsProperty = null; 197 | for (var i = 0, l = this.vendors.length; i < l; i++) { 198 | if (this.vendors[i] !== null) { 199 | cssProperty = this.vendors[i][0] + 'transform'; 200 | jsProperty = this.vendors[i][1] + 'Transform'; 201 | } else { 202 | cssProperty = 'transform'; 203 | jsProperty = 'transform'; 204 | } 205 | if (element.style[jsProperty] !== undefined) { 206 | propertySupport = true; 207 | break; 208 | } 209 | } 210 | switch(value) { 211 | case '2D': 212 | featureSupport = propertySupport; 213 | break; 214 | case '3D': 215 | if (propertySupport) { 216 | document.body.appendChild(element); 217 | element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 218 | propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty); 219 | featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== "none"; 220 | document.body.removeChild(element); 221 | } 222 | break; 223 | } 224 | return featureSupport; 225 | }; 226 | 227 | Parallax.prototype.ww = null; 228 | Parallax.prototype.wh = null; 229 | Parallax.prototype.hw = null; 230 | Parallax.prototype.hh = null; 231 | Parallax.prototype.portrait = null; 232 | Parallax.prototype.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i); 233 | Parallax.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 234 | Parallax.prototype.motionSupport = !!window.DeviceMotionEvent; 235 | Parallax.prototype.orientationSupport = !!window.DeviceOrientationEvent; 236 | Parallax.prototype.getUserMediaSupport = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); 237 | Parallax.prototype.headtrackrEnabled = false; //will turn to true if getUserMediaSupport and no DeviceMotion (or headtrackrPreferDeviceMotion = false) 238 | Parallax.prototype.orientationStatus = 0; 239 | Parallax.prototype.transform2DSupport = Parallax.prototype.transformSupport('2D'); 240 | Parallax.prototype.transform3DSupport = Parallax.prototype.transformSupport('3D'); 241 | 242 | /** 243 | * Method to be called when the headtrackr feature was asked for, there is getUserMedia support and no DeviceMotion or headtrackrPreferDeviceMotion = false 244 | */ 245 | Parallax.prototype.headtrackrPrepare = function(){ 246 | if(typeof headtrackr === "undefined"){ 247 | if(this.headtrackrScriptLocation !== null){ 248 | var headTrackrScript = document.createElement('script'), 249 | self = this; 250 | headTrackrScript.onload = function(script){ 251 | if(typeof headtrackr !== "undefined"){ 252 | self.headtrackrPrepare(); 253 | } 254 | else{ 255 | throw new Error("Wrong path to headtrackr script"); 256 | } 257 | }; 258 | headTrackrScript.src = this.headtrackrScriptLocation; 259 | document.getElementsByTagName('body')[0].appendChild(headTrackrScript); 260 | return false; 261 | } 262 | else{ 263 | throw new Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation"); 264 | } 265 | } 266 | 267 | var inputVideo = document.createElement('video'), 268 | canvasInput = document.createElement('canvas'), 269 | canvasDebug = null, 270 | videoWidth = "320", 271 | videoHeight = "240", 272 | headtrackrOptions = {}, 273 | self; 274 | 275 | //add the mousemove listener while connecting the camera 276 | //we'll remove it when the face is detected to plug trackr 277 | //then readd it when the trackr fails 278 | window.addEventListener('mousemove', this.onMouseMove); 279 | 280 | inputVideo.autoplay = true; 281 | inputVideo.loop = true; 282 | inputVideo.style.display = "none"; 283 | inputVideo.width = videoWidth; 284 | inputVideo.height = videoHeight; 285 | 286 | canvasInput.id = "headtrackr-display-video"; 287 | canvasInput.style.position = "fixed"; 288 | canvasInput.style.bottom = "0px"; 289 | canvasInput.style.right = "0px"; 290 | canvasInput.width = videoWidth; 291 | canvasInput.height = videoHeight; 292 | 293 | if(this.headtrackrDisplayVideo === true || this.headtrackrDebugView === true){ 294 | canvasInput.style.display = "block"; 295 | if(this.headtrackrDebugView === true){ 296 | canvasDebug = document.createElement('canvas'); 297 | canvasDebug.id = "headtrackr-debug-view"; 298 | canvasDebug.style.display = "block"; 299 | canvasDebug.style.position = "fixed"; 300 | canvasDebug.style.bottom = "0px"; 301 | canvasDebug.style.right = "0px"; 302 | canvasDebug.width = videoWidth; 303 | canvasDebug.height = videoHeight; 304 | headtrackrOptions.calcAngles = true; 305 | } 306 | } 307 | else{ 308 | this.headtrackrDebugView = false; 309 | canvasInput.style.display = "none"; 310 | } 311 | 312 | this.htrackr = new headtrackr.Tracker(headtrackrOptions); 313 | 314 | document.getElementsByTagName('body')[0].appendChild(inputVideo); 315 | document.getElementsByTagName('body')[0].appendChild(canvasInput); 316 | if(canvasDebug !== null){ 317 | document.getElementsByTagName('body')[0].appendChild(canvasDebug); 318 | this.htrackr.canvasDebug = canvasDebug; 319 | this.htrackr.ctxDebug = canvasDebug.getContext('2d'); 320 | } 321 | 322 | //callback to be used to display a "please allow your webcam" for example 323 | if(typeof(this.headtrackrOnBeforeCameraAccess) === "function"){ 324 | this.headtrackrOnBeforeCameraAccess(); 325 | } 326 | 327 | this.htrackr.init(inputVideo, canvasInput); 328 | this.htrackr.start(); 329 | this.htrackr.canvasInputInfos = { 330 | ww : canvasInput.width, 331 | wh : canvasInput.height, 332 | hw : canvasInput.width / 2, 333 | hh : canvasInput.height / 2 334 | }; 335 | 336 | self = this; 337 | document.addEventListener('headtrackrStatus', function(e){ 338 | console.log(e.status,e.type,e.timeStamp); 339 | if(e.status === "camera found"){ 340 | if(typeof(self.headtrackrOnCameraFound) === "function"){ 341 | self.headtrackrOnCameraFound(); 342 | } 343 | } 344 | else if(e.status === "found"){ 345 | window.removeEventListener('mousemove', self.onMouseMove); 346 | document.addEventListener("facetrackingEvent", self.onFaceTracking, false); 347 | } 348 | else if(e.status === "redetecting"){ 349 | window.addEventListener('mousemove', self.onMouseMove); 350 | document.removeEventListener("facetrackingEvent", self.onFaceTracking, false); 351 | } 352 | }); 353 | }; 354 | 355 | /** 356 | * Method to be called when the headtrackr was to be initiated but there was no getUserMediaSupport for 357 | */ 358 | Parallax.prototype.headtrackrFail = function(){ 359 | //if no callback is set, set the default callback 360 | if(typeof(this.headtrackrNoGetUserMediaCallback) !== "function"){ 361 | this.headtrackrNoGetUserMediaCallback = function(){ 362 | var message = "Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox"; 363 | console.log(message.replace(/
/g,' ').replace(/(<([^>]+)>)/ig,'')); 364 | 365 | var timeout = 10000; 366 | // create element and attach to body 367 | var d = document.createElement('div'), 368 | d2 = document.createElement('div'), 369 | p = document.createElement('p'); 370 | d.setAttribute('id', 'headtrackerMessageDiv'); 371 | 372 | d.style.left = "10%"; 373 | d.style.right = "10%"; 374 | d.style.top = "10%"; 375 | d.style.fontSize = "150%"; 376 | d.style.color = "#777"; 377 | d.style.position = "absolute"; 378 | d.style.fontFamily = "Helvetica, Arial, sans-serif"; 379 | d.style.zIndex = '100002'; 380 | 381 | d2.style.marginLeft = "auto"; 382 | d2.style.marginRight = "auto"; 383 | d2.style.width = "100%"; 384 | d2.style.textAlign = "center"; 385 | d2.style.color = "#fff"; 386 | d2.style.backgroundColor = "#444"; 387 | d2.style.opacity = "0.8"; 388 | 389 | p.setAttribute('id', 'parallaxHeadtrackerNoGetUserMediaMessage'); 390 | p.innerHTML = message; 391 | d2.appendChild(p); 392 | d.appendChild(d2); 393 | document.body.appendChild(d); 394 | 395 | setTimeout(function(){ 396 | d.parentNode.removeChild(d); 397 | },timeout); 398 | 399 | }; 400 | } 401 | this.headtrackrNoGetUserMediaCallback(); 402 | //back to normal behaviour 403 | this.headtrackr = false; 404 | }; 405 | 406 | Parallax.prototype.initialise = function() { 407 | 408 | // Configure Context Styles 409 | if (this.transform3DSupport) this.accelerate(this.element); 410 | var style = window.getComputedStyle(this.element); 411 | if (style.getPropertyValue('position') === 'static') { 412 | this.element.style.position = 'relative'; 413 | } 414 | 415 | // Configure Layer Styles 416 | for (var i = 0, l = this.layers.length; i < l; i++) { 417 | var layer = this.layers[i]; 418 | if (this.transform3DSupport) this.accelerate(layer); 419 | layer.style.position = i ? 'absolute' : 'relative'; 420 | layer.style.display = 'block'; 421 | layer.style.height = '100%'; 422 | layer.style.width = '100%'; 423 | layer.style.left = 0; 424 | layer.style.top = 0; 425 | 426 | // Cache Layer Depth 427 | this.depths.push(this.data(layer, 'depth') || 0); 428 | } 429 | 430 | // Setup 431 | 432 | //if headtrackr was asked, and can be supported (via getUserMedia), no matter if there is DeviceMotion on the device, enable it 433 | //if headtracker was asked but with headtrackrPreferDeviceMotion === true, the headtrackrPrepare() is made in the onOrientationTimer (where we make sure there is - or not - DeviceMotion support) 434 | if(this.headtrackr === true && this.getUserMediaSupport === true && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 435 | this.headtrackrPrepare(); 436 | this.headtrackrEnabled = true; 437 | } 438 | else if(this.headtrackr === true && this.getUserMediaSupport === false && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 439 | this.headtrackrFail(); 440 | } 441 | this.updateDimensions(); 442 | this.enable(); 443 | this.queueCalibration(this.calibrationDelay); 444 | }; 445 | 446 | Parallax.prototype.updateDimensions = function() { 447 | 448 | // Cache Context Dimensions 449 | this.ox = this.offset(this.element).left; 450 | this.oy = this.offset(this.element).top; 451 | this.ow = this.element.offsetWidth; 452 | this.oh = this.element.offsetHeight; 453 | 454 | // Cache Window Dimensions 455 | this.ww = window.innerWidth; 456 | this.wh = window.innerHeight; 457 | this.hw = this.ww / 2; 458 | this.hh = this.wh / 2; 459 | }; 460 | 461 | Parallax.prototype.queueCalibration = function(delay) { 462 | clearTimeout(this.calibrationTimer); 463 | this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay); 464 | }; 465 | 466 | Parallax.prototype.enable = function() { 467 | if (!this.enabled) { 468 | this.enabled = true; 469 | if (this.headtrackrEnabled === false && this.orientationSupport) { 470 | this.portrait = null; 471 | window.addEventListener('deviceorientation', this.onDeviceOrientation); 472 | setTimeout(this.onOrientationTimer, this.supportDelay); 473 | } 474 | else if (this.headtrackrEnabled === false) { 475 | this.cx = 0; 476 | this.cy = 0; 477 | this.portrait = false; 478 | window.addEventListener('mousemove', this.onMouseMove); 479 | } 480 | else { 481 | document.addEventListener("facetrackingEvent", this.onFaceTracking, false); 482 | } 483 | window.addEventListener('resize', this.onWindowResize); 484 | this.raf = requestAnimationFrame(this.onAnimationFrame); 485 | } 486 | }; 487 | 488 | Parallax.prototype.disable = function() { 489 | if (this.enabled) { 490 | this.enabled = false; 491 | if (this.headtrackrEnabled === false && this.orientationSupport) { 492 | window.removeEventListener('deviceorientation', this.onDeviceOrientation); 493 | } 494 | else if (this.headtrackrEnabled === false) { 495 | window.removeEventListener('mousemove', this.onMouseMove); 496 | } 497 | else { 498 | document.removeEventListener("facetrackingEvent", this.onFaceTracking, false); 499 | } 500 | window.removeEventListener('resize', this.onWindowResize); 501 | cancelAnimationFrame(this.raf); 502 | } 503 | }; 504 | 505 | Parallax.prototype.calibrate = function(x, y) { 506 | this.calibrateX = x === undefined ? this.calibrateX : x; 507 | this.calibrateY = y === undefined ? this.calibrateY : y; 508 | }; 509 | 510 | Parallax.prototype.invert = function(x, y) { 511 | this.invertX = x === undefined ? this.invertX : x; 512 | this.invertY = y === undefined ? this.invertY : y; 513 | }; 514 | 515 | Parallax.prototype.friction = function(x, y) { 516 | this.frictionX = x === undefined ? this.frictionX : x; 517 | this.frictionY = y === undefined ? this.frictionY : y; 518 | }; 519 | 520 | Parallax.prototype.scalar = function(x, y) { 521 | this.scalarX = x === undefined ? this.scalarX : x; 522 | this.scalarY = y === undefined ? this.scalarY : y; 523 | }; 524 | 525 | Parallax.prototype.headtrackrScalar = function(x, y) { 526 | this.headtrackrScalarX = x === undefined ? this.headtrackrScalarX : x; 527 | this.headtrackrScalarY = y === undefined ? this.headtrackrScalarY : y; 528 | }; 529 | 530 | Parallax.prototype.limit = function(x, y) { 531 | this.limitX = x === undefined ? this.limitX : x; 532 | this.limitY = y === undefined ? this.limitY : y; 533 | }; 534 | 535 | Parallax.prototype.clamp = function(value, min, max) { 536 | value = Math.max(value, min); 537 | value = Math.min(value, max); 538 | return value; 539 | }; 540 | 541 | Parallax.prototype.css = function(element, property, value) { 542 | var jsProperty = null; 543 | for (var i = 0, l = this.vendors.length; i < l; i++) { 544 | if (this.vendors[i] !== null) { 545 | jsProperty = this.camelCase(this.vendors[i][1] + '-' + property); 546 | } else { 547 | jsProperty = property; 548 | } 549 | if (element.style[jsProperty] !== undefined) { 550 | element.style[jsProperty] = value; 551 | break; 552 | } 553 | } 554 | }; 555 | 556 | Parallax.prototype.accelerate = function(element) { 557 | this.css(element, 'transform', 'translate3d(0,0,0)'); 558 | this.css(element, 'transform-style', 'preserve-3d'); 559 | this.css(element, 'backface-visibility', 'hidden'); 560 | }; 561 | 562 | Parallax.prototype.setPosition = function(element, x, y) { 563 | x += '%'; 564 | y += '%'; 565 | if (this.transform3DSupport) { 566 | this.css(element, 'transform', 'translate3d('+x+','+y+',0)'); 567 | } else if (this.transform2DSupport) { 568 | this.css(element, 'transform', 'translate('+x+','+y+')'); 569 | } else { 570 | element.style.left = x; 571 | element.style.top = y; 572 | } 573 | }; 574 | 575 | Parallax.prototype.onOrientationTimer = function(event) { 576 | if (this.orientationSupport && this.orientationStatus === 0) { 577 | this.disable(); 578 | this.orientationSupport = false; 579 | //only at this point we are sure there is no orientationSupport (can't rely on !!window.DeviceOrientationEvent, beacause we may be on a desktop that may expose the API but doesn't have any accelerometer) 580 | //so, we launch the headtrackr in fallback to deviceMotion as asked in the options as headtrackrPreferDeviceMotion = true 581 | if(this.headtrackr === true && this.getUserMediaSupport === true && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 582 | this.headtrackrPrepare(); 583 | this.headtrackrEnabled = true; 584 | } 585 | //in case there is no getUserMedia support but it was asked to use headtrackr 586 | else if(this.headtrackr === true && this.getUserMediaSupport === false && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 587 | this.headtrackrFail(); 588 | } 589 | this.enable(); 590 | } 591 | }; 592 | 593 | Parallax.prototype.onCalibrationTimer = function(event) { 594 | this.calibrationFlag = true; 595 | }; 596 | 597 | Parallax.prototype.onWindowResize = function(event) { 598 | this.updateDimensions(); 599 | }; 600 | 601 | Parallax.prototype.onAnimationFrame = function() { 602 | var dx = this.ix - this.cx; 603 | var dy = this.iy - this.cy; 604 | if ((Math.abs(dx) > this.calibrationThreshold) || (Math.abs(dy) > this.calibrationThreshold)) { 605 | this.queueCalibration(0); 606 | } 607 | if (this.portrait) { 608 | this.mx = (this.calibrateX ? dy : this.iy) * this.scalarX; 609 | this.my = (this.calibrateY ? dx : this.ix) * this.scalarY; 610 | } else { 611 | this.mx = (this.calibrateX ? dx : this.ix) * this.scalarX; 612 | this.my = (this.calibrateY ? dy : this.iy) * this.scalarY; 613 | } 614 | if (!isNaN(parseFloat(this.limitX))) { 615 | this.mx = this.clamp(this.mx, -this.limitX, this.limitX); 616 | } 617 | if (!isNaN(parseFloat(this.limitY))) { 618 | this.my = this.clamp(this.my, -this.limitY, this.limitY); 619 | } 620 | this.vx += (this.mx - this.vx) * this.frictionX; 621 | this.vy += (this.my - this.vy) * this.frictionY; 622 | for (var i = 0, l = this.layers.length; i < l; i++) { 623 | var layer = this.layers[i]; 624 | var depth = this.depths[i]; 625 | var xOffset = this.vx * depth * (this.invertX ? -1 : 1); 626 | var yOffset = this.vy * depth * (this.invertY ? -1 : 1); 627 | this.setPosition(layer, xOffset, yOffset); 628 | } 629 | this.raf = requestAnimationFrame(this.onAnimationFrame); 630 | }; 631 | 632 | Parallax.prototype.onDeviceOrientation = function(event) { 633 | 634 | // Validate environment and event properties. 635 | if (!this.desktop && event.beta !== null && event.gamma !== null) { 636 | 637 | // Set orientation status. 638 | this.orientationStatus = 1; 639 | 640 | // Extract Rotation 641 | var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 642 | var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 643 | 644 | // Detect Orientation Change 645 | var portrait = this.wh > this.ww; 646 | if (this.portrait !== portrait) { 647 | this.portrait = portrait; 648 | this.calibrationFlag = true; 649 | } 650 | 651 | // Set Calibration 652 | if (this.calibrationFlag) { 653 | this.calibrationFlag = false; 654 | this.cx = x; 655 | this.cy = y; 656 | } 657 | 658 | // Set Input 659 | this.ix = x; 660 | this.iy = y; 661 | } 662 | }; 663 | 664 | Parallax.prototype.onMouseMove = function(event) { 665 | 666 | // Calculate Input 667 | this.ix = (event.pageX - this.hw) / this.hw; 668 | this.iy = (event.pageY - this.hh) / this.hh; 669 | }; 670 | 671 | Parallax.prototype.onFaceTracking = function(event) { 672 | 673 | // Calculate Input 674 | if(event.detection === "CS"){ 675 | this.ix = -this.headtrackrScalarX*(event.x - this.htrackr.canvasInputInfos.hw) / this.htrackr.canvasInputInfos.hw; 676 | this.iy = this.headtrackrScalarY*(event.y - this.htrackr.canvasInputInfos.hh) / this.htrackr.canvasInputInfos.hh; 677 | if(this.headtrackrDebugView === true){ 678 | this.htrackr.canvasDebug.width = this.htrackr.canvasDebug.width; 679 | this.htrackr.ctxDebug.translate(event.x, event.y); 680 | this.htrackr.ctxDebug.rotate(event.angle-(Math.PI/2)); 681 | this.htrackr.ctxDebug.strokeStyle = "#00CC00"; 682 | this.htrackr.ctxDebug.strokeRect((-(event.width/2)) >> 0, (-(event.height/2)) >> 0, event.width, event.height); 683 | this.htrackr.ctxDebug.rotate((Math.PI/2)-event.angle); 684 | this.htrackr.ctxDebug.translate(-event.x, -event.y); 685 | } 686 | } 687 | 688 | }; 689 | 690 | // Expose Parallax 691 | window[NAME] = Parallax; 692 | 693 | })(window, document); 694 | 695 | /** 696 | * Request Animation Frame Polyfill. 697 | * @author Tino Zijdel 698 | * @author Paul Irish 699 | * @see https://gist.github.com/paulirish/1579671 700 | */ 701 | ;(function() { 702 | 703 | var lastTime = 0; 704 | var vendors = ['ms', 'moz', 'webkit', 'o']; 705 | 706 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 707 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 708 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 709 | } 710 | 711 | if (!window.requestAnimationFrame) { 712 | window.requestAnimationFrame = function(callback, element) { 713 | var currTime = new Date().getTime(); 714 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 715 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 716 | timeToCall); 717 | lastTime = currTime + timeToCall; 718 | return id; 719 | }; 720 | } 721 | 722 | if (!window.cancelAnimationFrame) { 723 | window.cancelAnimationFrame = function(id) { 724 | clearTimeout(id); 725 | }; 726 | } 727 | 728 | }()); 729 | -------------------------------------------------------------------------------- /deploy/parallax.min.js: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2013 Christophe Rosset - @topheman 6 | // Copyright (C) 2013 Matthew Wagerfield - @mwagerfield 7 | // 8 | // Permission is hereby granted, free of charge, to any 9 | // person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the 11 | // Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, 13 | // sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do 15 | // so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice 18 | // shall be included in all copies or substantial portions 19 | // of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 22 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 26 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 27 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 29 | // OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | //============================================================ 32 | (function(t,e,i){function a(t,e){this.element=t,this.layers=t.getElementsByClassName("layer");var i={calibrateX:this.data(this.element,"calibrate-x"),calibrateY:this.data(this.element,"calibrate-y"),invertX:this.data(this.element,"invert-x"),invertY:this.data(this.element,"invert-y"),limitX:this.data(this.element,"limit-x"),limitY:this.data(this.element,"limit-y"),scalarX:this.data(this.element,"scalar-x"),scalarY:this.data(this.element,"scalar-y"),frictionX:this.data(this.element,"friction-x"),frictionY:this.data(this.element,"friction-y"),headtrackr:this.data(this.element,"headtrackr"),headtrackrPreferDeviceMotion:this.data(this.element,"headtrackr-prefer-device-motion"),headtrackrDisplayVideo:this.data(this.element,"headtrackr-display-video"),headtrackrDebugView:this.data(this.element,"headtrackr-debug-view"),headtrackrScalarX:this.data(this.element,"headtrackr-scalar-x"),headtrackrScalarY:this.data(this.element,"headtrackr-scalar-y"),headtrackrScriptLocation:this.data(this.element,"headtrackr-script-location")};for(var a in i)null===i[a]&&delete i[a];this.extend(this,n,e,i),this.calibrationTimer=null,this.calibrationFlag=!0,this.enabled=!1,this.depths=[],this.raf=null,this.ox=0,this.oy=0,this.ow=0,this.oh=0,this.cx=0,this.cy=0,this.ix=0,this.iy=0,this.mx=0,this.my=0,this.vx=0,this.vy=0,this.onFaceTracking=this.onFaceTracking.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.onDeviceOrientation=this.onDeviceOrientation.bind(this),this.onOrientationTimer=this.onOrientationTimer.bind(this),this.onCalibrationTimer=this.onCalibrationTimer.bind(this),this.onAnimationFrame=this.onAnimationFrame.bind(this),this.onWindowResize=this.onWindowResize.bind(this),this.initialise()}var r="Parallax",s=30,n={calibrationThreshold:100,calibrationDelay:500,supportDelay:500,calibrateX:!1,calibrateY:!0,invertX:!0,invertY:!0,limitX:!1,limitY:!1,scalarX:10,scalarY:10,frictionX:.1,frictionY:.1,headtrackr:!1,headtrackrPreferDeviceMotion:!0,headtrackrDisplayVideo:!1,headtrackrDebugView:!1,headtrackrScalarX:3,headtrackrScalarY:3,headtrackrScriptLocation:null};a.prototype.extend=function(){if(arguments.length>1)for(var t=arguments[0],e=1,i=arguments.length;i>e;e++){var a=arguments[e];for(var r in a)t[r]=a[r]}},a.prototype.data=function(t,e){return this.deserialize(t.getAttribute("data-"+e))},a.prototype.deserialize=function(t){return"true"===t?!0:"false"===t?!1:"null"===t?null:!isNaN(parseFloat(t))&&isFinite(t)?parseFloat(t):t},a.prototype.offset=function(t){for(var e=0,i=0;t&&!isNaN(t.offsetLeft)&&!isNaN(t.offsetTop);)e+=t.offsetLeft-t.scrollLeft,i+=t.offsetTop-t.scrollTop,t=t.offsetParent;return{top:i,left:e}},a.prototype.camelCase=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},a.prototype.transformSupport=function(a){for(var r=e.createElement("div"),s=!1,n=null,o=!1,h=null,l=null,c=0,d=this.vendors.length;d>c;c++)if(null!==this.vendors[c]?(h=this.vendors[c][0]+"transform",l=this.vendors[c][1]+"Transform"):(h="transform",l="transform"),r.style[l]!==i){s=!0;break}switch(a){case"2D":o=s;break;case"3D":s&&(e.body.appendChild(r),r.style[l]="translate3d(1px,1px,1px)",n=t.getComputedStyle(r).getPropertyValue(h),o=n!==i&&n.length>0&&"none"!==n,e.body.removeChild(r))}return o},a.prototype.ww=null,a.prototype.wh=null,a.prototype.hw=null,a.prototype.hh=null,a.prototype.portrait=null,a.prototype.desktop=!navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i),a.prototype.vendors=[null,["-webkit-","webkit"],["-moz-","Moz"],["-o-","O"],["-ms-","ms"]],a.prototype.motionSupport=!!t.DeviceMotionEvent,a.prototype.orientationSupport=!!t.DeviceOrientationEvent,a.prototype.getUserMediaSupport=!!(navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia),a.prototype.headtrackrEnabled=!1,a.prototype.orientationStatus=0,a.prototype.transform2DSupport=a.prototype.transformSupport("2D"),a.prototype.transform3DSupport=a.prototype.transformSupport("3D"),a.prototype.headtrackrPrepare=function(){if("undefined"==typeof headtrackr){if(null!==this.headtrackrScriptLocation){var i=e.createElement("script"),a=this;return i.onload=function(){if("undefined"==typeof headtrackr)throw Error("Wrong path to headtrackr script");a.headtrackrPrepare()},i.src=this.headtrackrScriptLocation,e.getElementsByTagName("body")[0].appendChild(i),!1}throw Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation")}var a,r=e.createElement("video"),s=e.createElement("canvas"),n=null,o="320",h="240",l={};t.addEventListener("mousemove",this.onMouseMove),r.autoplay=!0,r.loop=!0,r.style.display="none",r.width=o,r.height=h,s.id="headtrackr-display-video",s.style.position="fixed",s.style.bottom="0px",s.style.right="0px",s.width=o,s.height=h,this.headtrackrDisplayVideo===!0||this.headtrackrDebugView===!0?(s.style.display="block",this.headtrackrDebugView===!0&&(n=e.createElement("canvas"),n.id="headtrackr-debug-view",n.style.display="block",n.style.position="fixed",n.style.bottom="0px",n.style.right="0px",n.width=o,n.height=h,l.calcAngles=!0)):(this.headtrackrDebugView=!1,s.style.display="none"),this.htrackr=new headtrackr.Tracker(l),e.getElementsByTagName("body")[0].appendChild(r),e.getElementsByTagName("body")[0].appendChild(s),null!==n&&(e.getElementsByTagName("body")[0].appendChild(n),this.htrackr.canvasDebug=n,this.htrackr.ctxDebug=n.getContext("2d")),"function"==typeof this.headtrackrOnBeforeCameraAccess&&this.headtrackrOnBeforeCameraAccess(),this.htrackr.init(r,s),this.htrackr.start(),this.htrackr.canvasInputInfos={ww:s.width,wh:s.height,hw:s.width/2,hh:s.height/2},a=this,e.addEventListener("headtrackrStatus",function(i){console.log(i.status,i.type,i.timeStamp),"camera found"===i.status?"function"==typeof a.headtrackrOnCameraFound&&a.headtrackrOnCameraFound():"found"===i.status?(t.removeEventListener("mousemove",a.onMouseMove),e.addEventListener("facetrackingEvent",a.onFaceTracking,!1)):"redetecting"===i.status&&(t.addEventListener("mousemove",a.onMouseMove),e.removeEventListener("facetrackingEvent",a.onFaceTracking,!1))})},a.prototype.headtrackrFail=function(){"function"!=typeof this.headtrackrNoGetUserMediaCallback&&(this.headtrackrNoGetUserMediaCallback=function(){var t="Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox";console.log(t.replace(/
/g," ").replace(/(<([^>]+)>)/gi,""));var i=1e4,a=e.createElement("div"),r=e.createElement("div"),s=e.createElement("p");a.setAttribute("id","headtrackerMessageDiv"),a.style.left="10%",a.style.right="10%",a.style.top="10%",a.style.fontSize="150%",a.style.color="#777",a.style.position="absolute",a.style.fontFamily="Helvetica, Arial, sans-serif",a.style.zIndex="100002",r.style.marginLeft="auto",r.style.marginRight="auto",r.style.width="100%",r.style.textAlign="center",r.style.color="#fff",r.style.backgroundColor="#444",r.style.opacity="0.8",s.setAttribute("id","parallaxHeadtrackerNoGetUserMediaMessage"),s.innerHTML=t,r.appendChild(s),a.appendChild(r),e.body.appendChild(a),setTimeout(function(){a.parentNode.removeChild(a)},i)}),this.headtrackrNoGetUserMediaCallback(),this.headtrackr=!1},a.prototype.initialise=function(){this.transform3DSupport&&this.accelerate(this.element);var e=t.getComputedStyle(this.element);"static"===e.getPropertyValue("position")&&(this.element.style.position="relative");for(var i=0,a=this.layers.length;a>i;i++){var r=this.layers[i];this.transform3DSupport&&this.accelerate(r),r.style.position=i?"absolute":"relative",r.style.display="block",r.style.height="100%",r.style.width="100%",r.style.left=0,r.style.top=0,this.depths.push(this.data(r,"depth")||0)}this.headtrackr!==!0||this.getUserMediaSupport!==!0||this.headtrackrPreferDeviceMotion!==!1&&this.orientationSupport!==!1||this.headtrackrEnabled!==!1?this.headtrackr!==!0||this.getUserMediaSupport!==!1||this.headtrackrPreferDeviceMotion!==!1&&this.orientationSupport!==!1||this.headtrackrEnabled!==!1||this.headtrackrFail():(this.headtrackrPrepare(),this.headtrackrEnabled=!0),this.updateDimensions(),this.enable(),this.queueCalibration(this.calibrationDelay)},a.prototype.updateDimensions=function(){this.ox=this.offset(this.element).left,this.oy=this.offset(this.element).top,this.ow=this.element.offsetWidth,this.oh=this.element.offsetHeight,this.ww=t.innerWidth,this.wh=t.innerHeight,this.hw=this.ww/2,this.hh=this.wh/2},a.prototype.queueCalibration=function(t){clearTimeout(this.calibrationTimer),this.calibrationTimer=setTimeout(this.onCalibrationTimer,t)},a.prototype.enable=function(){this.enabled||(this.enabled=!0,this.headtrackrEnabled===!1&&this.orientationSupport?(this.portrait=null,t.addEventListener("deviceorientation",this.onDeviceOrientation),setTimeout(this.onOrientationTimer,this.supportDelay)):this.headtrackrEnabled===!1?(this.cx=0,this.cy=0,this.portrait=!1,t.addEventListener("mousemove",this.onMouseMove)):e.addEventListener("facetrackingEvent",this.onFaceTracking,!1),t.addEventListener("resize",this.onWindowResize),this.raf=requestAnimationFrame(this.onAnimationFrame))},a.prototype.disable=function(){this.enabled&&(this.enabled=!1,this.headtrackrEnabled===!1&&this.orientationSupport?t.removeEventListener("deviceorientation",this.onDeviceOrientation):this.headtrackrEnabled===!1?t.removeEventListener("mousemove",this.onMouseMove):e.removeEventListener("facetrackingEvent",this.onFaceTracking,!1),t.removeEventListener("resize",this.onWindowResize),cancelAnimationFrame(this.raf))},a.prototype.calibrate=function(t,e){this.calibrateX=t===i?this.calibrateX:t,this.calibrateY=e===i?this.calibrateY:e},a.prototype.invert=function(t,e){this.invertX=t===i?this.invertX:t,this.invertY=e===i?this.invertY:e},a.prototype.friction=function(t,e){this.frictionX=t===i?this.frictionX:t,this.frictionY=e===i?this.frictionY:e},a.prototype.scalar=function(t,e){this.scalarX=t===i?this.scalarX:t,this.scalarY=e===i?this.scalarY:e},a.prototype.headtrackrScalar=function(t,e){this.headtrackrScalarX=t===i?this.headtrackrScalarX:t,this.headtrackrScalarY=e===i?this.headtrackrScalarY:e},a.prototype.limit=function(t,e){this.limitX=t===i?this.limitX:t,this.limitY=e===i?this.limitY:e},a.prototype.clamp=function(t,e,i){return t=Math.max(t,e),t=Math.min(t,i)},a.prototype.css=function(t,e,a){for(var r=null,s=0,n=this.vendors.length;n>s;s++)if(r=null!==this.vendors[s]?this.camelCase(this.vendors[s][1]+"-"+e):e,t.style[r]!==i){t.style[r]=a;break}},a.prototype.accelerate=function(t){this.css(t,"transform","translate3d(0,0,0)"),this.css(t,"transform-style","preserve-3d"),this.css(t,"backface-visibility","hidden")},a.prototype.setPosition=function(t,e,i){e+="%",i+="%",this.transform3DSupport?this.css(t,"transform","translate3d("+e+","+i+",0)"):this.transform2DSupport?this.css(t,"transform","translate("+e+","+i+")"):(t.style.left=e,t.style.top=i)},a.prototype.onOrientationTimer=function(){this.orientationSupport&&0===this.orientationStatus&&(this.disable(),this.orientationSupport=!1,this.headtrackr===!0&&this.getUserMediaSupport===!0&&this.headtrackrPreferDeviceMotion===!0&&this.headtrackrEnabled===!1?(this.headtrackrPrepare(),this.headtrackrEnabled=!0):this.headtrackr===!0&&this.getUserMediaSupport===!1&&this.headtrackrPreferDeviceMotion===!0&&this.headtrackrEnabled===!1&&this.headtrackrFail(),this.enable())},a.prototype.onCalibrationTimer=function(){this.calibrationFlag=!0},a.prototype.onWindowResize=function(){this.updateDimensions()},a.prototype.onAnimationFrame=function(){var t=this.ix-this.cx,e=this.iy-this.cy;(Math.abs(t)>this.calibrationThreshold||Math.abs(e)>this.calibrationThreshold)&&this.queueCalibration(0),this.portrait?(this.mx=(this.calibrateX?e:this.iy)*this.scalarX,this.my=(this.calibrateY?t:this.ix)*this.scalarY):(this.mx=(this.calibrateX?t:this.ix)*this.scalarX,this.my=(this.calibrateY?e:this.iy)*this.scalarY),isNaN(parseFloat(this.limitX))||(this.mx=this.clamp(this.mx,-this.limitX,this.limitX)),isNaN(parseFloat(this.limitY))||(this.my=this.clamp(this.my,-this.limitY,this.limitY)),this.vx+=(this.mx-this.vx)*this.frictionX,this.vy+=(this.my-this.vy)*this.frictionY;for(var i=0,a=this.layers.length;a>i;i++){var r=this.layers[i],s=this.depths[i],n=this.vx*s*(this.invertX?-1:1),o=this.vy*s*(this.invertY?-1:1);this.setPosition(r,n,o)}this.raf=requestAnimationFrame(this.onAnimationFrame)},a.prototype.onDeviceOrientation=function(t){if(!this.desktop&&null!==t.beta&&null!==t.gamma){this.orientationStatus=1;var e=(t.beta||0)/s,i=(t.gamma||0)/s,a=this.wh>this.ww;this.portrait!==a&&(this.portrait=a,this.calibrationFlag=!0),this.calibrationFlag&&(this.calibrationFlag=!1,this.cx=e,this.cy=i),this.ix=e,this.iy=i}},a.prototype.onMouseMove=function(t){this.ix=(t.pageX-this.hw)/this.hw,this.iy=(t.pageY-this.hh)/this.hh},a.prototype.onFaceTracking=function(t){"CS"===t.detection&&(this.ix=-this.headtrackrScalarX*(t.x-this.htrackr.canvasInputInfos.hw)/this.htrackr.canvasInputInfos.hw,this.iy=this.headtrackrScalarY*(t.y-this.htrackr.canvasInputInfos.hh)/this.htrackr.canvasInputInfos.hh,this.headtrackrDebugView===!0&&(this.htrackr.canvasDebug.width=this.htrackr.canvasDebug.width,this.htrackr.ctxDebug.translate(t.x,t.y),this.htrackr.ctxDebug.rotate(t.angle-Math.PI/2),this.htrackr.ctxDebug.strokeStyle="#00CC00",this.htrackr.ctxDebug.strokeRect(-(t.width/2)>>0,-(t.height/2)>>0,t.width,t.height),this.htrackr.ctxDebug.rotate(Math.PI/2-t.angle),this.htrackr.ctxDebug.translate(-t.x,-t.y)))},t[r]=a})(window,document),function(){for(var t=0,e=["ms","moz","webkit","o"],i=0;e.length>i&&!window.requestAnimationFrame;++i)window.requestAnimationFrame=window[e[i]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[i]+"CancelAnimationFrame"]||window[e[i]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(e){var i=(new Date).getTime(),a=Math.max(0,16-(i-t)),r=window.setTimeout(function(){e(i+a)},a);return t=i+a,r}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(t){clearTimeout(t)})}(); -------------------------------------------------------------------------------- /examples/images/layer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer1.png -------------------------------------------------------------------------------- /examples/images/layer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer2.png -------------------------------------------------------------------------------- /examples/images/layer3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer3.png -------------------------------------------------------------------------------- /examples/images/layer4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer4.png -------------------------------------------------------------------------------- /examples/images/layer5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer5.png -------------------------------------------------------------------------------- /examples/images/layer6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layer6.png -------------------------------------------------------------------------------- /examples/images/layers.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topheman/parallax/85a09f6c1a3800ce4bde6c985e0e2c2b9a22f0e8/examples/images/layers.psd -------------------------------------------------------------------------------- /examples/jquery.headtrackr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | jQuery Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | jQuery Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 28 |
29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/simple.headtrackr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js with headtrackr | Simple Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 28 |
29 | 30 | 31 | 32 | 33 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Simple Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 28 |
29 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/styles/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #111; 3 | margin: 0; 4 | } 5 | 6 | img { 7 | display: block; 8 | width: 100%; 9 | } 10 | 11 | .scene { 12 | max-width: 500px; 13 | margin: 0 auto; 14 | padding: 0; 15 | } 16 | 17 | .layer:nth-child(1) { 18 | opacity: 0.15; 19 | } 20 | .layer:nth-child(2) { 21 | opacity: 0.30; 22 | } 23 | .layer:nth-child(3) { 24 | opacity: 0.45; 25 | } 26 | .layer:nth-child(4) { 27 | opacity: 0.60; 28 | } 29 | .layer:nth-child(5) { 30 | opacity: 0.75; 31 | } 32 | .layer:nth-child(6) { 33 | opacity: 0.90; 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallax", 3 | "description" : "Simple package to add grunt support for development", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/topheman/parallax" 7 | }, 8 | "version": "1.0.0", 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "matchdep": ">=0.1.1", 12 | "grunt": ">=0.4.0", 13 | "grunt-contrib-connect": ">=0.1.2", 14 | "grunt-open": ">=0.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/jquery.parallax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery/Zepto Parallax Plugin 3 | * @author Matthew Wagerfield - @mwagerfield 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 | var NAME = 'parallax'; 11 | var MAGIC_NUMBER = 30; 12 | var DEFAULTS = { 13 | calibrationThreshold: 100, 14 | calibrationDelay: 500, 15 | supportDelay: 500, 16 | calibrateX: false, 17 | calibrateY: true, 18 | invertX: true, 19 | invertY: true, 20 | limitX: false, 21 | limitY: false, 22 | scalarX: 10.0, 23 | scalarY: 10.0, 24 | frictionX: 0.1, 25 | frictionY: 0.1, 26 | headtrackr: false, 27 | headtrackrPreferDeviceMotion: true, 28 | headtrackrDisplayVideo: false, 29 | headtrackrDebugView: false, 30 | headtrackrScalarX: 3, 31 | headtrackrScalarY: 3, 32 | headtrackrScriptLocation: null 33 | }; 34 | 35 | function Plugin(element, options) { 36 | 37 | // DOM Context 38 | this.element = element; 39 | 40 | // Selections 41 | this.$context = $(element).data('api', this); 42 | this.$layers = this.$context.find('.layer'); 43 | 44 | // Data Extraction 45 | var data = { 46 | calibrateX: this.$context.data('calibrate-x') || null, 47 | calibrateY: this.$context.data('calibrate-y') || null, 48 | invertX: this.$context.data('invert-x') || null, 49 | invertY: this.$context.data('invert-y') || null, 50 | limitX: parseFloat(this.$context.data('limit-x')) || null, 51 | limitY: parseFloat(this.$context.data('limit-y')) || null, 52 | scalarX: parseFloat(this.$context.data('scalar-x')) || null, 53 | scalarY: parseFloat(this.$context.data('scalar-y')) || null, 54 | frictionX: parseFloat(this.$context.data('friction-x')) || null, 55 | frictionY: parseFloat(this.$context.data('friction-y')) || null, 56 | headtrackr: this.$context.data('headtrackr') || null, 57 | headtrackrPreferDeviceMotion: this.$context.data('headtrackr-prefer-device-motion') || null, 58 | headtrackrDisplayVideo: this.$context.data('headtrackr-display-video') || null, 59 | headtrackrDebugView: this.$context.data('headtrackr-debug-view') || null, 60 | headtrackrScalarX: parseFloat(this.$context.data('headtrackr-scalar-x')) || null, 61 | headtrackrScalarY: parseFloat(this.$context.data('headtrackr-scalar-y')) || null, 62 | headtrackrScriptLocation: this.$context.data('headtrackr-script-location') || null 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.depths = []; 78 | this.raf = null; 79 | 80 | // Offset 81 | this.ox = 0; 82 | this.oy = 0; 83 | this.ow = 0; 84 | this.oh = 0; 85 | 86 | // Calibration 87 | this.cx = 0; 88 | this.cy = 0; 89 | 90 | // Input 91 | this.ix = 0; 92 | this.iy = 0; 93 | 94 | // Motion 95 | this.mx = 0; 96 | this.my = 0; 97 | 98 | // Velocity 99 | this.vx = 0; 100 | this.vy = 0; 101 | 102 | // Callbacks 103 | this.onFaceTracking = this.onFaceTracking.bind(this); 104 | this.onMouseMove = this.onMouseMove.bind(this); 105 | this.onDeviceOrientation = this.onDeviceOrientation.bind(this); 106 | this.onOrientationTimer = this.onOrientationTimer.bind(this); 107 | this.onCalibrationTimer = this.onCalibrationTimer.bind(this); 108 | this.onAnimationFrame = this.onAnimationFrame.bind(this); 109 | this.onWindowResize = this.onWindowResize.bind(this); 110 | 111 | // Initialise 112 | this.initialise(); 113 | } 114 | 115 | Plugin.prototype.transformSupport = function(value) { 116 | var element = document.createElement('div'); 117 | var propertySupport = false; 118 | var propertyValue = null; 119 | var featureSupport = false; 120 | var cssProperty = null; 121 | var jsProperty = null; 122 | for (var i = 0, l = this.vendors.length; i < l; i++) { 123 | if (this.vendors[i] !== null) { 124 | cssProperty = this.vendors[i][0] + 'transform'; 125 | jsProperty = this.vendors[i][1] + 'Transform'; 126 | } else { 127 | cssProperty = 'transform'; 128 | jsProperty = 'transform'; 129 | } 130 | if (element.style[jsProperty] !== undefined) { 131 | propertySupport = true; 132 | break; 133 | } 134 | } 135 | switch(value) { 136 | case '2D': 137 | featureSupport = propertySupport; 138 | break; 139 | case '3D': 140 | if (propertySupport) { 141 | document.body.appendChild(element); 142 | element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 143 | propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty); 144 | featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== "none"; 145 | document.body.removeChild(element); 146 | } 147 | break; 148 | } 149 | return featureSupport; 150 | }; 151 | 152 | Plugin.prototype.ww = null; 153 | Plugin.prototype.wh = null; 154 | Plugin.prototype.hw = null; 155 | Plugin.prototype.hh = null; 156 | Plugin.prototype.portrait = null; 157 | Plugin.prototype.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i); 158 | Plugin.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 159 | Plugin.prototype.motionSupport = !!window.DeviceMotionEvent; 160 | Plugin.prototype.orientationSupport = !!window.DeviceOrientationEvent; 161 | Plugin.prototype.getUserMediaSupport = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); 162 | Plugin.prototype.headtrackrEnabled = false; //will turn to true if getUserMediaSupport and no DeviceMotion (or headtrackrPreferDeviceMotion = false) 163 | Plugin.prototype.orientationStatus = 0; 164 | Plugin.prototype.transform2DSupport = Plugin.prototype.transformSupport('2D'); 165 | Plugin.prototype.transform3DSupport = Plugin.prototype.transformSupport('3D'); 166 | 167 | /** 168 | * Method to be called when the headtrackr feature was asked for, there is getUserMedia support and no DeviceMotion or headtrackrPreferDeviceMotion = false 169 | */ 170 | Plugin.prototype.headtrackrPrepare = function(){ 171 | if(typeof headtrackr === "undefined"){ 172 | if(this.headtrackrScriptLocation !== null){ 173 | var headTrackrScript = document.createElement('script'), 174 | self = this; 175 | headTrackrScript.onload = function(script){ 176 | if(typeof headtrackr !== "undefined"){ 177 | self.headtrackrPrepare(); 178 | } 179 | else{ 180 | throw new Error("Wrong path to headtrackr script"); 181 | } 182 | }; 183 | headTrackrScript.src = this.headtrackrScriptLocation; 184 | document.getElementsByTagName('body')[0].appendChild(headTrackrScript); 185 | return false; 186 | } 187 | else{ 188 | throw new Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation"); 189 | } 190 | } 191 | 192 | var inputVideo = document.createElement('video'), 193 | canvasInput = document.createElement('canvas'), 194 | canvasDebug = null, 195 | videoWidth = "320", 196 | videoHeight = "240", 197 | headtrackrOptions = {}, 198 | self; 199 | 200 | //add the mousemove listener while connecting the camera 201 | //we'll remove it when the face is detected to plug trackr 202 | //then readd it when the trackr fails 203 | window.addEventListener('mousemove', this.onMouseMove); 204 | 205 | inputVideo.autoplay = true; 206 | inputVideo.loop = true; 207 | inputVideo.style.display = "none"; 208 | inputVideo.width = videoWidth; 209 | inputVideo.height = videoHeight; 210 | 211 | canvasInput.id = "headtrackr-display-video"; 212 | canvasInput.style.position = "fixed"; 213 | canvasInput.style.bottom = "0px"; 214 | canvasInput.style.right = "0px"; 215 | canvasInput.width = videoWidth; 216 | canvasInput.height = videoHeight; 217 | 218 | if(this.headtrackrDisplayVideo === true || this.headtrackrDebugView === true){ 219 | canvasInput.style.display = "block"; 220 | if(this.headtrackrDebugView === true){ 221 | canvasDebug = document.createElement('canvas'); 222 | canvasDebug.id = "headtrackr-debug-view"; 223 | canvasDebug.style.display = "block"; 224 | canvasDebug.style.position = "fixed"; 225 | canvasDebug.style.bottom = "0px"; 226 | canvasDebug.style.right = "0px"; 227 | canvasDebug.width = videoWidth; 228 | canvasDebug.height = videoHeight; 229 | headtrackrOptions.calcAngles = true; 230 | } 231 | } 232 | else{ 233 | this.headtrackrDebugView = false; 234 | canvasInput.style.display = "none"; 235 | } 236 | 237 | this.htrackr = new headtrackr.Tracker(headtrackrOptions); 238 | 239 | document.getElementsByTagName('body')[0].appendChild(inputVideo); 240 | document.getElementsByTagName('body')[0].appendChild(canvasInput); 241 | if(canvasDebug !== null){ 242 | document.getElementsByTagName('body')[0].appendChild(canvasDebug); 243 | this.htrackr.canvasDebug = canvasDebug; 244 | this.htrackr.ctxDebug = canvasDebug.getContext('2d'); 245 | } 246 | 247 | //callback to be used to display a "please allow your webcam" for example 248 | if(typeof(this.headtrackrOnBeforeCameraAccess) === "function"){ 249 | this.headtrackrOnBeforeCameraAccess(); 250 | } 251 | 252 | this.htrackr.init(inputVideo, canvasInput); 253 | this.htrackr.start(); 254 | this.htrackr.canvasInputInfos = { 255 | ww : canvasInput.width, 256 | wh : canvasInput.height, 257 | hw : canvasInput.width / 2, 258 | hh : canvasInput.height / 2 259 | }; 260 | 261 | self = this; 262 | document.addEventListener('headtrackrStatus', function(e){ 263 | console.log(e.status,e.type,e.timeStamp); 264 | if(e.status === "camera found"){ 265 | if(typeof(self.headtrackrOnCameraFound) === "function"){ 266 | self.headtrackrOnCameraFound(); 267 | } 268 | } 269 | else if(e.status === "found"){ 270 | window.removeEventListener('mousemove', self.onMouseMove); 271 | document.addEventListener("facetrackingEvent", self.onFaceTracking, false); 272 | } 273 | else if(e.status === "redetecting"){ 274 | window.addEventListener('mousemove', self.onMouseMove); 275 | document.removeEventListener("facetrackingEvent", self.onFaceTracking, false); 276 | } 277 | }); 278 | }; 279 | 280 | /** 281 | * Method to be called when the headtrackr was to be initiated but there was no getUserMediaSupport for 282 | */ 283 | Plugin.prototype.headtrackrFail = function(){ 284 | //if no callback is set, set the default callback 285 | if(typeof(this.headtrackrNoGetUserMediaCallback) !== "function"){ 286 | this.headtrackrNoGetUserMediaCallback = function(){ 287 | var message = "Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox"; 288 | console.log(message.replace(/
/g,' ').replace(/(<([^>]+)>)/ig,'')); 289 | 290 | var timeout = 10000; 291 | // create element and attach to body 292 | var d = document.createElement('div'), 293 | d2 = document.createElement('div'), 294 | p = document.createElement('p'); 295 | d.setAttribute('id', 'headtrackerMessageDiv'); 296 | 297 | d.style.left = "10%"; 298 | d.style.right = "10%"; 299 | d.style.top = "10%"; 300 | d.style.fontSize = "150%"; 301 | d.style.color = "#777"; 302 | d.style.position = "absolute"; 303 | d.style.fontFamily = "Helvetica, Arial, sans-serif"; 304 | d.style.zIndex = '100002'; 305 | 306 | d2.style.marginLeft = "auto"; 307 | d2.style.marginRight = "auto"; 308 | d2.style.width = "100%"; 309 | d2.style.textAlign = "center"; 310 | d2.style.color = "#fff"; 311 | d2.style.backgroundColor = "#444"; 312 | d2.style.opacity = "0.8"; 313 | 314 | p.setAttribute('id', 'parallaxHeadtrackerNoGetUserMediaMessage'); 315 | p.innerHTML = message; 316 | d2.appendChild(p); 317 | d.appendChild(d2); 318 | document.body.appendChild(d); 319 | 320 | setTimeout(function(){ 321 | d.parentNode.removeChild(d); 322 | },timeout); 323 | 324 | }; 325 | } 326 | this.headtrackrNoGetUserMediaCallback(); 327 | //back to normal behaviour 328 | this.headtrackr = false; 329 | }; 330 | 331 | Plugin.prototype.initialise = function() { 332 | 333 | // Configure Styles 334 | if (this.$context.css('position') === 'static') { 335 | this.$context.css({ 336 | position:'relative' 337 | }); 338 | } 339 | this.$layers.css({ 340 | position:'absolute', 341 | display:'block', 342 | height:'100%', 343 | width:'100%', 344 | left: 0, 345 | top: 0 346 | }); 347 | this.$layers.first().css({ 348 | position:'relative' 349 | }); 350 | 351 | // Cache Depths 352 | this.$layers.each($.proxy(function(index, element) { 353 | this.depths.push($(element).data('depth') || 0); 354 | }, this)); 355 | 356 | // Hardware Accelerate Elements 357 | this.accelerate(this.$context); 358 | this.accelerate(this.$layers); 359 | 360 | // Setup 361 | 362 | //if headtrackr was asked, and can be supported (via getUserMedia), no matter if there is DeviceMotion on the device, enable it 363 | //if headtracker was asked but with headtrackrPreferDeviceMotion === true, the headtrackrPrepare() is made in the onOrientationTimer (where we make sure there is - or not - DeviceMotion support) 364 | if(this.headtrackr === true && this.getUserMediaSupport === true && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 365 | this.headtrackrPrepare(); 366 | this.headtrackrEnabled = true; 367 | } 368 | else if(this.headtrackr === true && this.getUserMediaSupport === false && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 369 | this.headtrackrFail(); 370 | } 371 | this.updateDimensions(); 372 | this.enable(); 373 | this.queueCalibration(this.calibrationDelay); 374 | }; 375 | 376 | Plugin.prototype.updateDimensions = function() { 377 | 378 | // Cache Context Dimensions 379 | this.ox = this.$context.offset().left; 380 | this.oy = this.$context.offset().top; 381 | this.ow = this.$context.width(); 382 | this.oh = this.$context.height(); 383 | 384 | // Cache Window Dimensions 385 | this.ww = window.innerWidth; 386 | this.wh = window.innerHeight; 387 | this.hw = this.ww / 2; 388 | this.hh = this.wh / 2; 389 | }; 390 | 391 | Plugin.prototype.queueCalibration = function(delay) { 392 | clearTimeout(this.calibrationTimer); 393 | this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay); 394 | }; 395 | 396 | Plugin.prototype.enable = function() { 397 | if (!this.enabled) { 398 | this.enabled = true; 399 | if (this.headtrackrEnabled === false && this.orientationSupport) { 400 | this.portrait = null; 401 | window.addEventListener('deviceorientation', this.onDeviceOrientation); 402 | setTimeout(this.onOrientationTimer, this.supportDelay); 403 | } 404 | else if (this.headtrackrEnabled === false) { 405 | this.cx = 0; 406 | this.cy = 0; 407 | this.portrait = false; 408 | window.addEventListener('mousemove', this.onMouseMove); 409 | } 410 | else { 411 | document.addEventListener("facetrackingEvent", this.onFaceTracking, false); 412 | } 413 | window.addEventListener('resize', this.onWindowResize); 414 | this.raf = requestAnimationFrame(this.onAnimationFrame); 415 | } 416 | }; 417 | 418 | Plugin.prototype.disable = function() { 419 | if (this.enabled) { 420 | this.enabled = false; 421 | if (this.headtrackrEnabled === false && this.orientationSupport) { 422 | window.removeEventListener('deviceorientation', this.onDeviceOrientation); 423 | } 424 | else if (this.headtrackrEnabled === false) { 425 | window.removeEventListener('mousemove', this.onMouseMove); 426 | } 427 | else { 428 | document.removeEventListener("facetrackingEvent", this.onFaceTracking, false); 429 | } 430 | window.removeEventListener('resize', this.onWindowResize); 431 | cancelAnimationFrame(this.raf); 432 | } 433 | }; 434 | 435 | Plugin.prototype.calibrate = function(x, y) { 436 | this.calibrateX = x === undefined ? this.calibrateX : x; 437 | this.calibrateY = y === undefined ? this.calibrateY : y; 438 | }; 439 | 440 | Plugin.prototype.invert = function(x, y) { 441 | this.invertX = x === undefined ? this.invertX : x; 442 | this.invertY = y === undefined ? this.invertY : y; 443 | }; 444 | 445 | Plugin.prototype.friction = function(x, y) { 446 | this.frictionX = x === undefined ? this.frictionX : x; 447 | this.frictionY = y === undefined ? this.frictionY : y; 448 | }; 449 | 450 | Plugin.prototype.scalar = function(x, y) { 451 | this.scalarX = x === undefined ? this.scalarX : x; 452 | this.scalarY = y === undefined ? this.scalarY : y; 453 | }; 454 | 455 | Plugin.prototype.headtrackrScalar = function(x, y) { 456 | this.headtrackrScalarX = x === undefined ? this.headtrackrScalarX : x; 457 | this.headtrackrScalarY = y === undefined ? this.headtrackrScalarY : y; 458 | }; 459 | 460 | Plugin.prototype.limit = function(x, y) { 461 | this.limitX = x === undefined ? this.limitX : x; 462 | this.limitY = y === undefined ? this.limitY : y; 463 | }; 464 | 465 | Plugin.prototype.clamp = function(value, min, max) { 466 | value = Math.max(value, min); 467 | value = Math.min(value, max); 468 | return value; 469 | }; 470 | 471 | Plugin.prototype.css = function(element, property, value) { 472 | var jsProperty = null; 473 | for (var i = 0, l = this.vendors.length; i < l; i++) { 474 | if (this.vendors[i] !== null) { 475 | jsProperty = $.camelCase(this.vendors[i][1] + '-' + property); 476 | } else { 477 | jsProperty = property; 478 | } 479 | if (element.style[jsProperty] !== undefined) { 480 | element.style[jsProperty] = value; 481 | break; 482 | } 483 | } 484 | }; 485 | 486 | Plugin.prototype.accelerate = function($element) { 487 | for (var i = 0, l = $element.length; i < l; i++) { 488 | var element = $element[i]; 489 | this.css(element, 'transform', 'translate3d(0,0,0)'); 490 | this.css(element, 'transform-style', 'preserve-3d'); 491 | this.css(element, 'backface-visibility', 'hidden'); 492 | } 493 | }; 494 | 495 | Plugin.prototype.setPosition = function(element, x, y) { 496 | x += '%'; 497 | y += '%'; 498 | if (this.transform3DSupport) { 499 | this.css(element, 'transform', 'translate3d('+x+','+y+',0)'); 500 | } else if (this.transform2DSupport) { 501 | this.css(element, 'transform', 'translate('+x+','+y+')'); 502 | } else { 503 | element.style.left = x; 504 | element.style.top = y; 505 | } 506 | }; 507 | 508 | Plugin.prototype.onOrientationTimer = function(event) { 509 | if (this.orientationSupport && this.orientationStatus === 0) { 510 | this.disable(); 511 | this.orientationSupport = false; 512 | //only at this point we are sure there is no orientationSupport (can't rely on !!window.DeviceOrientationEvent, beacause we may be on a desktop that may expose the API but doesn't have any accelerometer) 513 | //so, we launch the headtrackr in fallback to deviceMotion as asked in the options as headtrackrPreferDeviceMotion = true 514 | if(this.headtrackr === true && this.getUserMediaSupport === true && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 515 | this.headtrackrPrepare(); 516 | this.headtrackrEnabled = true; 517 | } 518 | //in case there is no getUserMedia support but it was asked to use headtrackr 519 | else if(this.headtrackr === true && this.getUserMediaSupport === false && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 520 | this.headtrackrFail(); 521 | } 522 | this.enable(); 523 | } 524 | }; 525 | 526 | Plugin.prototype.onCalibrationTimer = function(event) { 527 | this.calibrationFlag = true; 528 | }; 529 | 530 | Plugin.prototype.onWindowResize = function(event) { 531 | this.updateDimensions(); 532 | }; 533 | 534 | Plugin.prototype.onAnimationFrame = function() { 535 | var dx = this.ix - this.cx; 536 | var dy = this.iy - this.cy; 537 | if ((Math.abs(dx) > this.calibrationThreshold) || (Math.abs(dy) > this.calibrationThreshold)) { 538 | this.queueCalibration(0); 539 | } 540 | if (this.portrait) { 541 | this.mx = (this.calibrateX ? dy : this.iy) * this.scalarX; 542 | this.my = (this.calibrateY ? dx : this.ix) * this.scalarY; 543 | } else { 544 | this.mx = (this.calibrateX ? dx : this.ix) * this.scalarX; 545 | this.my = (this.calibrateY ? dy : this.iy) * this.scalarY; 546 | } 547 | if (!isNaN(parseFloat(this.limitX))) { 548 | this.mx = this.clamp(this.mx, -this.limitX, this.limitX); 549 | } 550 | if (!isNaN(parseFloat(this.limitY))) { 551 | this.my = this.clamp(this.my, -this.limitY, this.limitY); 552 | } 553 | this.vx += (this.mx - this.vx) * this.frictionX; 554 | this.vy += (this.my - this.vy) * this.frictionY; 555 | for (var i = 0, l = this.$layers.length; i < l; i++) { 556 | var depth = this.depths[i]; 557 | var layer = this.$layers[i]; 558 | var xOffset = this.vx * depth * (this.invertX ? -1 : 1); 559 | var yOffset = this.vy * depth * (this.invertY ? -1 : 1); 560 | this.setPosition(layer, xOffset, yOffset); 561 | } 562 | this.raf = requestAnimationFrame(this.onAnimationFrame); 563 | }; 564 | 565 | Plugin.prototype.onDeviceOrientation = function(event) { 566 | 567 | // Validate environment and event properties. 568 | if (!this.desktop && event.beta !== null && event.gamma !== null) { 569 | 570 | // Set orientation status. 571 | this.orientationStatus = 1; 572 | 573 | // Extract Rotation 574 | var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 575 | var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 576 | 577 | // Detect Orientation Change 578 | var portrait = window.innerHeight > window.innerWidth; 579 | if (this.portrait !== portrait) { 580 | this.portrait = portrait; 581 | this.calibrationFlag = true; 582 | } 583 | 584 | // Set Calibration 585 | if (this.calibrationFlag) { 586 | this.calibrationFlag = false; 587 | this.cx = x; 588 | this.cy = y; 589 | } 590 | 591 | // Set Input 592 | this.ix = x; 593 | this.iy = y; 594 | } 595 | }; 596 | 597 | Plugin.prototype.onMouseMove = function(event) { 598 | 599 | // Calculate Input 600 | this.ix = (event.pageX - this.hw) / this.hw; 601 | this.iy = (event.pageY - this.hh) / this.hh; 602 | }; 603 | 604 | Plugin.prototype.onFaceTracking = function(event) { 605 | 606 | // Calculate Input 607 | if(event.detection === "CS"){ 608 | this.ix = -this.headtrackrScalarX*(event.x - this.htrackr.canvasInputInfos.hw) / this.htrackr.canvasInputInfos.hw; 609 | this.iy = this.headtrackrScalarY*(event.y - this.htrackr.canvasInputInfos.hh) / this.htrackr.canvasInputInfos.hh; 610 | if(this.headtrackrDebugView === true){ 611 | this.htrackr.canvasDebug.width = this.htrackr.canvasDebug.width; 612 | this.htrackr.ctxDebug.translate(event.x, event.y); 613 | this.htrackr.ctxDebug.rotate(event.angle-(Math.PI/2)); 614 | this.htrackr.ctxDebug.strokeStyle = "#00CC00"; 615 | this.htrackr.ctxDebug.strokeRect((-(event.width/2)) >> 0, (-(event.height/2)) >> 0, event.width, event.height); 616 | this.htrackr.ctxDebug.rotate((Math.PI/2)-event.angle); 617 | this.htrackr.ctxDebug.translate(-event.x, -event.y); 618 | } 619 | } 620 | 621 | }; 622 | 623 | var API = { 624 | enable: Plugin.prototype.enable, 625 | disable: Plugin.prototype.disable, 626 | calibrate: Plugin.prototype.calibrate, 627 | friction: Plugin.prototype.friction, 628 | invert: Plugin.prototype.invert, 629 | scalar: Plugin.prototype.scalar, 630 | headtrackrScalar : Plugin.prototype.headtrackrScalar, 631 | limit: Plugin.prototype.limit 632 | }; 633 | 634 | $.fn[NAME] = function (value) { 635 | var args = arguments; 636 | return this.each(function () { 637 | var $this = $(this); 638 | var plugin = $this.data(NAME); 639 | if (!plugin) { 640 | plugin = new Plugin(this, value); 641 | $this.data(NAME, plugin); 642 | } 643 | if (API[value]) { 644 | plugin[value].apply(plugin, Array.prototype.slice.call(args, 1)); 645 | } 646 | }); 647 | }; 648 | 649 | })(window.jQuery || window.Zepto, window, document); 650 | -------------------------------------------------------------------------------- /source/parallax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parallax.js 3 | * @author Matthew Wagerfield - @mwagerfield 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 | var NAME = 'Parallax'; 11 | var MAGIC_NUMBER = 30; 12 | var DEFAULTS = { 13 | calibrationThreshold: 100, 14 | calibrationDelay: 500, 15 | supportDelay: 500, 16 | calibrateX: false, 17 | calibrateY: true, 18 | invertX: true, 19 | invertY: true, 20 | limitX: false, 21 | limitY: false, 22 | scalarX: 10.0, 23 | scalarY: 10.0, 24 | frictionX: 0.1, 25 | frictionY: 0.1, 26 | headtrackr: false, 27 | headtrackrPreferDeviceMotion: true, 28 | headtrackrDisplayVideo: false, 29 | headtrackrDebugView: false, 30 | headtrackrScalarX: 3, 31 | headtrackrScalarY: 3, 32 | headtrackrScriptLocation: null 33 | }; 34 | 35 | function Parallax(element, options) { 36 | 37 | // DOM Context 38 | this.element = element; 39 | this.layers = element.getElementsByClassName('layer'); 40 | 41 | // Data Extraction 42 | var data = { 43 | calibrateX: this.data(this.element, 'calibrate-x'), 44 | calibrateY: this.data(this.element, 'calibrate-y'), 45 | invertX: this.data(this.element, 'invert-x'), 46 | invertY: this.data(this.element, 'invert-y'), 47 | limitX: this.data(this.element, 'limit-x'), 48 | limitY: this.data(this.element, 'limit-y'), 49 | scalarX: this.data(this.element, 'scalar-x'), 50 | scalarY: this.data(this.element, 'scalar-y'), 51 | frictionX: this.data(this.element, 'friction-x'), 52 | frictionY: this.data(this.element, 'friction-y'), 53 | headtrackr: this.data(this.element, 'headtrackr'), 54 | headtrackrPreferDeviceMotion: this.data(this.element, 'headtrackr-prefer-device-motion'), 55 | headtrackrDisplayVideo: this.data(this.element, 'headtrackr-display-video'), 56 | headtrackrDebugView: this.data(this.element, 'headtrackr-debug-view'), 57 | headtrackrScalarX: this.data(this.element, 'headtrackr-scalar-x'), 58 | headtrackrScalarY: this.data(this.element, 'headtrackr-scalar-y'), 59 | headtrackrScriptLocation: this.data(this.element, 'headtrackr-script-location') 60 | }; 61 | 62 | // Delete Null Data Values 63 | for (var key in data) { 64 | if (data[key] === null) delete data[key]; 65 | } 66 | 67 | // Compose Settings Object 68 | this.extend(this, DEFAULTS, options, data); 69 | 70 | // States 71 | this.calibrationTimer = null; 72 | this.calibrationFlag = true; 73 | this.enabled = false; 74 | this.depths = []; 75 | this.raf = null; 76 | 77 | // Offset 78 | this.ox = 0; 79 | this.oy = 0; 80 | this.ow = 0; 81 | this.oh = 0; 82 | 83 | // Calibration 84 | this.cx = 0; 85 | this.cy = 0; 86 | 87 | // Input 88 | this.ix = 0; 89 | this.iy = 0; 90 | 91 | // Motion 92 | this.mx = 0; 93 | this.my = 0; 94 | 95 | // Velocity 96 | this.vx = 0; 97 | this.vy = 0; 98 | 99 | // Callbacks 100 | this.onFaceTracking = this.onFaceTracking.bind(this); 101 | this.onMouseMove = this.onMouseMove.bind(this); 102 | this.onDeviceOrientation = this.onDeviceOrientation.bind(this); 103 | this.onOrientationTimer = this.onOrientationTimer.bind(this); 104 | this.onCalibrationTimer = this.onCalibrationTimer.bind(this); 105 | this.onAnimationFrame = this.onAnimationFrame.bind(this); 106 | this.onWindowResize = this.onWindowResize.bind(this); 107 | 108 | // Initialise 109 | this.initialise(); 110 | } 111 | 112 | Parallax.prototype.extend = function() { 113 | if (arguments.length > 1) { 114 | var master = arguments[0]; 115 | for (var i = 1, l = arguments.length; i < l; i++) { 116 | var object = arguments[i]; 117 | for (var key in object) { 118 | master[key] = object[key]; 119 | } 120 | } 121 | } 122 | }; 123 | 124 | Parallax.prototype.data = function(element, name) { 125 | return this.deserialize(element.getAttribute('data-'+name)); 126 | }; 127 | 128 | Parallax.prototype.deserialize = function(value) { 129 | if (value === "true") { 130 | return true; 131 | } else if (value === "false") { 132 | return false; 133 | } else if (value === "null") { 134 | return null; 135 | } else if (!isNaN(parseFloat(value)) && isFinite(value)) { 136 | return parseFloat(value); 137 | } else { 138 | return value; 139 | } 140 | }; 141 | 142 | Parallax.prototype.offset = function(element) { 143 | var x = 0, y = 0; 144 | while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { 145 | x += element.offsetLeft - element.scrollLeft; 146 | y += element.offsetTop - element.scrollTop; 147 | element = element.offsetParent; 148 | } 149 | return {top:y, left:x}; 150 | }; 151 | 152 | Parallax.prototype.camelCase = function(value) { 153 | return value.replace(/-+(.)?/g, function(match, character){ 154 | return character ? character.toUpperCase() : ''; 155 | }); 156 | }; 157 | 158 | Parallax.prototype.transformSupport = function(value) { 159 | var element = document.createElement('div'); 160 | var propertySupport = false; 161 | var propertyValue = null; 162 | var featureSupport = false; 163 | var cssProperty = null; 164 | var jsProperty = null; 165 | for (var i = 0, l = this.vendors.length; i < l; i++) { 166 | if (this.vendors[i] !== null) { 167 | cssProperty = this.vendors[i][0] + 'transform'; 168 | jsProperty = this.vendors[i][1] + 'Transform'; 169 | } else { 170 | cssProperty = 'transform'; 171 | jsProperty = 'transform'; 172 | } 173 | if (element.style[jsProperty] !== undefined) { 174 | propertySupport = true; 175 | break; 176 | } 177 | } 178 | switch(value) { 179 | case '2D': 180 | featureSupport = propertySupport; 181 | break; 182 | case '3D': 183 | if (propertySupport) { 184 | document.body.appendChild(element); 185 | element.style[jsProperty] = 'translate3d(1px,1px,1px)'; 186 | propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty); 187 | featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== "none"; 188 | document.body.removeChild(element); 189 | } 190 | break; 191 | } 192 | return featureSupport; 193 | }; 194 | 195 | Parallax.prototype.ww = null; 196 | Parallax.prototype.wh = null; 197 | Parallax.prototype.hw = null; 198 | Parallax.prototype.hh = null; 199 | Parallax.prototype.portrait = null; 200 | Parallax.prototype.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i); 201 | Parallax.prototype.vendors = [null,['-webkit-','webkit'],['-moz-','Moz'],['-o-','O'],['-ms-','ms']]; 202 | Parallax.prototype.motionSupport = !!window.DeviceMotionEvent; 203 | Parallax.prototype.orientationSupport = !!window.DeviceOrientationEvent; 204 | Parallax.prototype.getUserMediaSupport = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); 205 | Parallax.prototype.headtrackrEnabled = false; //will turn to true if getUserMediaSupport and no DeviceMotion (or headtrackrPreferDeviceMotion = false) 206 | Parallax.prototype.orientationStatus = 0; 207 | Parallax.prototype.transform2DSupport = Parallax.prototype.transformSupport('2D'); 208 | Parallax.prototype.transform3DSupport = Parallax.prototype.transformSupport('3D'); 209 | 210 | /** 211 | * Method to be called when the headtrackr feature was asked for, there is getUserMedia support and no DeviceMotion or headtrackrPreferDeviceMotion = false 212 | */ 213 | Parallax.prototype.headtrackrPrepare = function(){ 214 | if(typeof headtrackr === "undefined"){ 215 | if(this.headtrackrScriptLocation !== null){ 216 | var headTrackrScript = document.createElement('script'), 217 | self = this; 218 | headTrackrScript.onload = function(script){ 219 | if(typeof headtrackr !== "undefined"){ 220 | self.headtrackrPrepare(); 221 | } 222 | else{ 223 | throw new Error("Wrong path to headtrackr script"); 224 | } 225 | }; 226 | headTrackrScript.src = this.headtrackrScriptLocation; 227 | document.getElementsByTagName('body')[0].appendChild(headTrackrScript); 228 | return false; 229 | } 230 | else{ 231 | throw new Error("To use the headtrackr feature, you need to include the headtrakr.js or headtrackr.min.js script before the parallax one, or set its location in the parallax option headtrackrScriptLocation"); 232 | } 233 | } 234 | 235 | var inputVideo = document.createElement('video'), 236 | canvasInput = document.createElement('canvas'), 237 | canvasDebug = null, 238 | videoWidth = "320", 239 | videoHeight = "240", 240 | headtrackrOptions = {}, 241 | self; 242 | 243 | //add the mousemove listener while connecting the camera 244 | //we'll remove it when the face is detected to plug trackr 245 | //then readd it when the trackr fails 246 | window.addEventListener('mousemove', this.onMouseMove); 247 | 248 | inputVideo.autoplay = true; 249 | inputVideo.loop = true; 250 | inputVideo.style.display = "none"; 251 | inputVideo.width = videoWidth; 252 | inputVideo.height = videoHeight; 253 | 254 | canvasInput.id = "headtrackr-display-video"; 255 | canvasInput.style.position = "fixed"; 256 | canvasInput.style.bottom = "0px"; 257 | canvasInput.style.right = "0px"; 258 | canvasInput.width = videoWidth; 259 | canvasInput.height = videoHeight; 260 | 261 | if(this.headtrackrDisplayVideo === true || this.headtrackrDebugView === true){ 262 | canvasInput.style.display = "block"; 263 | if(this.headtrackrDebugView === true){ 264 | canvasDebug = document.createElement('canvas'); 265 | canvasDebug.id = "headtrackr-debug-view"; 266 | canvasDebug.style.display = "block"; 267 | canvasDebug.style.position = "fixed"; 268 | canvasDebug.style.bottom = "0px"; 269 | canvasDebug.style.right = "0px"; 270 | canvasDebug.width = videoWidth; 271 | canvasDebug.height = videoHeight; 272 | headtrackrOptions.calcAngles = true; 273 | } 274 | } 275 | else{ 276 | this.headtrackrDebugView = false; 277 | canvasInput.style.display = "none"; 278 | } 279 | 280 | this.htrackr = new headtrackr.Tracker(headtrackrOptions); 281 | 282 | document.getElementsByTagName('body')[0].appendChild(inputVideo); 283 | document.getElementsByTagName('body')[0].appendChild(canvasInput); 284 | if(canvasDebug !== null){ 285 | document.getElementsByTagName('body')[0].appendChild(canvasDebug); 286 | this.htrackr.canvasDebug = canvasDebug; 287 | this.htrackr.ctxDebug = canvasDebug.getContext('2d'); 288 | } 289 | 290 | //callback to be used to display a "please allow your webcam" for example 291 | if(typeof(this.headtrackrOnBeforeCameraAccess) === "function"){ 292 | this.headtrackrOnBeforeCameraAccess(); 293 | } 294 | 295 | this.htrackr.init(inputVideo, canvasInput); 296 | this.htrackr.start(); 297 | this.htrackr.canvasInputInfos = { 298 | ww : canvasInput.width, 299 | wh : canvasInput.height, 300 | hw : canvasInput.width / 2, 301 | hh : canvasInput.height / 2 302 | }; 303 | 304 | self = this; 305 | document.addEventListener('headtrackrStatus', function(e){ 306 | console.log(e.status,e.type,e.timeStamp); 307 | if(e.status === "camera found"){ 308 | if(typeof(self.headtrackrOnCameraFound) === "function"){ 309 | self.headtrackrOnCameraFound(); 310 | } 311 | } 312 | else if(e.status === "found"){ 313 | window.removeEventListener('mousemove', self.onMouseMove); 314 | document.addEventListener("facetrackingEvent", self.onFaceTracking, false); 315 | } 316 | else if(e.status === "redetecting"){ 317 | window.addEventListener('mousemove', self.onMouseMove); 318 | document.removeEventListener("facetrackingEvent", self.onFaceTracking, false); 319 | } 320 | }); 321 | }; 322 | 323 | /** 324 | * Method to be called when the headtrackr was to be initiated but there was no getUserMediaSupport for 325 | */ 326 | Parallax.prototype.headtrackrFail = function(){ 327 | //if no callback is set, set the default callback 328 | if(typeof(this.headtrackrNoGetUserMediaCallback) !== "function"){ 329 | this.headtrackrNoGetUserMediaCallback = function(){ 330 | var message = "Sorry no getUserMedia support on your browser.

To test facetracking,

please use
Chrome or Firefox"; 331 | console.log(message.replace(/
/g,' ').replace(/(<([^>]+)>)/ig,'')); 332 | 333 | var timeout = 10000; 334 | // create element and attach to body 335 | var d = document.createElement('div'), 336 | d2 = document.createElement('div'), 337 | p = document.createElement('p'); 338 | d.setAttribute('id', 'headtrackerMessageDiv'); 339 | 340 | d.style.left = "10%"; 341 | d.style.right = "10%"; 342 | d.style.top = "10%"; 343 | d.style.fontSize = "150%"; 344 | d.style.color = "#777"; 345 | d.style.position = "absolute"; 346 | d.style.fontFamily = "Helvetica, Arial, sans-serif"; 347 | d.style.zIndex = '100002'; 348 | 349 | d2.style.marginLeft = "auto"; 350 | d2.style.marginRight = "auto"; 351 | d2.style.width = "100%"; 352 | d2.style.textAlign = "center"; 353 | d2.style.color = "#fff"; 354 | d2.style.backgroundColor = "#444"; 355 | d2.style.opacity = "0.8"; 356 | 357 | p.setAttribute('id', 'parallaxHeadtrackerNoGetUserMediaMessage'); 358 | p.innerHTML = message; 359 | d2.appendChild(p); 360 | d.appendChild(d2); 361 | document.body.appendChild(d); 362 | 363 | setTimeout(function(){ 364 | d.parentNode.removeChild(d); 365 | },timeout); 366 | 367 | }; 368 | } 369 | this.headtrackrNoGetUserMediaCallback(); 370 | //back to normal behaviour 371 | this.headtrackr = false; 372 | }; 373 | 374 | Parallax.prototype.initialise = function() { 375 | 376 | // Configure Context Styles 377 | if (this.transform3DSupport) this.accelerate(this.element); 378 | var style = window.getComputedStyle(this.element); 379 | if (style.getPropertyValue('position') === 'static') { 380 | this.element.style.position = 'relative'; 381 | } 382 | 383 | // Configure Layer Styles 384 | for (var i = 0, l = this.layers.length; i < l; i++) { 385 | var layer = this.layers[i]; 386 | if (this.transform3DSupport) this.accelerate(layer); 387 | layer.style.position = i ? 'absolute' : 'relative'; 388 | layer.style.display = 'block'; 389 | layer.style.height = '100%'; 390 | layer.style.width = '100%'; 391 | layer.style.left = 0; 392 | layer.style.top = 0; 393 | 394 | // Cache Layer Depth 395 | this.depths.push(this.data(layer, 'depth') || 0); 396 | } 397 | 398 | // Setup 399 | 400 | //if headtrackr was asked, and can be supported (via getUserMedia), no matter if there is DeviceMotion on the device, enable it 401 | //if headtracker was asked but with headtrackrPreferDeviceMotion === true, the headtrackrPrepare() is made in the onOrientationTimer (where we make sure there is - or not - DeviceMotion support) 402 | if(this.headtrackr === true && this.getUserMediaSupport === true && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 403 | this.headtrackrPrepare(); 404 | this.headtrackrEnabled = true; 405 | } 406 | else if(this.headtrackr === true && this.getUserMediaSupport === false && (this.headtrackrPreferDeviceMotion === false || this.orientationSupport === false) && this.headtrackrEnabled === false){ 407 | this.headtrackrFail(); 408 | } 409 | this.updateDimensions(); 410 | this.enable(); 411 | this.queueCalibration(this.calibrationDelay); 412 | }; 413 | 414 | Parallax.prototype.updateDimensions = function() { 415 | 416 | // Cache Context Dimensions 417 | this.ox = this.offset(this.element).left; 418 | this.oy = this.offset(this.element).top; 419 | this.ow = this.element.offsetWidth; 420 | this.oh = this.element.offsetHeight; 421 | 422 | // Cache Window Dimensions 423 | this.ww = window.innerWidth; 424 | this.wh = window.innerHeight; 425 | this.hw = this.ww / 2; 426 | this.hh = this.wh / 2; 427 | }; 428 | 429 | Parallax.prototype.queueCalibration = function(delay) { 430 | clearTimeout(this.calibrationTimer); 431 | this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay); 432 | }; 433 | 434 | Parallax.prototype.enable = function() { 435 | if (!this.enabled) { 436 | this.enabled = true; 437 | if (this.headtrackrEnabled === false && this.orientationSupport) { 438 | this.portrait = null; 439 | window.addEventListener('deviceorientation', this.onDeviceOrientation); 440 | setTimeout(this.onOrientationTimer, this.supportDelay); 441 | } 442 | else if (this.headtrackrEnabled === false) { 443 | this.cx = 0; 444 | this.cy = 0; 445 | this.portrait = false; 446 | window.addEventListener('mousemove', this.onMouseMove); 447 | } 448 | else { 449 | document.addEventListener("facetrackingEvent", this.onFaceTracking, false); 450 | } 451 | window.addEventListener('resize', this.onWindowResize); 452 | this.raf = requestAnimationFrame(this.onAnimationFrame); 453 | } 454 | }; 455 | 456 | Parallax.prototype.disable = function() { 457 | if (this.enabled) { 458 | this.enabled = false; 459 | if (this.headtrackrEnabled === false && this.orientationSupport) { 460 | window.removeEventListener('deviceorientation', this.onDeviceOrientation); 461 | } 462 | else if (this.headtrackrEnabled === false) { 463 | window.removeEventListener('mousemove', this.onMouseMove); 464 | } 465 | else { 466 | document.removeEventListener("facetrackingEvent", this.onFaceTracking, false); 467 | } 468 | window.removeEventListener('resize', this.onWindowResize); 469 | cancelAnimationFrame(this.raf); 470 | } 471 | }; 472 | 473 | Parallax.prototype.calibrate = function(x, y) { 474 | this.calibrateX = x === undefined ? this.calibrateX : x; 475 | this.calibrateY = y === undefined ? this.calibrateY : y; 476 | }; 477 | 478 | Parallax.prototype.invert = function(x, y) { 479 | this.invertX = x === undefined ? this.invertX : x; 480 | this.invertY = y === undefined ? this.invertY : y; 481 | }; 482 | 483 | Parallax.prototype.friction = function(x, y) { 484 | this.frictionX = x === undefined ? this.frictionX : x; 485 | this.frictionY = y === undefined ? this.frictionY : y; 486 | }; 487 | 488 | Parallax.prototype.scalar = function(x, y) { 489 | this.scalarX = x === undefined ? this.scalarX : x; 490 | this.scalarY = y === undefined ? this.scalarY : y; 491 | }; 492 | 493 | Parallax.prototype.headtrackrScalar = function(x, y) { 494 | this.headtrackrScalarX = x === undefined ? this.headtrackrScalarX : x; 495 | this.headtrackrScalarY = y === undefined ? this.headtrackrScalarY : y; 496 | }; 497 | 498 | Parallax.prototype.limit = function(x, y) { 499 | this.limitX = x === undefined ? this.limitX : x; 500 | this.limitY = y === undefined ? this.limitY : y; 501 | }; 502 | 503 | Parallax.prototype.clamp = function(value, min, max) { 504 | value = Math.max(value, min); 505 | value = Math.min(value, max); 506 | return value; 507 | }; 508 | 509 | Parallax.prototype.css = function(element, property, value) { 510 | var jsProperty = null; 511 | for (var i = 0, l = this.vendors.length; i < l; i++) { 512 | if (this.vendors[i] !== null) { 513 | jsProperty = this.camelCase(this.vendors[i][1] + '-' + property); 514 | } else { 515 | jsProperty = property; 516 | } 517 | if (element.style[jsProperty] !== undefined) { 518 | element.style[jsProperty] = value; 519 | break; 520 | } 521 | } 522 | }; 523 | 524 | Parallax.prototype.accelerate = function(element) { 525 | this.css(element, 'transform', 'translate3d(0,0,0)'); 526 | this.css(element, 'transform-style', 'preserve-3d'); 527 | this.css(element, 'backface-visibility', 'hidden'); 528 | }; 529 | 530 | Parallax.prototype.setPosition = function(element, x, y) { 531 | x += '%'; 532 | y += '%'; 533 | if (this.transform3DSupport) { 534 | this.css(element, 'transform', 'translate3d('+x+','+y+',0)'); 535 | } else if (this.transform2DSupport) { 536 | this.css(element, 'transform', 'translate('+x+','+y+')'); 537 | } else { 538 | element.style.left = x; 539 | element.style.top = y; 540 | } 541 | }; 542 | 543 | Parallax.prototype.onOrientationTimer = function(event) { 544 | if (this.orientationSupport && this.orientationStatus === 0) { 545 | this.disable(); 546 | this.orientationSupport = false; 547 | //only at this point we are sure there is no orientationSupport (can't rely on !!window.DeviceOrientationEvent, beacause we may be on a desktop that may expose the API but doesn't have any accelerometer) 548 | //so, we launch the headtrackr in fallback to deviceMotion as asked in the options as headtrackrPreferDeviceMotion = true 549 | if(this.headtrackr === true && this.getUserMediaSupport === true && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 550 | this.headtrackrPrepare(); 551 | this.headtrackrEnabled = true; 552 | } 553 | //in case there is no getUserMedia support but it was asked to use headtrackr 554 | else if(this.headtrackr === true && this.getUserMediaSupport === false && this.headtrackrPreferDeviceMotion === true && this.headtrackrEnabled === false){ 555 | this.headtrackrFail(); 556 | } 557 | this.enable(); 558 | } 559 | }; 560 | 561 | Parallax.prototype.onCalibrationTimer = function(event) { 562 | this.calibrationFlag = true; 563 | }; 564 | 565 | Parallax.prototype.onWindowResize = function(event) { 566 | this.updateDimensions(); 567 | }; 568 | 569 | Parallax.prototype.onAnimationFrame = function() { 570 | var dx = this.ix - this.cx; 571 | var dy = this.iy - this.cy; 572 | if ((Math.abs(dx) > this.calibrationThreshold) || (Math.abs(dy) > this.calibrationThreshold)) { 573 | this.queueCalibration(0); 574 | } 575 | if (this.portrait) { 576 | this.mx = (this.calibrateX ? dy : this.iy) * this.scalarX; 577 | this.my = (this.calibrateY ? dx : this.ix) * this.scalarY; 578 | } else { 579 | this.mx = (this.calibrateX ? dx : this.ix) * this.scalarX; 580 | this.my = (this.calibrateY ? dy : this.iy) * this.scalarY; 581 | } 582 | if (!isNaN(parseFloat(this.limitX))) { 583 | this.mx = this.clamp(this.mx, -this.limitX, this.limitX); 584 | } 585 | if (!isNaN(parseFloat(this.limitY))) { 586 | this.my = this.clamp(this.my, -this.limitY, this.limitY); 587 | } 588 | this.vx += (this.mx - this.vx) * this.frictionX; 589 | this.vy += (this.my - this.vy) * this.frictionY; 590 | for (var i = 0, l = this.layers.length; i < l; i++) { 591 | var layer = this.layers[i]; 592 | var depth = this.depths[i]; 593 | var xOffset = this.vx * depth * (this.invertX ? -1 : 1); 594 | var yOffset = this.vy * depth * (this.invertY ? -1 : 1); 595 | this.setPosition(layer, xOffset, yOffset); 596 | } 597 | this.raf = requestAnimationFrame(this.onAnimationFrame); 598 | }; 599 | 600 | Parallax.prototype.onDeviceOrientation = function(event) { 601 | 602 | // Validate environment and event properties. 603 | if (!this.desktop && event.beta !== null && event.gamma !== null) { 604 | 605 | // Set orientation status. 606 | this.orientationStatus = 1; 607 | 608 | // Extract Rotation 609 | var x = (event.beta || 0) / MAGIC_NUMBER; // -90 :: 90 610 | var y = (event.gamma || 0) / MAGIC_NUMBER; // -180 :: 180 611 | 612 | // Detect Orientation Change 613 | var portrait = this.wh > this.ww; 614 | if (this.portrait !== portrait) { 615 | this.portrait = portrait; 616 | this.calibrationFlag = true; 617 | } 618 | 619 | // Set Calibration 620 | if (this.calibrationFlag) { 621 | this.calibrationFlag = false; 622 | this.cx = x; 623 | this.cy = y; 624 | } 625 | 626 | // Set Input 627 | this.ix = x; 628 | this.iy = y; 629 | } 630 | }; 631 | 632 | Parallax.prototype.onMouseMove = function(event) { 633 | 634 | // Calculate Input 635 | this.ix = (event.pageX - this.hw) / this.hw; 636 | this.iy = (event.pageY - this.hh) / this.hh; 637 | }; 638 | 639 | Parallax.prototype.onFaceTracking = function(event) { 640 | 641 | // Calculate Input 642 | if(event.detection === "CS"){ 643 | this.ix = -this.headtrackrScalarX*(event.x - this.htrackr.canvasInputInfos.hw) / this.htrackr.canvasInputInfos.hw; 644 | this.iy = this.headtrackrScalarY*(event.y - this.htrackr.canvasInputInfos.hh) / this.htrackr.canvasInputInfos.hh; 645 | if(this.headtrackrDebugView === true){ 646 | this.htrackr.canvasDebug.width = this.htrackr.canvasDebug.width; 647 | this.htrackr.ctxDebug.translate(event.x, event.y); 648 | this.htrackr.ctxDebug.rotate(event.angle-(Math.PI/2)); 649 | this.htrackr.ctxDebug.strokeStyle = "#00CC00"; 650 | this.htrackr.ctxDebug.strokeRect((-(event.width/2)) >> 0, (-(event.height/2)) >> 0, event.width, event.height); 651 | this.htrackr.ctxDebug.rotate((Math.PI/2)-event.angle); 652 | this.htrackr.ctxDebug.translate(-event.x, -event.y); 653 | } 654 | } 655 | 656 | }; 657 | 658 | // Expose Parallax 659 | window[NAME] = Parallax; 660 | 661 | })(window, document); 662 | -------------------------------------------------------------------------------- /source/requestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Request Animation Frame Polyfill. 3 | * @author Tino Zijdel 4 | * @author Paul Irish 5 | * @see https://gist.github.com/paulirish/1579671 6 | */ 7 | ;(function() { 8 | 9 | var lastTime = 0; 10 | var vendors = ['ms', 'moz', 'webkit', 'o']; 11 | 12 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 13 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 14 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 15 | } 16 | 17 | if (!window.requestAnimationFrame) { 18 | window.requestAnimationFrame = function(callback, element) { 19 | var currTime = new Date().getTime(); 20 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 21 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 22 | timeToCall); 23 | lastTime = currTime + timeToCall; 24 | return id; 25 | }; 26 | } 27 | 28 | if (!window.cancelAnimationFrame) { 29 | window.cancelAnimationFrame = function(id) { 30 | clearTimeout(id); 31 | }; 32 | } 33 | 34 | }()); 35 | --------------------------------------------------------------------------------