├── .gitignore ├── .npmignore ├── .nvmrc ├── LICENSE ├── README.md ├── examples ├── assets │ ├── images │ │ ├── layer1.png │ │ ├── layer2.png │ │ ├── layer3.png │ │ ├── layer4.png │ │ ├── layer5.png │ │ └── layer6.png │ └── styles.scss └── pages │ ├── callback.html │ ├── destroy.html │ ├── hoveronly.html │ ├── input_element.html │ ├── interactive.html │ ├── relative.html │ ├── selector.html │ ├── separate_axis_data.html │ └── simple.html ├── gulpfile.js ├── logo.png ├── package-lock.json ├── package.json └── src └── parallax.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules 3 | dist 4 | **/*.css 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | logo.png 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | // 3 | // The MIT License 4 | // 5 | // Copyright (C) 2014 Matthew Wagerfield - @wagerfield 6 | // 7 | // Permission is hereby granted, free of charge, to any 8 | // person obtaining a copy of this software and associated 9 | // documentation files (the "Software"), to deal in the 10 | // Software without restriction, including without limitation 11 | // the rights to use, copy, modify, merge, publish, distribute, 12 | // sublicense, and/or sell copies of the Software, and to 13 | // permit persons to whom the Software is furnished to do 14 | // so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice 17 | // shall be included in all copies or substantial portions 18 | // of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 21 | // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 22 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 24 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 25 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26 | // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 28 | // OR OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | //============================================================ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Parallax.js](logo.png) 2 | 3 | [![CDNJS](https://img.shields.io/cdnjs/v/parallax.svg)](https://cdnjs.com/libraries/parallax) 4 | 5 | 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. 6 | 7 | Check out the **[demo](https://matthew.wagerfield.com/parallax/)** to see it in action! 8 | 9 | # Table of Contents 10 | 11 | - [1. Getting started](#1-getting-started) 12 | - [1.1 Installation](#11-installation) 13 | - [1.2 Preparations](#12-preparations) 14 | - [1.3 Run Parallax](#13-run-parallax) 15 | - [2. Configuration](#2-configuration) 16 | - [2.1 Programmatic vs Declarative](#21-programmatic-vs-declarative) 17 | - [2.2 Configuration Options](#22-configuration-options) 18 | - [3. Methods](#3-methods) 19 | - [4. Development](#4-development) 20 | - [4.1 Running the Project](#41-running-the-project) 21 | - [4.2 Opening an Issue](#42-opening-an-issue) 22 | - [4.3 Known Issues](#43-known-issues) 23 | - [5. FAQ](#5-faq) 24 | - [6. Information](#6-information) 25 | - [6.1 License](#61-license) 26 | - [6.2 Contributors](#62-authors) 27 | 28 | # 1. Getting started 29 | 30 | ## 1.1 Installation 31 | 32 | ### 1.1 a) Using the CDN 33 | 34 | 1. Add `` to your markup 35 | 2. Done! 36 | 37 | Many thanks to the fine folks over at [cdnjs](https://cdnjs.com/) for hosting our library. 38 | 39 | ### 1.1 b) Beginners 40 | 41 | 1. Head over to the [releases](https://github.com/wagerfield/parallax/releases) Section 42 | 2. Download `compiled.zip` from the latest release 43 | 3. Extract the ZIP archive and locate the `parallax.js` and `parallax.min.js` files 44 | - Use `parallax.js` if you want to snoop around in the code 45 | - Use `parallax.min.js` for deployment, because it has a smaller file size 46 | 4. Copy the file of your choice into your project directory 47 | 5. So far, so good! 48 | 49 | ### 1.1 c) Professionals 50 | 51 | `npm i -s parallax-js` 52 | 53 | You will then find the source code in `node_modules/parallax-js/src/parallax.js` and the browserified, babelified, uglified, production-ready version in `node_modules/parallax-js/dist/parallax.min.js` 54 | 55 | ## 1.2 Preparations 56 | 57 | ### Include the Script 58 | 59 | If you use the compiled version, either downloaded from the releases page, or copied from the `dist` folder, include the script like any other Javascript library: 60 | `` 61 | 62 | Of course, when you've installed via npm, and use browserify/babel, you can also simply do: 63 | `import Parallax from 'parallax-js'` or 64 | `const Parallax = require('parallax-js')` 65 | 66 | ### Create your HTML elements 67 | 68 | Each Parallax.js instance needs a container element, the scene. You're free to identify it by any means you want, but for now, let's use an ID: 69 | 70 | ```html 71 |
72 |
73 | ``` 74 | 75 | Per default, all direct child elements of the scene will become moving objects, the layers. You can change this to a custom query selector, but again, we're going with the easiest approach for now: 76 | 77 | ```html 78 |
79 |
My first Layer!
80 |
My second Layer!
81 |
82 | ``` 83 | 84 | While all other options and parameters are optional, with sane defaults, and can be set programatically, each layer needs a `data-depth` attribute. The movement applied to each layer will be multiplied by its depth attribute. 85 | 86 | ```html 87 |
88 |
My first Layer!
89 |
My second Layer!
90 |
91 | ``` 92 | 93 | ## 1.3 Run Parallax 94 | 95 | As soon as your DOM is ready and loaded, you can create a new Parallax.js instance, providing your scene element as first parameter. 96 | 97 | ```javascript 98 | var scene = document.getElementById('scene'); 99 | var parallaxInstance = new Parallax(scene); 100 | ``` 101 | 102 | That's it, you're running Parallax.js now! 103 | 104 | # 2. Configuration 105 | 106 | ## 2.1 Programmatic vs Declarative 107 | 108 | Most configuration settings can be declared either as data-value attribute of the scene element, or property of the configuration object. The programmatic approach will take priority over the data-value attributes set in the HTML. 109 | Some options can also be set at run-time via instance methods. 110 | 111 | Declarative: 112 | 113 | ```html 114 |
115 |
My first Layer!
116 |
My second Layer!
117 |
118 | ``` 119 | 120 | Programmatic: 121 | 122 | ```javascript 123 | var scene = document.getElementById('scene'); 124 | var parallaxInstance = new Parallax(scene, { 125 | relativeInput: true 126 | }); 127 | ``` 128 | 129 | Using Methods at Runtime: 130 | 131 | ```javascript 132 | parallaxInstance.friction(0.2, 0.2); 133 | ``` 134 | 135 | ## 2.2 Configuration Options 136 | 137 | ### relativeInput 138 | 139 | Property: **relativeInput** 140 | Attribute: **data-relative-input** 141 | 142 | Value: *boolean* 143 | Default: *false* 144 | 145 | Makes mouse input relative to the position of the scene element. 146 | No effect when gyroscope is used. 147 | 148 | ### clipRelativeInput 149 | 150 | Property: **clipRelativeInput** 151 | Attribute: **data-clip-relative-input** 152 | 153 | Value: *boolean* 154 | Default: *false* 155 | 156 | Clips mouse input to the bounds of the scene. This means the movement stops as soon as the edge of the scene element is reached by the cursor. 157 | No effect when gyroscope is used, or `hoverOnly` is active. 158 | 159 | ### hoverOnly 160 | 161 | Property: **hoverOnly** 162 | Attribute: **data-hover-only** 163 | 164 | Value: *boolean* 165 | Default: *false* 166 | 167 | Parallax will only be in effect while the cursor is over the scene element, otherwise all layers move back to their initial position. Works best in combination with `relativeInput`. 168 | No effect when gyroscope is used. 169 | 170 | ### inputElement 171 | 172 | Property: **inputElement** 173 | Attribute: **data-input-element** 174 | Method: **setInputElement(HTMLElement)** 175 | 176 | Value: *null* or *HTMLElement* / *String* 177 | Default: *null* 178 | 179 | Allows usage of a different element for cursor input. 180 | The configuration property expects an HTMLElement, the data value attribute a query selector string. 181 | Will only work in combination with `relativeInput`, setting `hoverOnly` might make sense too. 182 | No effect when gyroscope is used. 183 | 184 | ### calibrateX & calibrateY 185 | 186 | Property: **calibrateX** & **calibrateY** 187 | Attribute: **data-calibrate-x** & **data-calibrate-y** 188 | Method: **calibrate(x, y)** 189 | 190 | Value: *boolean* 191 | Default: *false* for X, *true* for Y 192 | 193 | Caches the initial X/Y axis value on initialization and calculates motion relative to this. 194 | No effect when cursor is used. 195 | 196 | ### invertX & invertY 197 | 198 | Property: **invertX** & **invertY** 199 | Attribute: **data-invert-x** & **data-invert-y** 200 | Method: **invert(x, y)** 201 | 202 | Value: *boolean* 203 | Default: *true* 204 | 205 | Inverts the movement of the layers relative to the input. Setting both of these values to *false* will cause the layers to move with the device motion or cursor. 206 | 207 | ### limitX & limitY 208 | 209 | Property: **limitX** & **limitY** 210 | Attribute: **data-limit-x** & **data-limit-y** 211 | Method: **limit(x, y)** 212 | 213 | Value: *false* or *integer* 214 | Default: *false* 215 | 216 | Limits the movement of layers on the respective axis. Leaving this value at false gives complete freedom to the movement. 217 | 218 | ### scalarX & scalarY 219 | 220 | Property: **scalarX** & **scalarY** 221 | Attribute: **data-scalar-x** & **data-scalar-y** 222 | Method: **scalar(x, y)** 223 | 224 | Value: *float* 225 | Default: *10.0* 226 | 227 | Multiplies the input motion by this value, increasing or decreasing the movement speed and range. 228 | 229 | ### frictionX & frictionY 230 | 231 | Property: **frictionX** & **frictionY** 232 | Attribute: **data-friction-x** & **data-friction-y** 233 | Method: **friction(x, y)** 234 | 235 | Value: *float* between *0* and *1* 236 | Default: *0.1* 237 | 238 | Amount of friction applied to the layers. At *1* the layers will instantly go to their new positions, everything below 1 adds some easing. 239 | The default value of *0.1* adds some sensible easing. Try *0.15* or *0.075* for some difference. 240 | 241 | ### originX & originY 242 | 243 | Property: **originX** & **originY** 244 | Attribute: **data-origin-x** & **data-origin-y** 245 | Method: **origin(x, y)** 246 | 247 | Value: *float* between *0* and *1* 248 | Default: *0.5* 249 | 250 | X and Y origin of the mouse input. The default of *0.5* refers to the center of the screen or element, *0* is the left (X axis) or top (Y axis) border, 1 the right or bottom. 251 | No effect when gyroscope is used. 252 | 253 | ### precision 254 | 255 | Property: **precision** 256 | Attribute: **data-precision** 257 | 258 | Value: *integer* 259 | Default: *1* 260 | 261 | Decimals the element positions will be rounded to. *1* is a sensible default which you should not need to change in the next few years, unless you have a very interesting and unique setup. 262 | 263 | ### selector 264 | 265 | Property: **selector** 266 | Attribute: **data-selector** 267 | 268 | Value: *null* or *string* 269 | Default: *null* 270 | 271 | String that will be fed to querySelectorAll on the scene element to select the layer elements. When *null*, will simply select all direct child elements. 272 | Use `.layer` for legacy behaviour, selecting only child elements having the class name *layer*. 273 | 274 | ### pointerEvents 275 | 276 | Property: **pointerEvents** 277 | Attribute: **data-pointer-events** 278 | 279 | Value: *boolean* 280 | Default: *false* 281 | 282 | Set to *true* to enable interactions with the scene and layer elements. When set to the default of *false*, the CSS attribute `pointer-events: none` will be applied for performance reasons. 283 | Setting this to *true* alone will not be enough to fully interact with all layers, since they will be overlapping. You have to either set `position: absolute` on all layer child elements, or keep **pointerEvents** at *false* and set `pointer-events: all` for the interactable elements only. 284 | 285 | ### onReady 286 | 287 | Property: **onReady** 288 | 289 | Value: *null* or *function* 290 | Default: *null* 291 | 292 | Callback function that will be called as soon as the Parallax instance has finished its setup. This might currently take up to 1000ms (`calibrationDelay * 2`). 293 | 294 | # 3. Methods 295 | 296 | In addition to the configuration methods outlined in the section above, there are a few more publicly accessible methods: 297 | 298 | ### enable() 299 | 300 | Enables a disabled Parallax instance. 301 | 302 | ### disable() 303 | 304 | Disables a running Parallax instance. 305 | 306 | ### destroy() 307 | 308 | Completely destroys a Parallax instance, allowing it to be garbage collected. 309 | 310 | ### version() 311 | 312 | Returns the version number of the Parallax library. 313 | 314 | # 4. Development 315 | 316 | ## 4.1 Running the Project 317 | 318 | 1. Clone the Repository `git clone git@github.com:wagerfield/parallax.git` 319 | 2. Open the working directory `cd parallax` 320 | 3. Install dependencies `npm install` 321 | 4. Run development server `gulp watch` 322 | 5. Open [http://localhost:9000/](http://localhost:9000/) in browser 323 | 324 | ## 4.2 Opening an Issue 325 | 326 | If you need help relating the direct usage of this library in a project of yours, provide us with a working, running example of your work. This can be a GitHub repository, a ZIP file containing your work, a project on CodePen or JSFiddle, you name it. 327 | *Do not complain about something not working without giving us some way to help you.* Thank you! 328 | 329 | ## 4.3 Known Issues 330 | 331 | ### SVG-Bug in MS Edge 332 | 333 | It seems MS Edge does not support the `children` or `querySelectorAll` methods for SVG elements. 334 | 335 | ### Animation running really slow 336 | 337 | Depending on your site, the GPU might have a bit too much to do. You can try adding the CSS definition `will-change: transform` to the layer elements to speed things up. Use sparingly! 338 | 339 | ### Gyroscope not working on Android 340 | 341 | Android will only allow access to the gyroscope o secure origins (that is, with `https` protocol). 342 | 343 | ### Gyroscope not working on iOS 344 | 345 | Because gyroscope data had been abused to track users, it's disabled on iDevices by default and needs to be enabled by the users. You can try asking for permission via [DeviceOrientationEvent.requestPermission](https://www.w3.org/TR/orientation-event/#dom-deviceorientationevent-requestpermission). 346 | 347 | Do something like: 348 | 349 | ```js 350 | DeviceOrientationEvent 351 | .requestPermission() 352 | .then(() => { 353 | new Parallax(scene) 354 | }) 355 | ``` 356 | 357 | ### Unable to manually set position of layers 358 | 359 | Since this often lead to issues, this library forces the positioning of the layers to be absolute. If you need to override this, add `!important` to your CSS positioning. 360 | 361 | # 5. FAQ 362 | 363 | ### How can I use this Library with jQuery? 364 | 365 | jQuery will not prevent you from using this library in any way. If you want to use jQuery for selecting your Parallax scene element, you can do so too. 366 | 367 | ```javascript 368 | var scene = $('#scene').get(0); 369 | var parallaxInstance = new Parallax(scene); 370 | ``` 371 | 372 | ### How can I interact with my layers? 373 | 374 | Check out the section on the configuration option `pointerEvents` above. 375 | 376 | ### How do I get the demo files to work? 377 | 378 | Either download compiled_with_examples.zip from the [GitHub Releases](https://github.com/wagerfield/parallax/releases) section, or follow section 4.1 379 | 380 | 381 | # 6. Information 382 | 383 | ## 6.1 License 384 | 385 | This project is licensed under the terms of the [MIT](http://www.opensource.org/licenses/mit-license.php) License. Enjoy! 386 | 387 | ## 6.2 Authors 388 | 389 | Matthew Wagerfield: [@wagerfield](http://twitter.com/wagerfield) 390 | René Roth: [Website](http://reneroth.xyz/) 391 | -------------------------------------------------------------------------------- /examples/assets/images/layer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer1.png -------------------------------------------------------------------------------- /examples/assets/images/layer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer2.png -------------------------------------------------------------------------------- /examples/assets/images/layer3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer3.png -------------------------------------------------------------------------------- /examples/assets/images/layer4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer4.png -------------------------------------------------------------------------------- /examples/assets/images/layer5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer5.png -------------------------------------------------------------------------------- /examples/assets/images/layer6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/examples/assets/images/layer6.png -------------------------------------------------------------------------------- /examples/assets/styles.scss: -------------------------------------------------------------------------------- 1 | $color-green: #00ffaa; 2 | $color-background: #111; 3 | $color-text: #555; 4 | 5 | 6 | body { 7 | background: $color-background; 8 | color: $color-text; 9 | font-family: monospace; 10 | font-size: 18px; 11 | margin: 0; 12 | } 13 | 14 | img { 15 | display: block; 16 | width: 100%; 17 | } 18 | 19 | input[type=checkbox] { 20 | display: none; 21 | } 22 | 23 | label { 24 | cursor: pointer; 25 | display: inline-block; 26 | margin-right: 1em; 27 | padding: 0.4em 0; 28 | } 29 | 30 | input[type=checkbox] + label:before { 31 | background: $color-text; 32 | content: ''; 33 | display: inline-block; 34 | height: 16px; 35 | margin-right: 8px; 36 | position: relative; 37 | top: 1px; 38 | width: 16px; 39 | } 40 | 41 | input[type=checkbox]:checked + label:before { 42 | background: $color-green; 43 | } 44 | 45 | .container { 46 | margin: 0 auto; 47 | max-width: 600px; 48 | padding: 10px; 49 | position: relative; 50 | } 51 | 52 | .container--offset { 53 | margin-left: 0; 54 | } 55 | 56 | button { 57 | background: $color-text; 58 | border: 10px solid $color-green; 59 | cursor: pointer; 60 | display: block; 61 | font-family: monospace; 62 | font-size: 24px; 63 | height: 80px; 64 | line-height: 60px; 65 | margin: 0; 66 | outline: none; 67 | padding: 0 1.2em; 68 | text-align: right; 69 | 70 | &:hover { 71 | background: $color-green; 72 | } 73 | 74 | &#deleteme { 75 | margin: 2rem; 76 | } 77 | } 78 | 79 | .scene { 80 | margin: 0; 81 | padding: 0; 82 | 83 | button { 84 | left: 10%; 85 | top: 260px; 86 | width: 80%; 87 | position: absolute; 88 | } 89 | } 90 | 91 | .fill { 92 | bottom: 5%; 93 | left: 5%; 94 | position: absolute; 95 | right: 5%; 96 | top: 5%; 97 | } 98 | 99 | .expand-width { 100 | width: 100%; 101 | } 102 | 103 | .border { 104 | border: 2px dashed $color-green; 105 | } 106 | 107 | .aspect { 108 | opacity: 0.2; 109 | } 110 | 111 | @for $i from 1 through 6 { 112 | .scene > *:nth-child(#{$i}) { 113 | opacity: #{0.15 * $i}; 114 | 115 | button { 116 | transform: rotate(#{180 - 30 * $i}deg) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/pages/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Callback Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/pages/destroy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Instance Destruction Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/pages/hoveronly.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Hover Only Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/pages/input_element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Input Element Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/pages/interactive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Interactive Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/pages/relative.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Relative Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /examples/pages/selector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Selector Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
I am not a Parallax Layer
25 |
I am not a Parallax Layer
26 |
I am not a Parallax Layer
27 |
28 |
29 | 30 | 31 | 32 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/pages/separate_axis_data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Parallax.js | Separate Axis Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/pages/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 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const path = require('path') 3 | 4 | const autoprefixer = require('autoprefixer') 5 | const babelify = require('babelify') 6 | const browserify = require('browserify') 7 | const browsersync = require('browser-sync').create() 8 | const buffer = require('vinyl-buffer') 9 | const notifier = require('node-notifier') 10 | const postcss = require('gulp-postcss') 11 | const rename = require('gulp-rename'); 12 | const rimraf = require('rimraf') 13 | const sass = require('gulp-sass') 14 | const source = require('vinyl-source-stream') 15 | const sourcemaps = require('gulp-sourcemaps') 16 | const uglify = require('gulp-uglify') 17 | const util = require('gulp-util') 18 | 19 | gulp.task('clean', (cb) => { 20 | rimraf('./dist', cb) 21 | }) 22 | 23 | gulp.task('build', ['clean'], () => { 24 | gulp.start('build:js', 'build:scss') 25 | }) 26 | 27 | function showError(arg) { 28 | notifier.notify({ 29 | title: 'Error', 30 | message: '' + arg, 31 | sound: 'Basso' 32 | }) 33 | console.log(arg) 34 | this.emit('end') 35 | } 36 | 37 | gulp.task('build:scss', () => { 38 | return gulp.src(path.join('examples', 'assets', 'styles.scss')) 39 | .pipe(sass({ 40 | outputStyle: 'nested', 41 | precision: 10, 42 | includePaths: ['.', 'node_modules'], 43 | onError: showError 44 | }).on('error', function(error) { 45 | showError(error) 46 | this.emit('end') 47 | })) 48 | .pipe(postcss([ 49 | autoprefixer({ 50 | browsers: ['last 2 versions', 'Firefox ESR', 'Explorer >= 9', 'Android >= 4.0', '> 2%'] 51 | }) 52 | ])) 53 | .pipe(gulp.dest(path.join('examples', 'assets'))) 54 | .pipe(browsersync.stream({match: '**/*.css'})) 55 | }) 56 | 57 | gulp.task('build:js', () => { 58 | return browserify({entries: path.join('src', 'parallax.js'), debug: false, standalone: 'Parallax'}) 59 | .transform("babelify", {presets: ["es2015"]}) 60 | .bundle() 61 | .on('error', showError) 62 | .pipe(source('parallax.js')) 63 | .pipe(buffer()) 64 | .pipe(gulp.dest('dist')) 65 | .pipe(rename('parallax.min.js')) 66 | .pipe(sourcemaps.init({loadMaps: true})) 67 | .pipe(uglify()) 68 | .on('error', showError) 69 | .pipe(sourcemaps.write('./')) 70 | .pipe(gulp.dest('dist')) 71 | .pipe(browsersync.stream({match: path.join('**','*.js')})) 72 | }) 73 | 74 | gulp.task('watch', ['build'], () => { 75 | browsersync.init({ 76 | notify: false, 77 | port: 9000, 78 | server: { 79 | baseDir: [path.join('examples', 'pages'), path.join('examples', 'assets'), 'dist'], 80 | directory: true 81 | } 82 | }) 83 | gulp.watch(path.join('src', '*.js'), ['build:js']) 84 | gulp.watch(path.join('examples', 'assets', '*.scss'), ['build:scss']) 85 | gulp.watch(path.join('examples', 'pages', '*.html'), browsersync.reload) 86 | }) 87 | 88 | gulp.task('default', ['watch']) 89 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagerfield/parallax/04e53e94665dd55181f8d54312a9a9e63fef52ab/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallax-js", 3 | "description": "Parallax Engine that reacts to the orientation of a smart device.", 4 | "version": "3.1.0", 5 | "license": "MIT", 6 | "main": "dist/parallax.js", 7 | "homepage": "http://wagerfield.github.io/parallax/", 8 | "author": "Matthew Wagerfield , René Roth ", 9 | "directories": { 10 | "example": "examples" 11 | }, 12 | "scripts": {}, 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/wagerfield/parallax.git" 16 | }, 17 | "keywords": [ 18 | "parallax", 19 | "gyroscope", 20 | "jquery", 21 | "javascript", 22 | "library" 23 | ], 24 | "bugs": { 25 | "url": "https://github.com/wagerfield/parallax/issues" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^7.1.4", 29 | "babel-preset-es2015": "^6.24.1", 30 | "babelify": "^7.3.0", 31 | "browser-sync": "^2.18.13", 32 | "browserify": "^14.0.0", 33 | "gulp": "^3.9.1", 34 | "gulp-postcss": "^7.0.0", 35 | "gulp-rename": "^1.2.2", 36 | "gulp-sass": "^3.1.0", 37 | "gulp-sourcemaps": "^2.6.1", 38 | "gulp-uglify": "^3.0.0", 39 | "gulp-util": "^3.0.8", 40 | "node-notifier": "^5.0.2", 41 | "rimraf": "^2.5.4", 42 | "vinyl-buffer": "^1.0.0", 43 | "vinyl-source-stream": "^1.1.0" 44 | }, 45 | "dependencies": { 46 | "object-assign": "^4.1.1", 47 | "raf": "^3.3.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/parallax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parallax.js 3 | * @author Matthew Wagerfield - @wagerfield, René Roth - mail@reneroth.org 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 | 9 | const rqAnFr = require('raf') 10 | const objectAssign = require('object-assign') 11 | 12 | const helpers = { 13 | propertyCache: {}, 14 | vendors: [null, ['-webkit-','webkit'], ['-moz-','Moz'], ['-o-','O'], ['-ms-','ms']], 15 | 16 | clamp(value, min, max) { 17 | return min < max 18 | ? (value < min ? min : value > max ? max : value) 19 | : (value < max ? max : value > min ? min : value) 20 | }, 21 | 22 | data(element, name) { 23 | return helpers.deserialize(element.getAttribute('data-'+name)) 24 | }, 25 | 26 | deserialize(value) { 27 | if (value === 'true') { 28 | return true 29 | } else if (value === 'false') { 30 | return false 31 | } else if (value === 'null') { 32 | return null 33 | } else if (!isNaN(parseFloat(value)) && isFinite(value)) { 34 | return parseFloat(value) 35 | } else { 36 | return value 37 | } 38 | }, 39 | 40 | camelCase(value) { 41 | return value.replace(/-+(.)?/g, (match, character) => { 42 | return character ? character.toUpperCase() : '' 43 | }) 44 | }, 45 | 46 | accelerate(element) { 47 | helpers.css(element, 'transform', 'translate3d(0,0,0) rotate(0.0001deg)') 48 | helpers.css(element, 'transform-style', 'preserve-3d') 49 | helpers.css(element, 'backface-visibility', 'hidden') 50 | }, 51 | 52 | transformSupport(value) { 53 | let element = document.createElement('div'), 54 | propertySupport = false, 55 | propertyValue = null, 56 | featureSupport = false, 57 | cssProperty = null, 58 | jsProperty = null 59 | for (let i = 0, l = helpers.vendors.length; i < l; i++) { 60 | if (helpers.vendors[i] !== null) { 61 | cssProperty = helpers.vendors[i][0] + 'transform' 62 | jsProperty = helpers.vendors[i][1] + 'Transform' 63 | } else { 64 | cssProperty = 'transform' 65 | jsProperty = 'transform' 66 | } 67 | if (element.style[jsProperty] !== undefined) { 68 | propertySupport = true 69 | break 70 | } 71 | } 72 | switch(value) { 73 | case '2D': 74 | featureSupport = propertySupport 75 | break 76 | case '3D': 77 | if (propertySupport) { 78 | let body = document.body || document.createElement('body'), 79 | documentElement = document.documentElement, 80 | documentOverflow = documentElement.style.overflow, 81 | isCreatedBody = false 82 | 83 | if (!document.body) { 84 | isCreatedBody = true 85 | documentElement.style.overflow = 'hidden' 86 | documentElement.appendChild(body) 87 | body.style.overflow = 'hidden' 88 | body.style.background = '' 89 | } 90 | 91 | body.appendChild(element) 92 | element.style[jsProperty] = 'translate3d(1px,1px,1px)' 93 | propertyValue = window.getComputedStyle(element).getPropertyValue(cssProperty) 94 | featureSupport = propertyValue !== undefined && propertyValue.length > 0 && propertyValue !== 'none' 95 | documentElement.style.overflow = documentOverflow 96 | body.removeChild(element) 97 | 98 | if ( isCreatedBody ) { 99 | body.removeAttribute('style') 100 | body.parentNode.removeChild(body) 101 | } 102 | } 103 | break 104 | } 105 | return featureSupport 106 | }, 107 | 108 | css(element, property, value) { 109 | let jsProperty = helpers.propertyCache[property] 110 | if (!jsProperty) { 111 | for (let i = 0, l = helpers.vendors.length; i < l; i++) { 112 | if (helpers.vendors[i] !== null) { 113 | jsProperty = helpers.camelCase(helpers.vendors[i][1] + '-' + property) 114 | } else { 115 | jsProperty = property 116 | } 117 | if (element.style[jsProperty] !== undefined) { 118 | helpers.propertyCache[property] = jsProperty 119 | break 120 | } 121 | } 122 | } 123 | element.style[jsProperty] = value 124 | } 125 | 126 | } 127 | 128 | const MAGIC_NUMBER = 30, 129 | DEFAULTS = { 130 | relativeInput: false, 131 | clipRelativeInput: false, 132 | inputElement: null, 133 | hoverOnly: false, 134 | calibrationThreshold: 100, 135 | calibrationDelay: 500, 136 | supportDelay: 500, 137 | calibrateX: false, 138 | calibrateY: true, 139 | invertX: true, 140 | invertY: true, 141 | limitX: false, 142 | limitY: false, 143 | scalarX: 10.0, 144 | scalarY: 10.0, 145 | frictionX: 0.1, 146 | frictionY: 0.1, 147 | originX: 0.5, 148 | originY: 0.5, 149 | pointerEvents: false, 150 | precision: 1, 151 | onReady: null, 152 | selector: null 153 | } 154 | 155 | class Parallax { 156 | constructor(element, options) { 157 | 158 | this.element = element 159 | 160 | const data = { 161 | calibrateX: helpers.data(this.element, 'calibrate-x'), 162 | calibrateY: helpers.data(this.element, 'calibrate-y'), 163 | invertX: helpers.data(this.element, 'invert-x'), 164 | invertY: helpers.data(this.element, 'invert-y'), 165 | limitX: helpers.data(this.element, 'limit-x'), 166 | limitY: helpers.data(this.element, 'limit-y'), 167 | scalarX: helpers.data(this.element, 'scalar-x'), 168 | scalarY: helpers.data(this.element, 'scalar-y'), 169 | frictionX: helpers.data(this.element, 'friction-x'), 170 | frictionY: helpers.data(this.element, 'friction-y'), 171 | originX: helpers.data(this.element, 'origin-x'), 172 | originY: helpers.data(this.element, 'origin-y'), 173 | pointerEvents: helpers.data(this.element, 'pointer-events'), 174 | precision: helpers.data(this.element, 'precision'), 175 | relativeInput: helpers.data(this.element, 'relative-input'), 176 | clipRelativeInput: helpers.data(this.element, 'clip-relative-input'), 177 | hoverOnly: helpers.data(this.element, 'hover-only'), 178 | inputElement: document.querySelector(helpers.data(this.element, 'input-element')), 179 | selector: helpers.data(this.element, 'selector') 180 | } 181 | 182 | for (let key in data) { 183 | if (data[key] === null) { 184 | delete data[key] 185 | } 186 | } 187 | 188 | objectAssign(this, DEFAULTS, data, options) 189 | 190 | if(!this.inputElement) { 191 | this.inputElement = this.element 192 | } 193 | 194 | this.calibrationTimer = null 195 | this.calibrationFlag = true 196 | this.enabled = false 197 | this.depthsX = [] 198 | this.depthsY = [] 199 | this.raf = null 200 | 201 | this.bounds = null 202 | this.elementPositionX = 0 203 | this.elementPositionY = 0 204 | this.elementWidth = 0 205 | this.elementHeight = 0 206 | 207 | this.elementCenterX = 0 208 | this.elementCenterY = 0 209 | 210 | this.elementRangeX = 0 211 | this.elementRangeY = 0 212 | 213 | this.calibrationX = 0 214 | this.calibrationY = 0 215 | 216 | this.inputX = 0 217 | this.inputY = 0 218 | 219 | this.motionX = 0 220 | this.motionY = 0 221 | 222 | this.velocityX = 0 223 | this.velocityY = 0 224 | 225 | this.onMouseMove = this.onMouseMove.bind(this) 226 | this.onDeviceOrientation = this.onDeviceOrientation.bind(this) 227 | this.onDeviceMotion = this.onDeviceMotion.bind(this) 228 | this.onOrientationTimer = this.onOrientationTimer.bind(this) 229 | this.onMotionTimer = this.onMotionTimer.bind(this) 230 | this.onCalibrationTimer = this.onCalibrationTimer.bind(this) 231 | this.onAnimationFrame = this.onAnimationFrame.bind(this) 232 | this.onWindowResize = this.onWindowResize.bind(this) 233 | 234 | this.windowWidth = null 235 | this.windowHeight = null 236 | this.windowCenterX = null 237 | this.windowCenterY = null 238 | this.windowRadiusX = null 239 | this.windowRadiusY = null 240 | this.portrait = false 241 | this.desktop = !navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|mobi|tablet|opera mini|nexus 7)/i) 242 | this.motionSupport = !!window.DeviceMotionEvent && !this.desktop 243 | this.orientationSupport = !!window.DeviceOrientationEvent && !this.desktop 244 | this.orientationStatus = 0 245 | this.motionStatus = 0 246 | 247 | this.initialise() 248 | } 249 | 250 | initialise() { 251 | if (this.transform2DSupport === undefined) { 252 | this.transform2DSupport = helpers.transformSupport('2D') 253 | this.transform3DSupport = helpers.transformSupport('3D') 254 | } 255 | 256 | // Configure Context Styles 257 | if (this.transform3DSupport) { 258 | helpers.accelerate(this.element) 259 | } 260 | 261 | let style = window.getComputedStyle(this.element) 262 | if (style.getPropertyValue('position') === 'static') { 263 | this.element.style.position = 'relative' 264 | } 265 | 266 | // Pointer events 267 | if(!this.pointerEvents) { 268 | this.element.style.pointerEvents = 'none' 269 | } 270 | 271 | // Setup 272 | this.updateLayers() 273 | this.updateDimensions() 274 | this.enable() 275 | this.queueCalibration(this.calibrationDelay) 276 | } 277 | 278 | doReadyCallback() { 279 | if(this.onReady) { 280 | this.onReady() 281 | } 282 | } 283 | 284 | updateLayers() { 285 | if(this.selector) { 286 | this.layers = this.element.querySelectorAll(this.selector) 287 | } else { 288 | this.layers = this.element.children 289 | } 290 | 291 | if(!this.layers.length) { 292 | console.warn('ParallaxJS: Your scene does not have any layers.') 293 | } 294 | 295 | this.depthsX = [] 296 | this.depthsY = [] 297 | 298 | for (let index = 0; index < this.layers.length; index++) { 299 | let layer = this.layers[index] 300 | 301 | if (this.transform3DSupport) { 302 | helpers.accelerate(layer) 303 | } 304 | 305 | layer.style.position = index ? 'absolute' : 'relative' 306 | layer.style.display = 'block' 307 | layer.style.left = 0 308 | layer.style.top = 0 309 | 310 | let depth = helpers.data(layer, 'depth') || 0 311 | this.depthsX.push(helpers.data(layer, 'depth-x') || depth) 312 | this.depthsY.push(helpers.data(layer, 'depth-y') || depth) 313 | } 314 | } 315 | 316 | updateDimensions() { 317 | this.windowWidth = window.innerWidth 318 | this.windowHeight = window.innerHeight 319 | this.windowCenterX = this.windowWidth * this.originX 320 | this.windowCenterY = this.windowHeight * this.originY 321 | this.windowRadiusX = Math.max(this.windowCenterX, this.windowWidth - this.windowCenterX) 322 | this.windowRadiusY = Math.max(this.windowCenterY, this.windowHeight - this.windowCenterY) 323 | } 324 | 325 | updateBounds() { 326 | this.bounds = this.inputElement.getBoundingClientRect() 327 | this.elementPositionX = this.bounds.left 328 | this.elementPositionY = this.bounds.top 329 | this.elementWidth = this.bounds.width 330 | this.elementHeight = this.bounds.height 331 | this.elementCenterX = this.elementWidth * this.originX 332 | this.elementCenterY = this.elementHeight * this.originY 333 | this.elementRangeX = Math.max(this.elementCenterX, this.elementWidth - this.elementCenterX) 334 | this.elementRangeY = Math.max(this.elementCenterY, this.elementHeight - this.elementCenterY) 335 | } 336 | 337 | queueCalibration(delay) { 338 | clearTimeout(this.calibrationTimer) 339 | this.calibrationTimer = setTimeout(this.onCalibrationTimer, delay) 340 | } 341 | 342 | enable() { 343 | if (this.enabled) { 344 | return 345 | } 346 | this.enabled = true 347 | 348 | if (this.orientationSupport) { 349 | this.portrait = false 350 | window.addEventListener('deviceorientation', this.onDeviceOrientation) 351 | this.detectionTimer = setTimeout(this.onOrientationTimer, this.supportDelay) 352 | } else if (this.motionSupport) { 353 | this.portrait = false 354 | window.addEventListener('devicemotion', this.onDeviceMotion) 355 | this.detectionTimer = setTimeout(this.onMotionTimer, this.supportDelay) 356 | } else { 357 | this.calibrationX = 0 358 | this.calibrationY = 0 359 | this.portrait = false 360 | window.addEventListener('mousemove', this.onMouseMove) 361 | this.doReadyCallback() 362 | } 363 | 364 | window.addEventListener('resize', this.onWindowResize) 365 | this.raf = rqAnFr(this.onAnimationFrame) 366 | } 367 | 368 | disable() { 369 | if (!this.enabled) { 370 | return 371 | } 372 | this.enabled = false 373 | 374 | if (this.orientationSupport) { 375 | window.removeEventListener('deviceorientation', this.onDeviceOrientation) 376 | } else if (this.motionSupport) { 377 | window.removeEventListener('devicemotion', this.onDeviceMotion) 378 | } else { 379 | window.removeEventListener('mousemove', this.onMouseMove) 380 | } 381 | 382 | window.removeEventListener('resize', this.onWindowResize) 383 | rqAnFr.cancel(this.raf) 384 | } 385 | 386 | calibrate(x, y) { 387 | this.calibrateX = x === undefined ? this.calibrateX : x 388 | this.calibrateY = y === undefined ? this.calibrateY : y 389 | } 390 | 391 | invert(x, y) { 392 | this.invertX = x === undefined ? this.invertX : x 393 | this.invertY = y === undefined ? this.invertY : y 394 | } 395 | 396 | friction(x, y) { 397 | this.frictionX = x === undefined ? this.frictionX : x 398 | this.frictionY = y === undefined ? this.frictionY : y 399 | } 400 | 401 | scalar(x, y) { 402 | this.scalarX = x === undefined ? this.scalarX : x 403 | this.scalarY = y === undefined ? this.scalarY : y 404 | } 405 | 406 | limit(x, y) { 407 | this.limitX = x === undefined ? this.limitX : x 408 | this.limitY = y === undefined ? this.limitY : y 409 | } 410 | 411 | origin(x, y) { 412 | this.originX = x === undefined ? this.originX : x 413 | this.originY = y === undefined ? this.originY : y 414 | } 415 | 416 | setInputElement(element) { 417 | this.inputElement = element 418 | this.updateDimensions() 419 | } 420 | 421 | setPosition(element, x, y) { 422 | x = x.toFixed(this.precision) + 'px' 423 | y = y.toFixed(this.precision) + 'px' 424 | if (this.transform3DSupport) { 425 | helpers.css(element, 'transform', 'translate3d(' + x + ',' + y + ',0)') 426 | } else if (this.transform2DSupport) { 427 | helpers.css(element, 'transform', 'translate(' + x + ',' + y + ')') 428 | } else { 429 | element.style.left = x 430 | element.style.top = y 431 | } 432 | } 433 | 434 | onOrientationTimer() { 435 | if (this.orientationSupport && this.orientationStatus === 0) { 436 | this.disable() 437 | this.orientationSupport = false 438 | this.enable() 439 | } else { 440 | this.doReadyCallback() 441 | } 442 | } 443 | 444 | onMotionTimer() { 445 | if (this.motionSupport && this.motionStatus === 0) { 446 | this.disable() 447 | this.motionSupport = false 448 | this.enable() 449 | } else { 450 | this.doReadyCallback() 451 | } 452 | } 453 | 454 | onCalibrationTimer() { 455 | this.calibrationFlag = true 456 | } 457 | 458 | onWindowResize() { 459 | this.updateDimensions() 460 | } 461 | 462 | onAnimationFrame() { 463 | this.updateBounds() 464 | let calibratedInputX = this.inputX - this.calibrationX, 465 | calibratedInputY = this.inputY - this.calibrationY 466 | if ((Math.abs(calibratedInputX) > this.calibrationThreshold) || (Math.abs(calibratedInputY) > this.calibrationThreshold)) { 467 | this.queueCalibration(0) 468 | } 469 | if (this.portrait) { 470 | this.motionX = this.calibrateX ? calibratedInputY : this.inputY 471 | this.motionY = this.calibrateY ? calibratedInputX : this.inputX 472 | } else { 473 | this.motionX = this.calibrateX ? calibratedInputX : this.inputX 474 | this.motionY = this.calibrateY ? calibratedInputY : this.inputY 475 | } 476 | this.motionX *= this.elementWidth * (this.scalarX / 100) 477 | this.motionY *= this.elementHeight * (this.scalarY / 100) 478 | if (!isNaN(parseFloat(this.limitX))) { 479 | this.motionX = helpers.clamp(this.motionX, -this.limitX, this.limitX) 480 | } 481 | if (!isNaN(parseFloat(this.limitY))) { 482 | this.motionY = helpers.clamp(this.motionY, -this.limitY, this.limitY) 483 | } 484 | this.velocityX += (this.motionX - this.velocityX) * this.frictionX 485 | this.velocityY += (this.motionY - this.velocityY) * this.frictionY 486 | for (let index = 0; index < this.layers.length; index++) { 487 | let layer = this.layers[index], 488 | depthX = this.depthsX[index], 489 | depthY = this.depthsY[index], 490 | xOffset = this.velocityX * (depthX * (this.invertX ? -1 : 1)), 491 | yOffset = this.velocityY * (depthY * (this.invertY ? -1 : 1)) 492 | this.setPosition(layer, xOffset, yOffset) 493 | } 494 | this.raf = rqAnFr(this.onAnimationFrame) 495 | } 496 | 497 | rotate(beta, gamma){ 498 | // Extract Rotation 499 | let x = (beta || 0) / MAGIC_NUMBER, // -90 :: 90 500 | y = (gamma || 0) / MAGIC_NUMBER // -180 :: 180 501 | 502 | // Detect Orientation Change 503 | let portrait = this.windowHeight > this.windowWidth 504 | if (this.portrait !== portrait) { 505 | this.portrait = portrait 506 | this.calibrationFlag = true 507 | } 508 | 509 | if (this.calibrationFlag) { 510 | this.calibrationFlag = false 511 | this.calibrationX = x 512 | this.calibrationY = y 513 | } 514 | 515 | this.inputX = x 516 | this.inputY = y 517 | } 518 | 519 | onDeviceOrientation(event) { 520 | let beta = event.beta 521 | let gamma = event.gamma 522 | if (beta !== null && gamma !== null) { 523 | this.orientationStatus = 1 524 | this.rotate(beta, gamma) 525 | } 526 | } 527 | 528 | onDeviceMotion(event) { 529 | let beta = event.rotationRate.beta 530 | let gamma = event.rotationRate.gamma 531 | if (beta !== null && gamma !== null) { 532 | this.motionStatus = 1 533 | this.rotate(beta, gamma) 534 | } 535 | } 536 | 537 | onMouseMove(event) { 538 | let clientX = event.clientX, 539 | clientY = event.clientY 540 | 541 | // reset input to center if hoverOnly is set and we're not hovering the element 542 | if(this.hoverOnly && 543 | ((clientX < this.elementPositionX || clientX > this.elementPositionX + this.elementWidth) || 544 | (clientY < this.elementPositionY || clientY > this.elementPositionY + this.elementHeight))) { 545 | this.inputX = 0 546 | this.inputY = 0 547 | return 548 | } 549 | 550 | if (this.relativeInput) { 551 | // Clip mouse coordinates inside element bounds. 552 | if (this.clipRelativeInput) { 553 | clientX = Math.max(clientX, this.elementPositionX) 554 | clientX = Math.min(clientX, this.elementPositionX + this.elementWidth) 555 | clientY = Math.max(clientY, this.elementPositionY) 556 | clientY = Math.min(clientY, this.elementPositionY + this.elementHeight) 557 | } 558 | // Calculate input relative to the element. 559 | if(this.elementRangeX && this.elementRangeY) { 560 | this.inputX = (clientX - this.elementPositionX - this.elementCenterX) / this.elementRangeX 561 | this.inputY = (clientY - this.elementPositionY - this.elementCenterY) / this.elementRangeY 562 | } 563 | } else { 564 | // Calculate input relative to the window. 565 | if(this.windowRadiusX && this.windowRadiusY) { 566 | this.inputX = (clientX - this.windowCenterX) / this.windowRadiusX 567 | this.inputY = (clientY - this.windowCenterY) / this.windowRadiusY 568 | } 569 | } 570 | } 571 | 572 | destroy() { 573 | this.disable() 574 | 575 | clearTimeout(this.calibrationTimer) 576 | clearTimeout(this.detectionTimer) 577 | 578 | this.element.removeAttribute('style') 579 | for (let index = 0; index < this.layers.length; index++) { 580 | this.layers[index].removeAttribute('style') 581 | } 582 | 583 | delete this.element 584 | delete this.layers 585 | } 586 | 587 | version() { 588 | return '3.1.0' 589 | } 590 | 591 | } 592 | 593 | module.exports = Parallax 594 | --------------------------------------------------------------------------------