├── .babelrc ├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── demo ├── canvas.js ├── demo.css ├── index.html ├── main.js ├── point.js └── scene.js ├── dev ├── index.html └── main.js ├── dist ├── ScrollTrigger.js ├── ScrollTrigger.min.js ├── ScrollTrigger.min.js.LICENSE.txt └── ScrollTrigger.min.js.map ├── package-lock.json ├── package.json ├── src ├── ScrollTrigger.js ├── config │ └── DefaultOptions.js ├── extensions │ └── Array.js └── scripts │ ├── ScrollAnimationLoop.js │ ├── Trigger.js │ └── TriggerCollection.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "entry" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Build 13 | run: | 14 | npm ci 15 | npm run build 16 | env: 17 | CI: true 18 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Build 15 | run: | 16 | npm ci 17 | npm run build-demo 18 | cp demo/demo.css ./public/demo.css 19 | env: 20 | CI: true 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v2.5.0 23 | env: 24 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 25 | PUBLISH_BRANCH: gh-pages 26 | PUBLISH_DIR: ./public 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | node_modules/ 4 | public/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erik Terwan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScrollTrigger 2 | [![Build](https://github.com/terwanerik/ScrollTrigger/workflows/Build/badge.svg)](https://github.com/terwanerik/ScrollTrigger/actions?query=workflow%3ABuild) 3 | [![Deploy](https://github.com/terwanerik/ScrollTrigger/workflows/Deploy/badge.svg)](https://github.com/terwanerik/ScrollTrigger/actions?query=workflow%3ADeploy) 4 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/terwanerik/ScrollTrigger/blob/master/LICENSE) 5 | [![Issues](https://img.shields.io/github/issues/terwanerik/ScrollTrigger.svg)](https://github.com/terwanerik/ScrollTrigger/issues) 6 | [![GitHub release](https://img.shields.io/github/release/terwanerik/ScrollTrigger.svg)](https://github.com/terwanerik/ScrollTrigger/releases) 7 | [![npm (scoped)](https://img.shields.io/npm/v/@terwanerik/scrolltrigger)](https://www.npmjs.com/package/@terwanerik/scrolltrigger) 8 | 9 | Let your page react to scroll changes. 10 | 11 | The most basic usage of ScrollTrigger is to trigger classes based on the current scroll position. E.g. when an element enters the viewport, fade it in. You can add custom offsets per element, or set offsets on the viewport (e.g. always trigger after the element reaches 20% of the viewport) 12 | 13 | When using the callbacks ScrollTrigger becomes really powerfull. You can run custom code when an element enters / becomes visible, and even return Promises to halt the trigger if the callback fails. This makes lazy loading images very easy. 14 | 15 | ## Installation 16 | `npm install @terwanerik/scrolltrigger` or just add the `dist/ScrollTrigger.min.js` file to your project and import that. 17 | 18 | ## How to use? 19 | The easiest way to start is to create a new instance and add some triggers to it, with all default values. This will toggle the 'visible' class when the element comes into the viewport, and toggles the 'invisible' class when it scrolls out of the viewport. 20 | 21 | ```javascript 22 | // when using ES6 import / npm 23 | import ScrollTrigger from '@terwanerik/scrolltrigger' 24 | // Create a new ScrollTrigger instance with default options 25 | const trigger = new ScrollTrigger() // When not using npm, create a new instance with 'new ScrollTrigger.default()' 26 | // Add all html elements with attribute data-trigger 27 | trigger.add('[data-trigger]') 28 | ``` 29 | 30 | Now in your CSS add the following classes, this fades the `[data-trigger]` elements in and out. 31 | 32 | ```css 33 | .visible, .invisible { 34 | opacity: 0.0; 35 | transition: opacity 0.5s ease; 36 | } 37 | .visible { 38 | opacity: 1.0; 39 | } 40 | ``` 41 | 42 | > ⚠️ **Attention** 43 | > Are you migrating from 0.x to 1.x? [Checkout the migration guide!](https://github.com/terwanerik/ScrollTrigger#migrating-from-0x-to-1x) 44 | 45 | ### Some more demo's 46 | - [A Vue.js example with image lazy loading](https://codepen.io/erikterwan/pen/bGNeRMr) 47 | - [A Vue.js example with infinite scroll](https://codepen.io/erikterwan/pen/QWwEMdZ) 48 | 49 | ## A more detailed example 50 | Adding callbacks / different classes can be done globally, this becomes the default for all triggers you add, or you can specify custom configuration when adding a trigger. 51 | 52 | ```javascript 53 | // Create a new ScrollTrigger instance with some custom options 54 | const trigger = new ScrollTrigger({ 55 | trigger: { 56 | once: true 57 | } 58 | }) 59 | // Add all html elements with attribute data-trigger, these elements will only be triggered once 60 | trigger.add('[data-trigger]') 61 | // Add all html elements with attribute data-triggerAlways, these elements will always be triggered 62 | trigger.add('[data-triggerAlways]', { once: false }) 63 | ``` 64 | 65 | ## Options 66 | The full set of options is taken from the `demo` directory, for more info, check that out. 67 | 68 | ```javascript 69 | const trigger = new ScrollTrigger({ 70 | // Set custom (default) options for the triggers, these can be overwritten 71 | // when adding new triggers to the ScrollTrigger instance. If you pass 72 | // options when adding new triggers, you'll only need to pass the object 73 | // `trigger`, e.g. { once: false } 74 | trigger: { 75 | // If the trigger should just work one time 76 | once: false, 77 | offset: { 78 | // Set an offset based on the elements position, returning an 79 | // integer = offset in px, float = offset in percentage of either 80 | // width (when setting the x offset) or height (when setting y) 81 | // 82 | // So setting an yOffset of 0.2 means 20% of the elements height, 83 | // the callback / class will be toggled when the element is 20% 84 | // in the viewport. 85 | element: { 86 | x: 0, 87 | y: (trigger, rect, direction) => { 88 | // You can add custom offsets according to callbacks, you 89 | // get passed the trigger, rect (DOMRect) and the scroll 90 | // direction, a string of either top, left, right or 91 | // bottom. 92 | return 0.2 93 | } 94 | }, 95 | // Setting an offset of 0.2 on the viewport means the trigger 96 | // will be called when the element is 20% in the viewport. So if 97 | // your screen is 1200x600px, the trigger will be called when the 98 | // user has scrolled for 120px. 99 | viewport: { 100 | x: 0, 101 | y: (trigger, frame, direction) => { 102 | // We check if the trigger is visible, if so, the offset 103 | // on the viewport is 0, otherwise it's 20% of the height 104 | // of the viewport. This causes the triggers to animate 105 | // 'on screen' when the element is in the viewport, but 106 | // don't trigger the 'out' class until the element is out 107 | // of the viewport. 108 | 109 | // This is the same as returning Math.ceil(0.2 * frame.h) 110 | return trigger.visible ? 0 : 0.2 111 | } 112 | } 113 | }, 114 | toggle: { 115 | // The class(es) that should be toggled 116 | class: { 117 | in: 'visible', // Either a string, or an array of strings 118 | out: ['invisible', 'extraClassToToggleWhenHidden'] 119 | }, 120 | callback: { 121 | // A callback when the element is going in the viewport, you can 122 | // return a Promise here, the trigger will not be called until 123 | // the promise resolves. 124 | in: null, 125 | // A callback when the element is visible on screen, keeps 126 | // on triggering for as long as 'sustain' is set 127 | visible: null, 128 | // A callback when the element is going out of the viewport. 129 | // You can also return a promise here, like in the 'in' callback. 130 | // 131 | // Here an example where all triggers take 10ms to trigger 132 | // the 'out' class. 133 | out: (trigger) => { 134 | // `trigger` contains the Trigger object that goes out 135 | // of the viewport 136 | return new Promise((resolve, reject) => { 137 | setTimeout(resolve, 10) 138 | }) 139 | } 140 | } 141 | }, 142 | }, 143 | // Set custom options and callbacks for the ScrollAnimationLoop 144 | scroll: { 145 | // The amount of ms the scroll loop should keep triggering after the 146 | // scrolling has stopped. This is sometimes nice for canvas 147 | // animations. 148 | sustain: 200, 149 | // Window|HTMLDocument|HTMLElement to check for scroll events 150 | element: window, 151 | // Add a callback when the user has scrolled, keeps on triggering for 152 | // as long as the sustain is set to do 153 | callback: didScroll, 154 | // Callback when the user started scrolling 155 | start: () => {}, 156 | // Callback when the user stopped scrolling 157 | stop: () => {}, 158 | // Callback when the user changes direction in scrolling 159 | directionChange: () => {} 160 | } 161 | }) 162 | 163 | /*** 164 | ** Methods on the ScrollTrigger instance 165 | ***/ 166 | 167 | /** 168 | * Creates a Trigger object from a given element and optional option set 169 | * @param {HTMLElement} element 170 | * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options 171 | * @returns Trigger 172 | */ 173 | trigger.createTrigger(element, options) 174 | 175 | /** 176 | * Creates an array of triggers 177 | * @param {HTMLElement[]|NodeList} elements 178 | * @param {Object} [options=null] options 179 | * @returns {Trigger[]} Array of triggers 180 | */ 181 | trigger.createTriggers(elements, options) 182 | 183 | /** 184 | * Adds triggers 185 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 186 | * @param {Object} [options=null] options 187 | * @returns {ScrollTrigger} 188 | */ 189 | trigger.add(objects, options) 190 | 191 | /** 192 | * Removes triggers 193 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 194 | * @returns {ScrollTrigger} 195 | */ 196 | trigger.remove(objects) 197 | 198 | /** 199 | * Lookup one or multiple triggers by a query string 200 | * @param {string} selector 201 | * @returns {Trigger[]} 202 | */ 203 | trigger.query(selector) 204 | 205 | /** 206 | * Lookup one or multiple triggers by a certain HTMLElement or NodeList 207 | * @param {HTMLElement|HTMLElement[]|NodeList} element 208 | * @returns {Trigger|Trigger[]|null} 209 | */ 210 | trigger.search(element) 211 | 212 | /** 213 | * Reattaches the scroll listener 214 | */ 215 | trigger.listen() 216 | 217 | /** 218 | * Kills the scroll listener 219 | */ 220 | trigger.kill() 221 | 222 | 223 | /*** 224 | ** Methods on a Trigger instance, e.g. when receiving from a callback or from a query 225 | ***/ 226 | const receivedTrigger = new Trigger() 227 | 228 | /** 229 | * The HTML element 230 | */ 231 | receivedTrigger.element 232 | 233 | /** 234 | * The offset settings 235 | */ 236 | receivedTrigger.offset 237 | 238 | /** 239 | * The toggle settings 240 | */ 241 | receivedTrigger.toggle 242 | 243 | /** 244 | * If the trigger should fire once, boolean 245 | */ 246 | receivedTrigger.once 247 | 248 | /** 249 | * If the trigger is visible, boolean 250 | */ 251 | receivedTrigger.visible 252 | ``` 253 | 254 | ## Migrating from 0.x to 1.x 255 | The main differences between `0.x` and `1.x` are the way you add and configure your 256 | triggers. `0.x` added all HTMLElement's with the data-scroll attribute by default, 257 | `1.x` doesn't do that, this requires you to add the triggers yourself. This improves 258 | the configuration of the triggers. 259 | 260 | Also, please note that when *not* using a package manager / webpack, and you're 261 | just importing the minified version, you'll have to always use `new ScrollTrigger.default()`. 262 | 263 | ```html 264 | 265 | 268 | ``` 269 | 270 | Take for example the following element in ScrollTrigger `0.x`: 271 | 272 | ```html 273 |
274 | ``` 275 | 276 | In ScrollTrigger `1.x` you would write this mostly in JavaScript: 277 | 278 | ```javascript 279 | // Say you have some divs with class 'animateMe' 280 | const scrollTrigger = new ScrollTrigger() 281 | scrollTrigger.add('.animateMe', { 282 | once: true, // same functionality as the `once` flag in v0.x 283 | offset: { 284 | element: { 285 | y: 1.0 // note that we pass a float instead of an integer, when the 286 | // offset is a float, it takes it as a percentage, in this 287 | // case, add 100% of the height of the element, the same 288 | // functionality as the `addHeight` flag in v0.x 289 | } 290 | }, 291 | toggle: { 292 | callback: { 293 | in: () => { // same as the data-scroll-showCallback, no need to set a 294 | // custom callScope when calling custom functions and 295 | // the possibility to return a Promise 296 | alert('Visible') 297 | }, 298 | out: () => { // same as the data-scroll-hideCallback 299 | alert('Invisible') 300 | } 301 | } 302 | } 303 | }) 304 | ``` 305 | 306 | The advantage of writing all this in javascript is the configuration possible, say 307 | i want to change the offset of the element after the first time it's been visible 308 | (e.g. remove the `addHeight` flag after it's been shown): 309 | 310 | ```javascript 311 | scrollTrigger.add('.animateMe', { 312 | offset: { 313 | element: { 314 | y: 1.0 315 | } 316 | }, 317 | toggle: { 318 | callback: { 319 | in: (trigger) => { 320 | // remove the element offset 321 | trigger.offset.element.y = 0 322 | } 323 | } 324 | } 325 | }) 326 | ``` 327 | 328 | Another example for setting custom classes per toggle; 329 | 330 | ```html 331 |
332 | ``` 333 | 334 | Becomes 335 | 336 | ```javascript 337 | const scrollTrigger = new ScrollTrigger() 338 | 339 | scrollTrigger.add('[data-scroll]', { 340 | toggle: { 341 | class: { 342 | in: 'animateIn', 343 | out: 'animateOut' 344 | } 345 | } 346 | }) 347 | ``` 348 | 349 | If you have any questions on migrating to `v1.x` feel free to [create a new issue](https://github.com/terwanerik/ScrollTrigger/issues). 350 | 351 | ## Contributing 352 | Fork, have a look in the `src/` directory and enjoy! If you have improvements, start a new branch & create a pull request when you're all done :) 353 | 354 | ## Troubleshooting 355 | You can see really quickly if the Trigger is working by hitting 'inspect element'. Here you can see if the visible/invisble class is toggled when you scroll past the element. 356 | 357 | If the classes don't toggle, check the JavaScript console. There might be some handy info in there. 358 | 359 | #### Found an issue? 360 | Ooh snap, well, bugs happen. Please create a new issue and mention the OS and browser (including version) that the issue is occurring on. If you are really kind, make a [minimal, complete and verifiable example](http://stackoverflow.com/help/mcve) and upload that to [codepen](http://codepen.io). 361 | 362 | ## Legacy 363 | Looking for the old ScrollTrigger? Check out the [legacy branch](https://github.com/terwanerik/ScrollTrigger/tree/legacy-v0.3.6)! 364 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ScrollTrigger", 3 | "main": "dist/ScrollTrigger.js", 4 | "homepage": "https://github.com/terwanerik/ScrollTrigger", 5 | "authors": [ 6 | "Erik Terwan" 7 | ], 8 | "description": "Triggers classes on html elements based on the scroll position. It makes use of requestAnimationFrame so it doesn't jack the users scroll, that way the user / browser keeps their original scroll behaviour. Animations run when the browser is ready for it.", 9 | "moduleType": "globals", 10 | "keywords": [ 11 | "scroll", 12 | "trigger", 13 | "animation", 14 | "css3" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /demo/canvas.js: -------------------------------------------------------------------------------- 1 | import Scene from './scene' 2 | 3 | const FRAME_RATE = 60 4 | const FRAME_RATE_SECONDS = 1000 / FRAME_RATE 5 | 6 | export default class Canvas { 7 | constructor(ctx, w, h) { 8 | this.ctx = ctx 9 | this.width = w 10 | this.height = h 11 | 12 | this.scene = new Scene(ctx, w, h) 13 | this.lastDraw = null 14 | } 15 | 16 | update() { 17 | this.scene.update(this.width, this.height) 18 | } 19 | 20 | set scrollDelta(val) { 21 | this.scene.scrollDelta = val 22 | } 23 | 24 | draw() { 25 | const now = Date.now() 26 | 27 | if (this.lastDraw !== null) { 28 | const diff = now - this.lastDraw 29 | 30 | if (diff < FRAME_RATE_SECONDS) { return } 31 | } 32 | 33 | this.ctx.clearRect(0, 0, this.width, this.height) 34 | this.scene.draw() 35 | 36 | this.lastDraw = now 37 | } 38 | 39 | didResize() { 40 | this.scene.reset() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | background: #021517; 8 | color: #a0bbbd; 9 | 10 | font-family: 'Montserrat', Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; 11 | font-weight: 400; 12 | font-size: 21px; 13 | -webkit-font-smoothing: antialiased; 14 | } 15 | 16 | h1, h2, h3, h4, p { 17 | margin: 0; 18 | padding: 0.25em 0; 19 | } 20 | 21 | h1 { 22 | font-size: 2.5em; 23 | } 24 | 25 | h2 { 26 | font-size: 1.875em; 27 | font-weight: normal; 28 | } 29 | 30 | h3 { 31 | font-size: 1.5em; 32 | } 33 | 34 | a { 35 | text-decoration: none; 36 | font-weight: 500; 37 | color: #689396; 38 | } 39 | 40 | section { 41 | display: table; 42 | position: relative; 43 | box-sizing: border-box; 44 | width: 100%; 45 | min-height: 100vh; 46 | 47 | padding: 75px 15px; 48 | } 49 | 50 | section div.Wrapper { 51 | display: table-cell; 52 | vertical-align: middle; 53 | } 54 | 55 | canvas { 56 | position: fixed; 57 | top: 0; 58 | left: 0; 59 | width: 100%; 60 | height: 100%; 61 | 62 | z-index: 0; 63 | } 64 | 65 | section h3, 66 | section p, 67 | section code { 68 | display: block; 69 | width: 98%; 70 | max-width: 680px; 71 | margin: 0 auto; 72 | padding: 0 0 25px 0; 73 | 74 | line-height: 1.4em; 75 | color: inherit; 76 | } 77 | 78 | section h3 { 79 | margin: 45px auto 0 auto; 80 | } 81 | 82 | section p span { 83 | display: inline-block; 84 | position: relative; 85 | top: -2px; 86 | padding: 0 5px; 87 | 88 | background: #689396; 89 | 90 | font-family: monospace; 91 | font-size: 14px; 92 | color: #fff; 93 | } 94 | 95 | section code { 96 | padding: 15px; 97 | 98 | background: #689396; 99 | color: #fff; 100 | font-size: 14px; 101 | } 102 | 103 | section code span { 104 | opacity: 0.6; 105 | } 106 | 107 | section.Intro { 108 | color: #a0bbbd; 109 | } 110 | 111 | section.Intro h2 { 112 | color: rgba(160, 187, 189, 0.8); 113 | } 114 | 115 | section.About { 116 | color: #497275; 117 | background: #a0bbbd; 118 | } 119 | 120 | section.Examples { 121 | background: #fff; 122 | color: #689396; 123 | } 124 | 125 | .CenterAlign { 126 | text-align: center; 127 | } 128 | 129 | div.Wrapper { 130 | position: relative; 131 | width: 96%; 132 | max-width: 680px; 133 | margin: 0 auto; 134 | 135 | z-index: 1; 136 | } 137 | 138 | div.MouseScroll { 139 | position: absolute; 140 | left: 50%; 141 | bottom: 25px; 142 | width: 100px; 143 | height: 100px; 144 | margin: 0 0 0 -50px; 145 | 146 | z-index: 2; 147 | } 148 | 149 | path.MouseScroll--chevron { 150 | animation: ChevronAnimation 3s ease infinite; 151 | transform: translateY(3px) 152 | } 153 | 154 | [data-slideInLeft].visible, [data-slideInLeft].invisible, 155 | [data-slideInRight].visible, [data-slideInRight].invisible , 156 | [data-slideInBottom].visible, [data-slideInBottom].invisible { 157 | opacity: 1.0; 158 | transform: translate(0, 0); 159 | transition: transform 0.8s ease, opacity 0.8s ease; 160 | } 161 | 162 | [data-slideInLeft].invisible { 163 | opacity: 0.0; 164 | transform: translate(10px, 0); 165 | } 166 | 167 | [data-slideInRight].invisible { 168 | opacity: 0.0; 169 | transform: translate(-10px, 0); 170 | } 171 | 172 | [data-slideInBottom].invisible { 173 | opacity: 0.0; 174 | transform: translate(0, 10px); 175 | } 176 | 177 | @keyframes ChevronAnimation { 178 | 0% { 179 | transform: translateY(3px); 180 | opacity: 1 181 | } 182 | 50% { 183 | transform: translateY(8px); 184 | opacity: 0 185 | } 186 | 60% { 187 | transform: translateY(3px); 188 | opacity: 0 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ScrollTrigger - Let your page react to scroll changes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 |
27 |

ScrollTrigger

28 |

Let your page react to scroll changes

29 |
30 |
31 |
32 |
33 |

34 | Trigger classes based on scroll position 35 |

36 |

37 | The most basic usage of ScrollTrigger is to trigger classes based 38 | on the current scroll position. E.g. when an element enters the 39 | viewport, fade it in. You can add custom offsets per element, or 40 | set offsets on the viewport (e.g. always trigger after the 41 | element reaches 20% of the viewport) 42 |

43 | 44 |

45 | Execute callbacks on entering / leaving the viewport 46 |

47 |

48 | When using the callbacks ScrollTrigger becomes really powerfull. 49 | You can run custom code when an element enters / becomes visible, 50 | and even return Promises to halt the trigger if the callback 51 | fails. This makes lazy loading images very easy. 52 |

53 |

54 |
55 | You've scrolled passed this block 0 times. 56 |
57 |
58 |
59 |
60 |
61 |

Show me some code!

62 |

63 | The easiest way to start is to create a new instance and add some 64 | triggers to it, with all default values. This will toggle the 'visible' 65 | class when the element comes into the viewport, and toggles the 'invisible' 66 | class when it scrolls out of the viewport. 67 |

68 | 69 | 70 | // Create a new ScrollTrigger instance with default options
71 | const trigger = new ScrollTrigger()
72 | // Add all html elements with attribute data-trigger
73 | trigger.add('[data-trigger]')
74 |
75 | // Now in your CSS add the following classes, this 76 | fades the [data-trigger] elements in and out
77 | 78 | .visible, .invisible {
79 |   opacity: 0.0;
80 |   transition: opacity 0.5s ease;
81 | }
82 | 83 | .visible {
84 |   opacity: 1.0;
85 | } 86 |
87 | 88 |

Now let's add some callbacks and custom classes

89 |

90 | Adding callbacks / different classes can be done globally, this 91 | becomes the default for all triggers you add, or you can specify 92 | custom configuration when adding a trigger. 93 |

94 | 95 | 96 | // Create a new ScrollTrigger instance with some custom options
97 | const trigger = new ScrollTrigger({
98 |   trigger: {
99 |     once: true
100 |   }
101 | })
102 | // Add all html elements with attribute data-trigger, these elements will only be triggered once
103 | trigger.add('[data-trigger]')
104 | 105 | // Add all html elements with attribute data-triggerAlways, these elements will always be triggered
106 | trigger.add('[data-triggerAlways]', { once: false })
107 |
108 | 109 |

110 | 111 |

112 | For more examples, checkout the /demo directory on 113 | GitHub. 114 |

115 |
116 |
117 |
118 |
119 |

More information?

120 |

Visit GitHub!

121 |
122 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import ScrollTrigger, { Trigger } from '../src/ScrollTrigger' 2 | import Canvas from './canvas' 3 | 4 | ((document, window) => { 5 | // This is where the magic happens, start by initializing a ScrollTrigger 6 | // instance. We can set default options for all triggers in the constructor. 7 | // 8 | // We set some default 'trigger' options, and add a custom callback for 9 | // the didScroll method. Also we set the scroll sustain to 800ms. 10 | const trigger = new ScrollTrigger({ 11 | // Set custom (default) options for the triggers, these can be overwritten 12 | // when adding new triggers to the ScrollTrigger instance. If you pass 13 | // options when adding new triggers, you'll only need to pass the object 14 | // `trigger`, e.g. { once: false } 15 | trigger: { 16 | // If the trigger should just work one time 17 | once: false, 18 | offset: { 19 | // Set an offset based on the elements position, returning an 20 | // integer = offset in px, float = offset in percentage of either 21 | // width (when setting the x offset) or height (when setting y) 22 | // 23 | // So setting an yOffset of 0.2 means 20% of the elements height, 24 | // the callback / class will be toggled when the element is 20% 25 | // in the viewport. 26 | element: { 27 | x: 0, 28 | y: (trigger, rect, direction) => { 29 | // You can add custom offsets according to callbacks, you 30 | // get passed the trigger, rect (DOMRect) and the scroll 31 | // direction, a string of either top, left, right or 32 | // bottom. 33 | return 0.2 34 | } 35 | }, 36 | // Setting an offset of 0.2 on the viewport means the trigger 37 | // will be called when the element is 20% in the viewport. So if 38 | // your screen is 1200x600px, the trigger will be called when the 39 | // user has scrolled for 120px. 40 | viewport: { 41 | x: 0, 42 | y: (trigger, frame, direction) => { 43 | // We check if the trigger is visible, if so, the offset 44 | // on the viewport is 0, otherwise it's 20% of the height 45 | // of the viewport. This causes the triggers to animate 46 | // 'on screen' when the element is in the viewport, but 47 | // don't trigger the 'out' class until the element is out 48 | // of the viewport. 49 | 50 | // This is the same as returning Math.ceil(0.2 * frame.h) 51 | return trigger.visible ? 0 : 0.2 52 | } 53 | } 54 | }, 55 | toggle: { 56 | // The class(es) that should be toggled 57 | class: { 58 | in: 'visible', // Either a string, or an array of strings 59 | out: ['invisible', 'extraClassToToggleWhenHidden'] 60 | }, 61 | callback: { 62 | // A callback when the element is going in the viewport, you can 63 | // return a Promise here, the trigger will not be called until 64 | // the promise resolves. 65 | in: null, 66 | // A callback when the element is visible on screen, keeps 67 | // on triggering for as long as 'sustain' is set 68 | visible: null, 69 | // A callback when the element is going out of the viewport. 70 | // You can also return a promise here, like in the 'in' callback. 71 | // 72 | // Here an example where all triggers take 10ms to trigger 73 | // the 'out' class. 74 | out: (trigger) => { 75 | // `trigger` contains the Trigger object that goes out 76 | // of the viewport 77 | return new Promise((resolve, reject) => { 78 | setTimeout(resolve, 10) 79 | }) 80 | } 81 | } 82 | }, 83 | }, 84 | // Set custom options and callbacks for the ScrollAnimationLoop 85 | scroll: { 86 | // The amount of ms the scroll loop should keep triggering after the 87 | // scrolling has stopped. This is sometimes nice for canvas 88 | // animations. 89 | sustain: 200, 90 | // Window|HTMLDocument|HTMLElement to check for scroll events 91 | element: window, 92 | // Add a callback when the user has scrolled, keeps on triggering for 93 | // as long as the sustain is set to do 94 | callback: didScroll, 95 | // Callback when the user started scrolling 96 | start: () => {}, 97 | // Callback when the user stopped scrolling 98 | stop: () => {}, 99 | // Callback when the user changes direction in scrolling 100 | directionChange: () => {} 101 | } 102 | }) 103 | 104 | const canvasElement = document.querySelector('canvas'), 105 | ctx = canvasElement.getContext('2d') 106 | 107 | let w = canvasElement.width = window.innerWidth, 108 | h = canvasElement.height = window.innerHeight, 109 | density = 1, isDrawing = true 110 | 111 | const canvas = new Canvas(ctx, w, h) 112 | 113 | function setup() { 114 | // Add the triggers 115 | addTriggers() 116 | 117 | // Basic canvas setup 118 | window.addEventListener('resize', resize) 119 | 120 | density = window.devicePixelRatio != undefined ? window.devicePixelRatio : 1.0 121 | 122 | canvasElement.width = w * density 123 | canvasElement.height = h * density 124 | 125 | canvas.width = w 126 | canvas.height = h 127 | 128 | ctx.scale(density,density) 129 | 130 | draw() 131 | } 132 | 133 | function addTriggers() { 134 | // Adding triggers can be done in multiple ways, the easiest is to pass 135 | // a querySelector. 136 | trigger.add('[data-slideInLeft]') 137 | .add('[data-slideInRight]') 138 | .add('[data-slideInBottom]') 139 | 140 | // Add the trigger for the callback example, also add a custom callback 141 | // when the trigger becomes visible. As an example we pass an HTMLElement 142 | // instead of a querySelector. 143 | const element = document.querySelector('[data-callback]') 144 | trigger.add(element, { 145 | toggle: { 146 | callback: { 147 | in: counterCallback 148 | } 149 | } 150 | }) 151 | } 152 | 153 | function counterCallback(trigger) { 154 | // In the callback we get passed the Trigger object, from here we have 155 | // access to the responding HTMLElement among other things. You could, 156 | // for instance, change the class it toggles, or attach another callback. 157 | // Check the console for more info. 158 | console.info(trigger) 159 | 160 | // For now, we just append the counter 161 | const counterElement = trigger.element.querySelector('span') 162 | const counter = parseInt(counterElement.innerText) 163 | 164 | counterElement.innerText = counter + 1 165 | } 166 | 167 | function didScroll(position) { 168 | // calculate the delta, from 0 to 1 (when having 1 screen height) to 169 | // animate with 170 | const delta = (position.y / window.innerHeight) 171 | 172 | canvas.scrollDelta = delta 173 | 174 | // change the backgroundColor accordingly 175 | // const lightness = map(delta, 0, 1, 5, 76) 176 | // const saturation = map(delta, 0, 1, 84, 0) 177 | 178 | // document.body.style.backgroundColor = `hsl(186, ${saturation}%, ${lightness}%)` 179 | 180 | // check if the canvas is on-screen, otherwise stop the animationLoop. 181 | if (position.y > window.innerHeight) { 182 | isDrawing = false 183 | } else if (!isDrawing) { 184 | isDrawing = true 185 | 186 | draw() 187 | } 188 | } 189 | 190 | function map(value, start1, stop1, start2, stop2) { 191 | return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2 192 | } 193 | 194 | function draw() { 195 | canvas.update() 196 | canvas.draw() 197 | 198 | if (isDrawing) { 199 | window.requestAnimationFrame(draw) 200 | } 201 | } 202 | 203 | function resize() { 204 | w = canvasElement.width = window.innerWidth 205 | h = canvasElement.height = window.innerHeight 206 | 207 | canvasElement.width = w * density 208 | canvasElement.height = h * density 209 | 210 | canvas.width = w 211 | canvas.height = h 212 | 213 | ctx.scale(density, density) 214 | 215 | canvas.didResize() 216 | } 217 | 218 | setup() 219 | })(document, window) 220 | -------------------------------------------------------------------------------- /demo/point.js: -------------------------------------------------------------------------------- 1 | const POINT_RADIUS = 3.5 2 | const MIN_SPEED = 0.05 3 | const MAX_SPEED = 0.1 4 | 5 | function getRandomNegative(from, to) { 6 | const rand = from + (Math.random() * (to - from)) 7 | 8 | return Math.random() > 0.5 ? rand : -rand 9 | } 10 | 11 | export default class Point { 12 | constructor(id, w, h) { 13 | this.id = id 14 | this.x = Math.random() * w 15 | this.y = Math.random() * h 16 | this.xExtra = 0 17 | this.yExtra = 0 18 | 19 | this.xSpeed = getRandomNegative(MIN_SPEED, MAX_SPEED) 20 | this.ySpeed = getRandomNegative(MIN_SPEED, MAX_SPEED) 21 | } 22 | 23 | update(w, h, delta) { 24 | let xExtra = 0 25 | let yExtra = 0 26 | 27 | if (this.x > w / 2) { 28 | this.xExtra = delta * (w / 2) 29 | } else { 30 | this.xExtra = -(delta * (w / 2)) 31 | } 32 | 33 | if (this.y > h / 2) { 34 | this.yExtra = delta * (h / 2) 35 | } else { 36 | this.yExtra = -(delta * (h / 2)) 37 | } 38 | 39 | this.x += this.xSpeed 40 | this.y += this.ySpeed 41 | 42 | if (this.x < POINT_RADIUS || this.x + POINT_RADIUS > w) { 43 | this.x = this.x < POINT_RADIUS ? POINT_RADIUS : (w - POINT_RADIUS) 44 | this.xSpeed = -this.xSpeed 45 | } 46 | 47 | if (this.y < POINT_RADIUS || this.y + POINT_RADIUS > h) { 48 | this.y = this.y < POINT_RADIUS ? POINT_RADIUS : (h - POINT_RADIUS) 49 | this.ySpeed = -this.ySpeed 50 | } 51 | } 52 | 53 | get calcX() { 54 | return this.x - this.xExtra 55 | } 56 | 57 | get calcY() { 58 | return this.y - this.yExtra 59 | } 60 | 61 | draw(ctx) { 62 | ctx.beginPath() 63 | ctx.arc(this.calcX, this.calcY, POINT_RADIUS, 0, 2 * Math.PI); 64 | ctx.fill() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /demo/scene.js: -------------------------------------------------------------------------------- 1 | import Point from './point' 2 | 3 | const AMOUNT_DELTA = 2.4 4 | const CONNECT_DISTANCE = 260 5 | const LINE_WIDTH = 2 6 | 7 | export default class Scene { 8 | constructor(ctx, w, h) { 9 | this.ctx = ctx 10 | this.width = w 11 | this.height = h 12 | this.scrollDelta = 0 13 | 14 | this.ctx.globalCompositeOperation = 'lighter' 15 | 16 | this.reset() 17 | } 18 | 19 | reset() { 20 | this.points = [] 21 | 22 | this.populate() 23 | } 24 | 25 | populate() { 26 | const amount = Math.ceil(((this.width + this.height) / 100) * AMOUNT_DELTA) 27 | 28 | for (let i = 0; i < amount; i++) { 29 | const point = new Point(i, this.width, this.height) 30 | 31 | this.points.push(point) 32 | } 33 | } 34 | 35 | update(w, h) { 36 | this.width = w 37 | this.height = h 38 | 39 | for (let i = 0; i < this.points.length; i++) { 40 | const point = this.points[i] 41 | 42 | point.update(w, h, this.scrollDelta) 43 | } 44 | } 45 | 46 | draw() { 47 | this.ctx.fillStyle = '#2B7174' 48 | 49 | let linesById = {} 50 | 51 | for (let x = 0; x < this.points.length; x++) { 52 | const point = this.points[x] 53 | 54 | point.draw(this.ctx) 55 | 56 | for (let y = 0; y < this.points.length; y++) { 57 | const reference = this.points[y] 58 | 59 | if (reference.id === point.id) { continue } 60 | 61 | const distanceX = Math.abs(point.calcX - reference.calcX) 62 | const distanceY = Math.abs(point.calcY - reference.calcY) 63 | const distance = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2)) 64 | 65 | if (distance <= CONNECT_DISTANCE) { 66 | const tag = point.id > reference.id ? `${reference.id}_${point.id}` : `${point.id}_${reference.id}` 67 | 68 | if (linesById.hasOwnProperty(tag)) { continue } 69 | 70 | linesById[tag] = { x1: point.calcX, y1: point.calcY, x2: reference.calcX, y2: reference.calcY, distance: distance } 71 | } 72 | } 73 | } 74 | 75 | const lines = Object.values(linesById) 76 | 77 | for (let i = 0; i < lines.length; i++) { 78 | const line = lines[i] 79 | const alpha = 1.0 - (line.distance / CONNECT_DISTANCE) 80 | 81 | this.ctx.strokeStyle = `rgba(98, 130, 94, ${alpha})` 82 | this.ctx.lineWidth = LINE_WIDTH 83 | this.ctx.beginPath() 84 | this.ctx.moveTo(line.x1, line.y1) 85 | this.ctx.lineTo(line.x2, line.y2) 86 | this.ctx.stroke() 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ScrollTrigger 6 | 7 | 27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /dev/main.js: -------------------------------------------------------------------------------- 1 | import ScrollTrigger from '../src/ScrollTrigger' 2 | 3 | // Setup ScrollTrigger with default trigger options 4 | const scroll = new ScrollTrigger({ 5 | trigger: { 6 | once: false 7 | }, 8 | scroll: { 9 | callback: (position, direction) => { 10 | console.log(position) 11 | } 12 | } 13 | }) 14 | 15 | // Add all sections to the scroll trigger colllection 16 | scroll.add('.Block') 17 | -------------------------------------------------------------------------------- /dist/ScrollTrigger.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define("ScrollTrigger", [], factory); 6 | else if(typeof exports === 'object') 7 | exports["ScrollTrigger"] = factory(); 8 | else 9 | root["ScrollTrigger"] = factory(); 10 | })(self, () => { 11 | return /******/ (() => { // webpackBootstrap 12 | /******/ var __webpack_modules__ = ({ 13 | 14 | /***/ 91: 15 | /***/ (() => { 16 | 17 | /** 18 | * Faster than .forEach 19 | * @param {(function())} fn The function to call 20 | */ 21 | Array.prototype.each = function (fn) { 22 | var l = this.length; 23 | for (var i = 0; i < l; i++) { 24 | var e = this[i]; 25 | if (e) { 26 | fn(e, i); 27 | } 28 | } 29 | }; 30 | 31 | /** 32 | * Give NodeList some Array functions 33 | */ 34 | NodeList.prototype.each = Array.prototype.each; 35 | NodeList.prototype.filter = Array.prototype.filter; 36 | 37 | /***/ }), 38 | 39 | /***/ 160: 40 | /***/ ((module) => { 41 | 42 | /*! 43 | * object-extend 44 | * A well-tested function to deep extend (or merge) JavaScript objects without further dependencies. 45 | * 46 | * http://github.com/bernhardw 47 | * 48 | * Copyright 2013, Bernhard Wanger 49 | * Released under the MIT license. 50 | * 51 | * Date: 2013-04-10 52 | */ 53 | 54 | 55 | /** 56 | * Extend object a with object b. 57 | * 58 | * @param {Object} a Source object. 59 | * @param {Object} b Object to extend with. 60 | * @returns {Object} a Extended object. 61 | */ 62 | module.exports = function extend(a, b) { 63 | 64 | // Don't touch 'null' or 'undefined' objects. 65 | if (a == null || b == null) { 66 | return a; 67 | } 68 | 69 | // TODO: Refactor to use for-loop for performance reasons. 70 | Object.keys(b).forEach(function (key) { 71 | 72 | // Detect object without array, date or null. 73 | // TODO: Performance test: 74 | // a) b.constructor === Object.prototype.constructor 75 | // b) Object.prototype.toString.call(b) == '[object Object]' 76 | if (Object.prototype.toString.call(b[key]) == '[object Object]') { 77 | if (Object.prototype.toString.call(a[key]) != '[object Object]') { 78 | a[key] = b[key]; 79 | } else { 80 | a[key] = extend(a[key], b[key]); 81 | } 82 | } else { 83 | a[key] = b[key]; 84 | } 85 | 86 | }); 87 | 88 | return a; 89 | 90 | }; 91 | 92 | /***/ }) 93 | 94 | /******/ }); 95 | /************************************************************************/ 96 | /******/ // The module cache 97 | /******/ var __webpack_module_cache__ = {}; 98 | /******/ 99 | /******/ // The require function 100 | /******/ function __webpack_require__(moduleId) { 101 | /******/ // Check if module is in cache 102 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 103 | /******/ if (cachedModule !== undefined) { 104 | /******/ return cachedModule.exports; 105 | /******/ } 106 | /******/ // Create a new module (and put it into the cache) 107 | /******/ var module = __webpack_module_cache__[moduleId] = { 108 | /******/ // no module.id needed 109 | /******/ // no module.loaded needed 110 | /******/ exports: {} 111 | /******/ }; 112 | /******/ 113 | /******/ // Execute the module function 114 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 115 | /******/ 116 | /******/ // Return the exports of the module 117 | /******/ return module.exports; 118 | /******/ } 119 | /******/ 120 | /************************************************************************/ 121 | /******/ /* webpack/runtime/compat get default export */ 122 | /******/ (() => { 123 | /******/ // getDefaultExport function for compatibility with non-harmony modules 124 | /******/ __webpack_require__.n = (module) => { 125 | /******/ var getter = module && module.__esModule ? 126 | /******/ () => (module['default']) : 127 | /******/ () => (module); 128 | /******/ __webpack_require__.d(getter, { a: getter }); 129 | /******/ return getter; 130 | /******/ }; 131 | /******/ })(); 132 | /******/ 133 | /******/ /* webpack/runtime/define property getters */ 134 | /******/ (() => { 135 | /******/ // define getter functions for harmony exports 136 | /******/ __webpack_require__.d = (exports, definition) => { 137 | /******/ for(var key in definition) { 138 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 139 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 140 | /******/ } 141 | /******/ } 142 | /******/ }; 143 | /******/ })(); 144 | /******/ 145 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 146 | /******/ (() => { 147 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 148 | /******/ })(); 149 | /******/ 150 | /******/ /* webpack/runtime/make namespace object */ 151 | /******/ (() => { 152 | /******/ // define __esModule on exports 153 | /******/ __webpack_require__.r = (exports) => { 154 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 155 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 156 | /******/ } 157 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 158 | /******/ }; 159 | /******/ })(); 160 | /******/ 161 | /************************************************************************/ 162 | var __webpack_exports__ = {}; 163 | // This entry need to be wrapped in an IIFE because it need to be in strict mode. 164 | (() => { 165 | "use strict"; 166 | // ESM COMPAT FLAG 167 | __webpack_require__.r(__webpack_exports__); 168 | 169 | // EXPORTS 170 | __webpack_require__.d(__webpack_exports__, { 171 | "ScrollAnimationLoop": () => (/* binding */ ScrollTrigger_ScrollAnimationLoop), 172 | "Trigger": () => (/* binding */ ScrollTrigger_Trigger), 173 | "TriggerCollection": () => (/* binding */ ScrollTrigger_TriggerCollection), 174 | "default": () => (/* binding */ ScrollTrigger) 175 | }); 176 | 177 | ;// CONCATENATED MODULE: ./src/config/DefaultOptions.js 178 | /** 179 | * Default options for ScrollTrigger 180 | */ 181 | /* harmony default export */ function DefaultOptions() { 182 | /** 183 | * The default options for a trigger 184 | * 185 | * @type { 186 | * { 187 | * once: boolean, 188 | * offset: { 189 | * viewport: { 190 | * x: number|(function(frame, direction)), 191 | * y: number|(function(frame, direction)) 192 | * }, 193 | * element: { 194 | * x: number|(function(rect, direction)), 195 | * y: number|(function(rect, direction)) 196 | * } 197 | * }, 198 | * toggle: { 199 | * class: { 200 | * in: string|string[], 201 | * out: string|string[] 202 | * }, 203 | * callback: { 204 | * in: {TriggerInCallback}, 205 | * visible: (function()), 206 | * out: (function()) 207 | * } 208 | * } 209 | * }} 210 | */ 211 | this.trigger = { 212 | once: false, 213 | offset: { 214 | viewport: { 215 | x: 0, 216 | y: 0 217 | }, 218 | element: { 219 | x: 0, 220 | y: 0 221 | } 222 | }, 223 | toggle: { 224 | "class": { 225 | "in": 'visible', 226 | out: 'invisible' 227 | }, 228 | callback: { 229 | "in": null, 230 | visible: null, 231 | out: null 232 | } 233 | } 234 | }; 235 | 236 | /** 237 | * The `in` callback is called when the element enters the viewport 238 | * @callback TriggerInCallback 239 | * @param {{x: Number, y: Number}} position 240 | * @param {string} direction 241 | */ 242 | 243 | /** 244 | * The default options for the scroll behaviour 245 | * @type { 246 | * { 247 | * sustain: number, 248 | * element: Window|HTMLDocument|HTMLElement, 249 | * callback: {ScrollCallback}, 250 | * start: (function()), 251 | * stop: (function()), 252 | * directionChange: (function(direction: {string})) 253 | * } 254 | * } 255 | */ 256 | this.scroll = { 257 | sustain: 300, 258 | element: window, 259 | callback: function callback() {}, 260 | start: function start() {}, 261 | stop: function stop() {}, 262 | directionChange: function directionChange() {} 263 | }; 264 | 265 | /** 266 | * The scroll callback is called when the user scrolls 267 | * @callback ScrollCallback 268 | * @param {{x: Number, y: Number}} position 269 | * @param {string} direction 270 | */ 271 | } 272 | // EXTERNAL MODULE: ./node_modules/object-extend/lib/extend.js 273 | var extend = __webpack_require__(160); 274 | var extend_default = /*#__PURE__*/__webpack_require__.n(extend); 275 | // EXTERNAL MODULE: ./src/extensions/Array.js 276 | var extensions_Array = __webpack_require__(91); 277 | ;// CONCATENATED MODULE: ./src/scripts/Trigger.js 278 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 279 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 280 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } 281 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 282 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } 283 | function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } 284 | 285 | 286 | 287 | function isInt(n) { 288 | return Number(n) === n && n % 1 === 0; 289 | } 290 | function isFloat(n) { 291 | return Number(n) === n && n % 1 !== 0; 292 | } 293 | var Trigger = /*#__PURE__*/function () { 294 | /** 295 | * Creates a new Trigger from the given element and options 296 | * 297 | * @param {Element|HTMLElement} element 298 | * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options 299 | */ 300 | function Trigger(element, options) { 301 | _classCallCheck(this, Trigger); 302 | this.element = element; 303 | options = extend_default()(new DefaultOptions().trigger, options); 304 | this.offset = options.offset; 305 | this.toggle = options.toggle; 306 | this.once = options.once; 307 | this.visible = null; 308 | this.active = true; 309 | } 310 | 311 | /** 312 | * Checks if the Trigger is in the viewport, calls the callbacks and toggles the classes 313 | * @param {HTMLElement|HTMLDocument|Window} parent 314 | * @param {string} direction top, bottom, left, right 315 | * @returns {boolean} If the element is visible 316 | */ 317 | _createClass(Trigger, [{ 318 | key: "checkVisibility", 319 | value: function checkVisibility(parent, direction) { 320 | if (!this.active) { 321 | return this.visible; 322 | } 323 | var parentWidth = parent.offsetWidth || parent.innerWidth || 0; 324 | var parentHeight = parent.offsetHeight || parent.innerHeight || 0; 325 | var parentFrame = { 326 | w: parentWidth, 327 | h: parentHeight 328 | }; 329 | var rect = this.getBounds(); 330 | var visible = this._checkVisibility(rect, parentFrame, direction); 331 | if (visible !== this.visible) { 332 | this.visible = visible; 333 | var response = this._toggleCallback(); 334 | if (response instanceof Promise) { 335 | response.then(this._toggleClass.bind(this))["catch"](function (e) { 336 | console.error('Trigger promise failed'); 337 | console.error(e); 338 | }); 339 | } else { 340 | this._toggleClass(); 341 | } 342 | if (this.visible && this.once) { 343 | this.active = false; 344 | } 345 | } else if (visible) { 346 | if (typeof this.toggle.callback.visible == 'function') { 347 | return this.toggle.callback.visible.call(this.element, this); 348 | } 349 | } 350 | return visible; 351 | } 352 | 353 | /** 354 | * Get the bounds of this element 355 | * @return {ClientRect | DOMRect} 356 | */ 357 | }, { 358 | key: "getBounds", 359 | value: function getBounds() { 360 | return this.element.getBoundingClientRect(); 361 | } 362 | 363 | /** 364 | * Get the calculated offset to place on the element 365 | * @param {ClientRect} rect 366 | * @param {string} direction top, bottom, left, right 367 | * @returns {{x: number, y: number}} 368 | * @private 369 | */ 370 | }, { 371 | key: "_getElementOffset", 372 | value: function _getElementOffset(rect, direction) { 373 | var offset = { 374 | x: 0, 375 | y: 0 376 | }; 377 | if (typeof this.offset.element.x === 'function') { 378 | offset.x = rect.width * this.offset.element.x(this, rect, direction); 379 | } else if (isFloat(this.offset.element.x)) { 380 | offset.x = rect.width * this.offset.element.x; 381 | } else if (isInt(this.offset.element.x)) { 382 | offset.x = this.offset.element.x; 383 | } 384 | if (typeof this.offset.element.y === 'function') { 385 | offset.y = rect.height * this.offset.element.y(this, rect, direction); 386 | } else if (isFloat(this.offset.element.y)) { 387 | offset.y = rect.height * this.offset.element.y; 388 | } else if (isInt(this.offset.element.y)) { 389 | offset.y = this.offset.element.y; 390 | } 391 | return offset; 392 | } 393 | 394 | /** 395 | * Get the calculated offset to place on the viewport 396 | * @param {{w: number, h: number}} parent 397 | * @param {string} direction top, bottom, left, right 398 | * @returns {{x: number, y: number}} 399 | * @private 400 | */ 401 | }, { 402 | key: "_getViewportOffset", 403 | value: function _getViewportOffset(parent, direction) { 404 | var offset = { 405 | x: 0, 406 | y: 0 407 | }; 408 | if (typeof this.offset.viewport.x === 'function') { 409 | offset.x = parent.w * this.offset.viewport.x(this, parent, direction); 410 | } else if (isFloat(this.offset.viewport.x)) { 411 | offset.x = parent.w * this.offset.viewport.x; 412 | } else if (isInt(this.offset.viewport.x)) { 413 | offset.x = this.offset.viewport.x; 414 | } 415 | if (typeof this.offset.viewport.y === 'function') { 416 | offset.y = parent.h * this.offset.viewport.y(this, parent, direction); 417 | } else if (isFloat(this.offset.viewport.y)) { 418 | offset.y = parent.h * this.offset.viewport.y; 419 | } else if (isInt(this.offset.viewport.y)) { 420 | offset.y = this.offset.viewport.y; 421 | } 422 | return offset; 423 | } 424 | 425 | /** 426 | * Check the visibility of the trigger in the viewport, with offsets applied 427 | * @param {ClientRect} rect 428 | * @param {{w: number, h: number}} parent 429 | * @param {string} direction top, bottom, left, right 430 | * @returns {boolean} 431 | * @private 432 | */ 433 | }, { 434 | key: "_checkVisibility", 435 | value: function _checkVisibility(rect, parent, direction) { 436 | var elementOffset = this._getElementOffset(rect, direction); 437 | var viewportOffset = this._getViewportOffset(parent, direction); 438 | var visible = true; 439 | if (rect.left - viewportOffset.x < -(rect.width - elementOffset.x)) { 440 | visible = false; 441 | } 442 | if (rect.left + viewportOffset.x > parent.w - elementOffset.x) { 443 | visible = false; 444 | } 445 | if (rect.top - viewportOffset.y < -(rect.height - elementOffset.y)) { 446 | visible = false; 447 | } 448 | if (rect.top + viewportOffset.y > parent.h - elementOffset.y) { 449 | visible = false; 450 | } 451 | return visible; 452 | } 453 | 454 | /** 455 | * Toggles the classes 456 | * @private 457 | */ 458 | }, { 459 | key: "_toggleClass", 460 | value: function _toggleClass() { 461 | var _this = this; 462 | if (this.visible) { 463 | if (Array.isArray(this.toggle["class"]["in"])) { 464 | this.toggle["class"]["in"].each(function (className) { 465 | _this.element.classList.add(className); 466 | }); 467 | } else { 468 | this.element.classList.add(this.toggle["class"]["in"]); 469 | } 470 | if (Array.isArray(this.toggle["class"].out)) { 471 | this.toggle["class"].out.each(function (className) { 472 | _this.element.classList.remove(className); 473 | }); 474 | } else { 475 | this.element.classList.remove(this.toggle["class"].out); 476 | } 477 | return; 478 | } 479 | if (Array.isArray(this.toggle["class"]["in"])) { 480 | this.toggle["class"]["in"].each(function (className) { 481 | _this.element.classList.remove(className); 482 | }); 483 | } else { 484 | this.element.classList.remove(this.toggle["class"]["in"]); 485 | } 486 | if (Array.isArray(this.toggle["class"].out)) { 487 | this.toggle["class"].out.each(function (className) { 488 | _this.element.classList.add(className); 489 | }); 490 | } else { 491 | this.element.classList.add(this.toggle["class"].out); 492 | } 493 | } 494 | 495 | /** 496 | * Toggles the callback 497 | * @private 498 | * @return null|Promise 499 | */ 500 | }, { 501 | key: "_toggleCallback", 502 | value: function _toggleCallback() { 503 | if (this.visible) { 504 | if (typeof this.toggle.callback["in"] == 'function') { 505 | return this.toggle.callback["in"].call(this.element, this); 506 | } 507 | } else { 508 | if (typeof this.toggle.callback.out == 'function') { 509 | return this.toggle.callback.out.call(this.element, this); 510 | } 511 | } 512 | } 513 | }]); 514 | return Trigger; 515 | }(); 516 | 517 | ;// CONCATENATED MODULE: ./src/scripts/TriggerCollection.js 518 | function TriggerCollection_typeof(obj) { "@babel/helpers - typeof"; return TriggerCollection_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, TriggerCollection_typeof(obj); } 519 | function TriggerCollection_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 520 | function TriggerCollection_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, TriggerCollection_toPropertyKey(descriptor.key), descriptor); } } 521 | function TriggerCollection_createClass(Constructor, protoProps, staticProps) { if (protoProps) TriggerCollection_defineProperties(Constructor.prototype, protoProps); if (staticProps) TriggerCollection_defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 522 | function TriggerCollection_toPropertyKey(arg) { var key = TriggerCollection_toPrimitive(arg, "string"); return TriggerCollection_typeof(key) === "symbol" ? key : String(key); } 523 | function TriggerCollection_toPrimitive(input, hint) { if (TriggerCollection_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (TriggerCollection_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } 524 | 525 | 526 | var TriggerCollection = /*#__PURE__*/function () { 527 | /** 528 | * Initializes the collection 529 | * @param {Trigger[]} [triggers=[]] triggers A set of triggers to init with, optional 530 | */ 531 | function TriggerCollection(triggers) { 532 | TriggerCollection_classCallCheck(this, TriggerCollection); 533 | /** 534 | * @member {Trigger[]} 535 | */ 536 | this.triggers = triggers instanceof Array ? triggers : []; 537 | } 538 | 539 | /** 540 | * Adds one or multiple Trigger objects 541 | * @param {Trigger|Trigger[]} objects 542 | */ 543 | TriggerCollection_createClass(TriggerCollection, [{ 544 | key: "add", 545 | value: function add(objects) { 546 | var _this = this; 547 | if (objects instanceof Trigger) { 548 | // single 549 | return this.triggers.push(objects); 550 | } 551 | objects.each(function (trigger) { 552 | if (trigger instanceof Trigger) { 553 | _this.triggers.push(trigger); 554 | } else { 555 | console.error('Object added to TriggerCollection is not a Trigger. Object: ', trigger); 556 | } 557 | }); 558 | } 559 | 560 | /** 561 | * Removes one or multiple Trigger objects 562 | * @param {Trigger|Trigger[]} objects 563 | */ 564 | }, { 565 | key: "remove", 566 | value: function remove(objects) { 567 | if (objects instanceof Trigger) { 568 | objects = [objects]; 569 | } 570 | this.triggers = this.triggers.filter(function (trigger) { 571 | var hit = false; 572 | objects.each(function (object) { 573 | if (object == trigger) { 574 | hit = true; 575 | } 576 | }); 577 | return !hit; 578 | }); 579 | } 580 | 581 | /** 582 | * Lookup one or multiple triggers by a query string 583 | * @param {string} selector 584 | * @returns {Trigger[]} 585 | */ 586 | }, { 587 | key: "query", 588 | value: function query(selector) { 589 | return this.triggers.filter(function (trigger) { 590 | var element = trigger.element; 591 | var parent = element.parentNode; 592 | var nodes = [].slice.call(parent.querySelectorAll(selector)); 593 | return nodes.indexOf(element) > -1; 594 | }); 595 | } 596 | 597 | /** 598 | * Lookup one or multiple triggers by a certain HTMLElement or NodeList 599 | * @param {HTMLElement|HTMLElement[]|NodeList} element 600 | * @returns {Trigger|Trigger[]|null} 601 | */ 602 | }, { 603 | key: "search", 604 | value: function search(element) { 605 | var found = this.triggers.filter(function (trigger) { 606 | if (element instanceof NodeList || Array.isArray(element)) { 607 | var hit = false; 608 | element.each(function (el) { 609 | if (trigger.element == el) { 610 | hit = true; 611 | } 612 | }); 613 | return hit; 614 | } 615 | return trigger.element == element; 616 | }); 617 | return found.length == 0 ? null : found.length > 1 ? found : found[0]; 618 | } 619 | 620 | /** 621 | * Calls a function on all triggers 622 | * @param {(function())} callback 623 | */ 624 | }, { 625 | key: "call", 626 | value: function call(callback) { 627 | this.triggers.each(callback); 628 | } 629 | }]); 630 | return TriggerCollection; 631 | }(); 632 | 633 | ;// CONCATENATED MODULE: ./src/scripts/ScrollAnimationLoop.js 634 | function ScrollAnimationLoop_typeof(obj) { "@babel/helpers - typeof"; return ScrollAnimationLoop_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, ScrollAnimationLoop_typeof(obj); } 635 | function ScrollAnimationLoop_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 636 | function ScrollAnimationLoop_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, ScrollAnimationLoop_toPropertyKey(descriptor.key), descriptor); } } 637 | function ScrollAnimationLoop_createClass(Constructor, protoProps, staticProps) { if (protoProps) ScrollAnimationLoop_defineProperties(Constructor.prototype, protoProps); if (staticProps) ScrollAnimationLoop_defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 638 | function ScrollAnimationLoop_toPropertyKey(arg) { var key = ScrollAnimationLoop_toPrimitive(arg, "string"); return ScrollAnimationLoop_typeof(key) === "symbol" ? key : String(key); } 639 | function ScrollAnimationLoop_toPrimitive(input, hint) { if (ScrollAnimationLoop_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (ScrollAnimationLoop_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } 640 | 641 | 642 | 643 | var ScrollAnimationLoop = /*#__PURE__*/function () { 644 | /** 645 | * ScrollAnimationLoop constructor. 646 | * Starts a requestAnimationFrame loop as long as the user has scrolled the scrollElement. Stops after a certain time. 647 | * 648 | * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop 649 | * @param {ScrollCallback} callback [loop=null] The loop callback 650 | */ 651 | function ScrollAnimationLoop(options, callback) { 652 | ScrollAnimationLoop_classCallCheck(this, ScrollAnimationLoop); 653 | this._parseOptions(options); 654 | if (typeof callback === 'function') { 655 | this.callback = callback; 656 | } 657 | this.direction = 'none'; 658 | this.position = this.getPosition(); 659 | this.lastAction = this._getTimestamp(); 660 | this._startRun(); 661 | this._boundListener = this._didScroll.bind(this); 662 | this.element.addEventListener('scroll', this._boundListener); 663 | } 664 | 665 | /** 666 | * Parses the options 667 | * 668 | * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop 669 | * @private 670 | */ 671 | ScrollAnimationLoop_createClass(ScrollAnimationLoop, [{ 672 | key: "_parseOptions", 673 | value: function _parseOptions(options) { 674 | var defaults = new DefaultOptions().scroll; 675 | if (typeof options != 'function') { 676 | defaults.callback = function () {}; 677 | defaults = extend_default()(defaults, options); 678 | } else { 679 | defaults.callback = options; 680 | } 681 | this.element = defaults.element; 682 | this.sustain = defaults.sustain; 683 | this.callback = defaults.callback; 684 | this.startCallback = defaults.start; 685 | this.stopCallback = defaults.stop; 686 | this.directionChange = defaults.directionChange; 687 | } 688 | 689 | /** 690 | * Callback when the user scrolled the element 691 | * @private 692 | */ 693 | }, { 694 | key: "_didScroll", 695 | value: function _didScroll() { 696 | var newPosition = this.getPosition(); 697 | if (this.position !== newPosition) { 698 | var newDirection = this.direction; 699 | if (newPosition.x !== this.position.x) { 700 | newDirection = newPosition.x > this.position.x ? 'right' : 'left'; 701 | } else if (newPosition.y !== this.position.y) { 702 | newDirection = newPosition.y > this.position.y ? 'bottom' : 'top'; 703 | } else { 704 | newDirection = 'none'; 705 | } 706 | if (newDirection !== this.direction) { 707 | this.direction = newDirection; 708 | if (typeof this.directionChange === 'function') { 709 | this.directionChange(this.direction); 710 | } 711 | } 712 | this.position = newPosition; 713 | this.lastAction = this._getTimestamp(); 714 | } else { 715 | this.direction = 'none'; 716 | } 717 | if (!this.running) { 718 | this._startRun(); 719 | } 720 | } 721 | 722 | /** 723 | * Starts the loop, calls the start callback 724 | * @private 725 | */ 726 | }, { 727 | key: "_startRun", 728 | value: function _startRun() { 729 | this.running = true; 730 | if (typeof this.startCallback === 'function') { 731 | this.startCallback(); 732 | } 733 | this._loop(); 734 | } 735 | 736 | /** 737 | * Stops the loop, calls the stop callback 738 | * @private 739 | */ 740 | }, { 741 | key: "_stopRun", 742 | value: function _stopRun() { 743 | this.running = false; 744 | if (typeof this.stopCallback === 'function') { 745 | this.stopCallback(); 746 | } 747 | } 748 | 749 | /** 750 | * The current position of the element 751 | * @returns {{x: number, y: number}} 752 | */ 753 | }, { 754 | key: "getPosition", 755 | value: function getPosition() { 756 | var left = this.element.pageXOffset || this.element.scrollLeft || document.documentElement.scrollLeft || 0; 757 | var top = this.element.pageYOffset || this.element.scrollTop || document.documentElement.scrollTop || 0; 758 | return { 759 | x: left, 760 | y: top 761 | }; 762 | } 763 | 764 | /** 765 | * The current timestamp in ms 766 | * @returns {number} 767 | * @private 768 | */ 769 | }, { 770 | key: "_getTimestamp", 771 | value: function _getTimestamp() { 772 | return Number(Date.now()); 773 | } 774 | 775 | /** 776 | * One single tick of the animation 777 | * @private 778 | */ 779 | }, { 780 | key: "_tick", 781 | value: function _tick() { 782 | this.callback(this.position, this.direction); 783 | var now = this._getTimestamp(); 784 | if (now - this.lastAction > this.sustain) { 785 | this._stopRun(); 786 | } 787 | if (this.running) { 788 | this._loop(); 789 | } 790 | } 791 | 792 | /** 793 | * Requests an animation frame 794 | * @private 795 | */ 796 | }, { 797 | key: "_loop", 798 | value: function _loop() { 799 | var frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) { 800 | setTimeout(callback, 1000 / 60); 801 | }; 802 | frame(this._tick.bind(this)); 803 | } 804 | 805 | /** 806 | * Kills the loop forever 807 | */ 808 | }, { 809 | key: "kill", 810 | value: function kill() { 811 | this.running = false; 812 | this.element.removeEventListener('scroll', this._boundListener); 813 | } 814 | }]); 815 | return ScrollAnimationLoop; 816 | }(); 817 | 818 | ;// CONCATENATED MODULE: ./src/ScrollTrigger.js 819 | function ScrollTrigger_typeof(obj) { "@babel/helpers - typeof"; return ScrollTrigger_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, ScrollTrigger_typeof(obj); } 820 | function ScrollTrigger_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 821 | function ScrollTrigger_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, ScrollTrigger_toPropertyKey(descriptor.key), descriptor); } } 822 | function ScrollTrigger_createClass(Constructor, protoProps, staticProps) { if (protoProps) ScrollTrigger_defineProperties(Constructor.prototype, protoProps); if (staticProps) ScrollTrigger_defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 823 | function ScrollTrigger_toPropertyKey(arg) { var key = ScrollTrigger_toPrimitive(arg, "string"); return ScrollTrigger_typeof(key) === "symbol" ? key : String(key); } 824 | function ScrollTrigger_toPrimitive(input, hint) { if (ScrollTrigger_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (ScrollTrigger_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } 825 | /*! 826 | * ScrollTrigger 827 | * 828 | * 829 | * http://github.com/terwanerik 830 | * 831 | * Copyright 2017, Erik Terwan 832 | * Released under the MIT license. 833 | * 834 | * Date: 2017-07-09 835 | */ 836 | 837 | /** 838 | * Created by Erik on 09/07/2017. 839 | */ 840 | 841 | 842 | 843 | 844 | 845 | 846 | var ScrollTrigger_Trigger = Trigger; 847 | var ScrollTrigger_TriggerCollection = TriggerCollection; 848 | var ScrollTrigger_ScrollAnimationLoop = ScrollAnimationLoop; 849 | var ScrollTrigger = /*#__PURE__*/function () { 850 | /** 851 | * Constructor for the scroll trigger 852 | * @param {DefaultOptions} [options=DefaultOptions] options 853 | */ 854 | function ScrollTrigger(options) { 855 | ScrollTrigger_classCallCheck(this, ScrollTrigger); 856 | this._parseOptions(options); 857 | this._initCollection(); 858 | this._initLoop(); 859 | } 860 | 861 | /** 862 | * Parses the options 863 | * @param {DefaultOptions} [options=DefaultOptions] options 864 | * @private 865 | */ 866 | ScrollTrigger_createClass(ScrollTrigger, [{ 867 | key: "_parseOptions", 868 | value: function _parseOptions(options) { 869 | options = extend_default()(new DefaultOptions(), options); 870 | this.defaultTrigger = options.trigger; 871 | this.scrollOptions = options.scroll; 872 | } 873 | 874 | /** 875 | * Initializes the collection, picks all [data-scroll] elements as initial elements 876 | * @private 877 | */ 878 | }, { 879 | key: "_initCollection", 880 | value: function _initCollection() { 881 | var scrollAttributes = document.querySelectorAll('[data-scroll]'); 882 | var elements = []; 883 | if (scrollAttributes.length > 0) { 884 | elements = this.createTriggers(scrollAttributes); 885 | } 886 | this.collection = new ScrollTrigger_TriggerCollection(elements); 887 | } 888 | 889 | /** 890 | * Initializes the scroll loop 891 | * @private 892 | */ 893 | }, { 894 | key: "_initLoop", 895 | value: function _initLoop() { 896 | var _this = this; 897 | this.loop = new ScrollTrigger_ScrollAnimationLoop({ 898 | sustain: this.scrollOptions.sustain, 899 | element: this.scrollOptions.element, 900 | callback: function callback(position, direction) { 901 | _this._scrollCallback(position, direction); 902 | }, 903 | start: function start() { 904 | _this._scrollStart(); 905 | }, 906 | stop: function stop() { 907 | _this._scrollStop(); 908 | }, 909 | directionChange: function directionChange(direction) { 910 | _this._scrollDirectionChange(direction); 911 | } 912 | }); 913 | } 914 | 915 | /** 916 | * Callback for checking triggers 917 | * @param {{x: number, y: number}} position 918 | * @param {string} direction 919 | * @private 920 | */ 921 | }, { 922 | key: "_scrollCallback", 923 | value: function _scrollCallback(position, direction) { 924 | var _this2 = this; 925 | this.collection.call(function (trigger) { 926 | trigger.checkVisibility(_this2.scrollOptions.element, direction); 927 | }); 928 | this.scrollOptions.callback(position, direction); 929 | } 930 | 931 | /** 932 | * When the scrolling started 933 | * @private 934 | */ 935 | }, { 936 | key: "_scrollStart", 937 | value: function _scrollStart() { 938 | this.scrollOptions.start(); 939 | } 940 | 941 | /** 942 | * When the scrolling stopped 943 | * @private 944 | */ 945 | }, { 946 | key: "_scrollStop", 947 | value: function _scrollStop() { 948 | this.scrollOptions.stop(); 949 | } 950 | 951 | /** 952 | * When the direction changes 953 | * @param {string} direction 954 | * @private 955 | */ 956 | }, { 957 | key: "_scrollDirectionChange", 958 | value: function _scrollDirectionChange(direction) { 959 | this.scrollOptions.directionChange(direction); 960 | } 961 | 962 | /** 963 | * Creates a Trigger object from a given element and optional option set 964 | * @param {HTMLElement} element 965 | * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options 966 | * @returns Trigger 967 | */ 968 | }, { 969 | key: "createTrigger", 970 | value: function createTrigger(element, options) { 971 | return new ScrollTrigger_Trigger(element, extend_default()(this.defaultTrigger, options)); 972 | } 973 | 974 | /** 975 | * Creates an array of triggers 976 | * @param {HTMLElement[]|NodeList} elements 977 | * @param {Object} [options=null] options 978 | * @returns {Trigger[]} Array of triggers 979 | */ 980 | }, { 981 | key: "createTriggers", 982 | value: function createTriggers(elements, options) { 983 | var _this3 = this; 984 | var triggers = []; 985 | elements.each(function (element) { 986 | triggers.push(_this3.createTrigger(element, options)); 987 | }); 988 | return triggers; 989 | } 990 | 991 | /** 992 | * Adds triggers 993 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 994 | * @param {Object} [options=null] options 995 | * @returns {ScrollTrigger} 996 | */ 997 | }, { 998 | key: "add", 999 | value: function add(objects, options) { 1000 | if (objects instanceof HTMLElement) { 1001 | this.collection.add(this.createTrigger(objects, options)); 1002 | return this; 1003 | } 1004 | if (objects instanceof ScrollTrigger_Trigger) { 1005 | this.collection.add(objects); 1006 | return this; 1007 | } 1008 | if (objects instanceof NodeList) { 1009 | this.collection.add(this.createTriggers(objects, options)); 1010 | return this; 1011 | } 1012 | if (Array.isArray(objects) && objects.length && objects[0] instanceof ScrollTrigger_Trigger) { 1013 | this.collection.add(objects); 1014 | return this; 1015 | } 1016 | if (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) { 1017 | this.collection.add(this.createTriggers(objects, options)); 1018 | return this; 1019 | } 1020 | 1021 | // assume it's a query string 1022 | this.collection.add(this.createTriggers(document.querySelectorAll(objects), options)); 1023 | return this; 1024 | } 1025 | 1026 | /** 1027 | * Removes triggers 1028 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 1029 | * @returns {ScrollTrigger} 1030 | */ 1031 | }, { 1032 | key: "remove", 1033 | value: function remove(objects) { 1034 | if (objects instanceof ScrollTrigger_Trigger) { 1035 | this.collection.remove(objects); 1036 | return this; 1037 | } 1038 | if (Array.isArray(objects) && objects.length && objects[0] instanceof ScrollTrigger_Trigger) { 1039 | this.collection.remove(objects); 1040 | return this; 1041 | } 1042 | if (objects instanceof HTMLElement) { 1043 | this.collection.remove(this.search(objects)); 1044 | return this; 1045 | } 1046 | if (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) { 1047 | this.collection.remove(this.search(objects)); 1048 | return this; 1049 | } 1050 | if (objects instanceof NodeList) { 1051 | this.collection.remove(this.search(objects)); 1052 | return this; 1053 | } 1054 | if (Array.isArray(objects) && objects.length && objects[0] instanceof ScrollTrigger_Trigger) { 1055 | this.collection.remove(objects); 1056 | return this; 1057 | } 1058 | 1059 | // assume it's a query string 1060 | this.collection.remove(this.query(objects.toString())); 1061 | return this; 1062 | } 1063 | 1064 | /** 1065 | * Lookup one or multiple triggers by a query string 1066 | * @param {string} selector 1067 | * @returns {Trigger[]} 1068 | */ 1069 | }, { 1070 | key: "query", 1071 | value: function query(selector) { 1072 | return this.collection.query(selector); 1073 | } 1074 | 1075 | /** 1076 | * Lookup one or multiple triggers by a certain HTMLElement or NodeList 1077 | * @param {HTMLElement|HTMLElement[]|NodeList} element 1078 | * @returns {Trigger|Trigger[]|null} 1079 | */ 1080 | }, { 1081 | key: "search", 1082 | value: function search(element) { 1083 | return this.collection.search(element); 1084 | } 1085 | 1086 | /** 1087 | * Reattaches the scroll listener 1088 | */ 1089 | }, { 1090 | key: "listen", 1091 | value: function listen() { 1092 | if (this.loop) { 1093 | return; 1094 | } 1095 | this._initLoop(); 1096 | } 1097 | 1098 | /** 1099 | * Kills the scroll listener 1100 | */ 1101 | }, { 1102 | key: "kill", 1103 | value: function kill() { 1104 | this.loop.kill(); 1105 | this.loop = null; 1106 | } 1107 | }]); 1108 | return ScrollTrigger; 1109 | }(); 1110 | 1111 | })(); 1112 | 1113 | /******/ return __webpack_exports__; 1114 | /******/ })() 1115 | ; 1116 | }); -------------------------------------------------------------------------------- /dist/ScrollTrigger.min.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see ScrollTrigger.min.js.LICENSE.txt */ 2 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("ScrollTrigger",[],e):"object"==typeof exports?exports.ScrollTrigger=e():t.ScrollTrigger=e()}(self,(()=>(()=>{var t={91:()=>{Array.prototype.each=function(t){for(var e=this.length,i=0;i{t.exports=function t(e,i){return null==e||null==i||Object.keys(i).forEach((function(n){"[object Object]"==Object.prototype.toString.call(i[n])?"[object Object]"!=Object.prototype.toString.call(e[n])?e[n]=i[n]:e[n]=t(e[n],i[n]):e[n]=i[n]})),e}}},e={};function i(n){var o=e[n];if(void 0!==o)return o.exports;var r=e[n]={exports:{}};return t[n](r,r.exports,i),r.exports}i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),i.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};return(()=>{"use strict";function t(){this.trigger={once:!1,offset:{viewport:{x:0,y:0},element:{x:0,y:0}},toggle:{class:{in:"visible",out:"invisible"},callback:{in:null,visible:null,out:null}}},this.scroll={sustain:300,element:window,callback:function(){},start:function(){},stop:function(){},directionChange:function(){}}}i.r(n),i.d(n,{ScrollAnimationLoop:()=>b,Trigger:()=>v,TriggerCollection:()=>m,default:()=>d});var e=i(160),o=i.n(e);function r(t){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r(t)}function s(t,e){for(var i=0;ie.w-n.x&&(r=!1),t.top-o.y<-(t.height-n.y)&&(r=!1),t.top+o.y>e.h-n.y&&(r=!1),r}},{key:"_toggleClass",value:function(){var t=this;if(this.visible)return Array.isArray(this.toggle.class.in)?this.toggle.class.in.each((function(e){t.element.classList.add(e)})):this.element.classList.add(this.toggle.class.in),void(Array.isArray(this.toggle.class.out)?this.toggle.class.out.each((function(e){t.element.classList.remove(e)})):this.element.classList.remove(this.toggle.class.out));Array.isArray(this.toggle.class.in)?this.toggle.class.in.each((function(e){t.element.classList.remove(e)})):this.element.classList.remove(this.toggle.class.in),Array.isArray(this.toggle.class.out)?this.toggle.class.out.each((function(e){t.element.classList.add(e)})):this.element.classList.add(this.toggle.class.out)}},{key:"_toggleCallback",value:function(){if(this.visible){if("function"==typeof this.toggle.callback.in)return this.toggle.callback.in.call(this.element,this)}else if("function"==typeof this.toggle.callback.out)return this.toggle.callback.out.call(this.element,this)}}])&&s(i.prototype,n),Object.defineProperty(i,"prototype",{writable:!1}),e}();function u(t){return u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},u(t)}function f(t,e){for(var i=0;i-1}))}},{key:"search",value:function(t){var e=this.triggers.filter((function(e){if(t instanceof NodeList||Array.isArray(t)){var i=!1;return t.each((function(t){e.element==t&&(i=!0)})),i}return e.element==t}));return 0==e.length?null:e.length>1?e:e[0]}},{key:"call",value:function(t){this.triggers.each(t)}}])&&f(e.prototype,i),Object.defineProperty(e,"prototype",{writable:!1}),t}(),b=function(){function e(t,i){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),this._parseOptions(t),"function"==typeof i&&(this.callback=i),this.direction="none",this.position=this.getPosition(),this.lastAction=this._getTimestamp(),this._startRun(),this._boundListener=this._didScroll.bind(this),this.element.addEventListener("scroll",this._boundListener)}var i,n;return i=e,(n=[{key:"_parseOptions",value:function(e){var i=(new t).scroll;"function"!=typeof e?(i.callback=function(){},i=o()(i,e)):i.callback=e,this.element=i.element,this.sustain=i.sustain,this.callback=i.callback,this.startCallback=i.start,this.stopCallback=i.stop,this.directionChange=i.directionChange}},{key:"_didScroll",value:function(){var t=this.getPosition();if(this.position!==t){var e=this.direction;(e=t.x!==this.position.x?t.x>this.position.x?"right":"left":t.y!==this.position.y?t.y>this.position.y?"bottom":"top":"none")!==this.direction&&(this.direction=e,"function"==typeof this.directionChange&&this.directionChange(this.direction)),this.position=t,this.lastAction=this._getTimestamp()}else this.direction="none";this.running||this._startRun()}},{key:"_startRun",value:function(){this.running=!0,"function"==typeof this.startCallback&&this.startCallback(),this._loop()}},{key:"_stopRun",value:function(){this.running=!1,"function"==typeof this.stopCallback&&this.stopCallback()}},{key:"getPosition",value:function(){return{x:this.element.pageXOffset||this.element.scrollLeft||document.documentElement.scrollLeft||0,y:this.element.pageYOffset||this.element.scrollTop||document.documentElement.scrollTop||0}}},{key:"_getTimestamp",value:function(){return Number(Date.now())}},{key:"_tick",value:function(){this.callback(this.position,this.direction),this._getTimestamp()-this.lastAction>this.sustain&&this._stopRun(),this.running&&this._loop()}},{key:"_loop",value:function(){(window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||function(t){setTimeout(t,1e3/60)})(this._tick.bind(this))}},{key:"kill",value:function(){this.running=!1,this.element.removeEventListener("scroll",this._boundListener)}}])&&y(i.prototype,n),Object.defineProperty(i,"prototype",{writable:!1}),e}(),d=function(){function e(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),this._parseOptions(t),this._initCollection(),this._initLoop()}var i,n;return i=e,(n=[{key:"_parseOptions",value:function(e){e=o()(new t,e),this.defaultTrigger=e.trigger,this.scrollOptions=e.scroll}},{key:"_initCollection",value:function(){var t=document.querySelectorAll("[data-scroll]"),e=[];t.length>0&&(e=this.createTriggers(t)),this.collection=new m(e)}},{key:"_initLoop",value:function(){var t=this;this.loop=new b({sustain:this.scrollOptions.sustain,element:this.scrollOptions.element,callback:function(e,i){t._scrollCallback(e,i)},start:function(){t._scrollStart()},stop:function(){t._scrollStop()},directionChange:function(e){t._scrollDirectionChange(e)}})}},{key:"_scrollCallback",value:function(t,e){var i=this;this.collection.call((function(t){t.checkVisibility(i.scrollOptions.element,e)})),this.scrollOptions.callback(t,e)}},{key:"_scrollStart",value:function(){this.scrollOptions.start()}},{key:"_scrollStop",value:function(){this.scrollOptions.stop()}},{key:"_scrollDirectionChange",value:function(t){this.scrollOptions.directionChange(t)}},{key:"createTrigger",value:function(t,e){return new v(t,o()(this.defaultTrigger,e))}},{key:"createTriggers",value:function(t,e){var i=this,n=[];return t.each((function(t){n.push(i.createTrigger(t,e))})),n}},{key:"add",value:function(t,e){return t instanceof HTMLElement?(this.collection.add(this.createTrigger(t,e)),this):t instanceof v?(this.collection.add(t),this):t instanceof NodeList?(this.collection.add(this.createTriggers(t,e)),this):Array.isArray(t)&&t.length&&t[0]instanceof v?(this.collection.add(t),this):Array.isArray(t)&&t.length&&t[0]instanceof HTMLElement?(this.collection.add(this.createTriggers(t,e)),this):(this.collection.add(this.createTriggers(document.querySelectorAll(t),e)),this)}},{key:"remove",value:function(t){return t instanceof v||Array.isArray(t)&&t.length&&t[0]instanceof v?(this.collection.remove(t),this):t instanceof HTMLElement||Array.isArray(t)&&t.length&&t[0]instanceof HTMLElement||t instanceof NodeList?(this.collection.remove(this.search(t)),this):Array.isArray(t)&&t.length&&t[0]instanceof v?(this.collection.remove(t),this):(this.collection.remove(this.query(t.toString())),this)}},{key:"query",value:function(t){return this.collection.query(t)}},{key:"search",value:function(t){return this.collection.search(t)}},{key:"listen",value:function(){this.loop||this._initLoop()}},{key:"kill",value:function(){this.loop.kill(),this.loop=null}}])&&g(i.prototype,n),Object.defineProperty(i,"prototype",{writable:!1}),e}()})(),n})())); 3 | //# sourceMappingURL=ScrollTrigger.min.js.map -------------------------------------------------------------------------------- /dist/ScrollTrigger.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * ScrollTrigger 3 | * 4 | * 5 | * http://github.com/terwanerik 6 | * 7 | * Copyright 2017, Erik Terwan 8 | * Released under the MIT license. 9 | * 10 | * Date: 2017-07-09 11 | */ 12 | 13 | /*! 14 | * object-extend 15 | * A well-tested function to deep extend (or merge) JavaScript objects without further dependencies. 16 | * 17 | * http://github.com/bernhardw 18 | * 19 | * Copyright 2013, Bernhard Wanger 20 | * Released under the MIT license. 21 | * 22 | * Date: 2013-04-10 23 | */ 24 | -------------------------------------------------------------------------------- /dist/ScrollTrigger.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ScrollTrigger.min.js","mappings":"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","sources":["webpack://ScrollTrigger/webpack/universalModuleDefinition","webpack://ScrollTrigger/./src/extensions/Array.js","webpack://ScrollTrigger/./node_modules/object-extend/lib/extend.js","webpack://ScrollTrigger/webpack/bootstrap","webpack://ScrollTrigger/webpack/runtime/compat get default export","webpack://ScrollTrigger/webpack/runtime/define property getters","webpack://ScrollTrigger/webpack/runtime/hasOwnProperty shorthand","webpack://ScrollTrigger/webpack/runtime/make namespace object","webpack://ScrollTrigger/./src/config/DefaultOptions.js","webpack://ScrollTrigger/./src/scripts/Trigger.js","webpack://ScrollTrigger/./src/ScrollTrigger.js","webpack://ScrollTrigger/./src/scripts/TriggerCollection.js","webpack://ScrollTrigger/./src/scripts/ScrollAnimationLoop.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"ScrollTrigger\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ScrollTrigger\"] = factory();\n\telse\n\t\troot[\"ScrollTrigger\"] = factory();\n})(self, () => {\nreturn ","/**\n * Faster than .forEach\n * @param {(function())} fn The function to call\n */\nArray.prototype.each = function (fn) {\n\tconst l = this.length\n\n\tfor(let i = 0; i < l; i++) {\n\t\tconst e = this[i]\n\n\t\tif (e) {\n\t\t\tfn(e,i)\n\t\t}\n\t}\n}\n\n/**\n * Give NodeList some Array functions\n */\nNodeList.prototype.each = Array.prototype.each\nNodeList.prototype.filter = Array.prototype.filter\n","/*!\n * object-extend\n * A well-tested function to deep extend (or merge) JavaScript objects without further dependencies.\n *\n * http://github.com/bernhardw\n *\n * Copyright 2013, Bernhard Wanger \n * Released under the MIT license.\n *\n * Date: 2013-04-10\n */\n\n\n/**\n * Extend object a with object b.\n *\n * @param {Object} a Source object.\n * @param {Object} b Object to extend with.\n * @returns {Object} a Extended object.\n */\nmodule.exports = function extend(a, b) {\n\n // Don't touch 'null' or 'undefined' objects.\n if (a == null || b == null) {\n return a;\n }\n\n // TODO: Refactor to use for-loop for performance reasons.\n Object.keys(b).forEach(function (key) {\n\n // Detect object without array, date or null.\n // TODO: Performance test:\n // a) b.constructor === Object.prototype.constructor\n // b) Object.prototype.toString.call(b) == '[object Object]'\n if (Object.prototype.toString.call(b[key]) == '[object Object]') {\n if (Object.prototype.toString.call(a[key]) != '[object Object]') {\n a[key] = b[key];\n } else {\n a[key] = extend(a[key], b[key]);\n }\n } else {\n a[key] = b[key];\n }\n\n });\n\n return a;\n\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/**\n * Default options for ScrollTrigger\n */\nexport default function() {\n\t/**\n\t * The default options for a trigger\n\t *\n\t * @type {\n\t * {\n\t * once: boolean,\n\t * offset: {\n\t * viewport: {\n\t * x: number|(function(frame, direction)),\n\t * y: number|(function(frame, direction))\n\t * },\n\t * element: {\n\t * x: number|(function(rect, direction)),\n\t * y: number|(function(rect, direction))\n\t * }\n\t * },\n\t * toggle: {\n\t * class: {\n\t * in: string|string[],\n\t * out: string|string[]\n\t * },\n\t * callback: {\n\t * in: {TriggerInCallback},\n * visible: (function()),\n\t * out: (function())\n\t * }\n\t * }\n\t * }}\n\t */\n\tthis.trigger = {\n\t\tonce: false,\n\t\toffset: {\n\t\t\tviewport: {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0\n\t\t\t},\n\t\t\telement: {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0\n\t\t\t}\n\t\t},\n\t\ttoggle: {\n\t\t\tclass: {\n\t\t\t\tin: 'visible',\n\t\t\t\tout: 'invisible'\n\t\t\t},\n\t\t\tcallback: {\n\t\t\t\tin: null,\n \t\tvisible: null,\n\t\t\t\tout: null\n\t\t\t}\n\t\t}\n\t}\n\n /**\n * The `in` callback is called when the element enters the viewport\n * @callback TriggerInCallback\n * @param {{x: Number, y: Number}} position\n * @param {string} direction\n */\n\n\t/**\n\t * The default options for the scroll behaviour\n\t * @type {\n\t * {\n\t * sustain: number,\n\t * element: Window|HTMLDocument|HTMLElement,\n\t * callback: {ScrollCallback},\n\t * start: (function()),\n\t * stop: (function()),\n\t * directionChange: (function(direction: {string}))\n\t * }\n\t * }\n\t */\n\tthis.scroll = {\n\t\tsustain: 300,\n\t\telement: window,\n\t\tcallback: () => {},\n\t\tstart: () => {},\n\t\tstop: () => {},\n\t\tdirectionChange: () => {}\n\t}\n\n /**\n * The scroll callback is called when the user scrolls\n * @callback ScrollCallback\n * @param {{x: Number, y: Number}} position\n * @param {string} direction\n */\n}\n","import DefaultOptions from '../config/DefaultOptions'\nimport extend from 'object-extend'\nimport '../extensions/Array'\n\nfunction isInt(n) {\n\treturn Number(n) === n && n % 1 === 0\n}\n\nfunction isFloat(n) {\n\treturn Number(n) === n && n % 1 !== 0\n}\n\nexport default class Trigger {\n\t/**\n\t * Creates a new Trigger from the given element and options\n\t *\n\t * @param {Element|HTMLElement} element\n\t * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options\n\t */\n\tconstructor(element, options) {\n\t\tthis.element = element\n\n\t\toptions = extend(new DefaultOptions().trigger, options)\n\n\t\tthis.offset = options.offset\n\t\tthis.toggle = options.toggle\n\t\tthis.once = options.once\n\t\tthis.visible = null\n\t\tthis.active = true\n\t}\n\n\t/**\n\t * Checks if the Trigger is in the viewport, calls the callbacks and toggles the classes\n\t * @param {HTMLElement|HTMLDocument|Window} parent\n\t * @param {string} direction top, bottom, left, right\n\t * @returns {boolean} If the element is visible\n\t */\n\tcheckVisibility(parent, direction) {\n\t\tif (!this.active) {\n\t\t\treturn this.visible\n\t\t}\n\n\t\tconst parentWidth = parent.offsetWidth || parent.innerWidth || 0\n\t\tconst parentHeight = parent.offsetHeight || parent.innerHeight || 0\n\n\t\tconst parentFrame = { w: parentWidth, h: parentHeight }\n\t\tconst rect = this.getBounds()\n\n\t\tconst visible = this._checkVisibility(rect, parentFrame, direction)\n\n\t\tif (visible !== this.visible) {\n\t\t\tthis.visible = visible\n\n\t\t\tconst response = this._toggleCallback()\n\n\t\t\tif (response instanceof Promise) {\n\t\t\t\t\tresponse.then(this._toggleClass.bind(this)).catch(e => {\n\t\t\t\t\t\tconsole.error('Trigger promise failed')\n\t\t\t\t\t\tconsole.error(e)\n\t\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t\tthis._toggleClass()\n\t\t\t}\n\n\t\t\tif (this.visible && this.once) {\n\t\t\t\tthis.active = false\n\t\t\t}\n\t\t} else if (visible) {\n\t\t\t\tif (typeof this.toggle.callback.visible == 'function') {\n\t\t\t\t\t\treturn this.toggle.callback.visible.call(this.element, this)\n\t\t\t\t}\n\t\t}\n\n\t\treturn visible\n\t}\n\n\t/**\n\t * Get the bounds of this element\n\t * @return {ClientRect | DOMRect}\n\t */\n\tgetBounds() {\n \treturn this.element.getBoundingClientRect()\n\t}\n\n\t/**\n\t * Get the calculated offset to place on the element\n\t * @param {ClientRect} rect\n\t * @param {string} direction top, bottom, left, right\n\t * @returns {{x: number, y: number}}\n\t * @private\n\t */\n\t_getElementOffset(rect, direction) {\n\t\tlet offset = { x: 0, y: 0 }\n\n\t\tif (typeof this.offset.element.x === 'function') {\n\t\t\toffset.x = rect.width * this.offset.element.x(this, rect, direction)\n\t\t} else if (isFloat(this.offset.element.x)) {\n\t\t\toffset.x = rect.width * this.offset.element.x\n\t\t} else if (isInt(this.offset.element.x)) {\n\t\t\toffset.x = this.offset.element.x\n\t\t}\n\n\t\tif (typeof this.offset.element.y === 'function') {\n\t\t\toffset.y = rect.height * this.offset.element.y(this, rect, direction)\n\t\t} else if (isFloat(this.offset.element.y)) {\n\t\t\toffset.y = rect.height * this.offset.element.y\n\t\t} else if (isInt(this.offset.element.y)) {\n\t\t\toffset.y = this.offset.element.y\n\t\t}\n\n\t\treturn offset\n\t}\n\n\t/**\n\t * Get the calculated offset to place on the viewport\n\t * @param {{w: number, h: number}} parent\n\t * @param {string} direction top, bottom, left, right\n\t * @returns {{x: number, y: number}}\n\t * @private\n\t */\n\t_getViewportOffset(parent, direction) {\n\t\tlet offset = { x: 0, y: 0 }\n\n\t\tif (typeof this.offset.viewport.x === 'function') {\n\t\t\toffset.x = parent.w * this.offset.viewport.x(this, parent, direction)\n\t\t} else if (isFloat(this.offset.viewport.x)) {\n\t\t\toffset.x = parent.w * this.offset.viewport.x\n\t\t} else if (isInt(this.offset.viewport.x)) {\n\t\t\toffset.x = this.offset.viewport.x\n\t\t}\n\n\t\tif (typeof this.offset.viewport.y === 'function') {\n\t\t\toffset.y = parent.h * this.offset.viewport.y(this, parent, direction)\n\t\t} else if (isFloat(this.offset.viewport.y)) {\n\t\t\toffset.y = parent.h * this.offset.viewport.y\n\t\t} else if (isInt(this.offset.viewport.y)) {\n\t\t\toffset.y = this.offset.viewport.y\n\t\t}\n\n\t\treturn offset\n\t}\n\n\t/**\n\t * Check the visibility of the trigger in the viewport, with offsets applied\n\t * @param {ClientRect} rect\n\t * @param {{w: number, h: number}} parent\n\t * @param {string} direction top, bottom, left, right\n\t * @returns {boolean}\n\t * @private\n\t */\n\t _checkVisibility(rect, parent, direction) {\n\t\tconst elementOffset = this._getElementOffset(rect, direction)\n\t\tconst viewportOffset = this._getViewportOffset(parent, direction)\n\n\t\tlet visible = true\n\n\t\tif ((rect.left - viewportOffset.x) < -(rect.width - elementOffset.x)) {\n\t\t\tvisible = false\n\t\t}\n\n\t\tif ((rect.left + viewportOffset.x) > (parent.w - elementOffset.x)) {\n\t\t\tvisible = false\n\t\t}\n\n\t\tif ((rect.top - viewportOffset.y) < -(rect.height - elementOffset.y)) {\n\t\t\tvisible = false\n\t\t}\n\n\t\tif ((rect.top + viewportOffset.y) > (parent.h - elementOffset.y)) {\n\t\t\tvisible = false\n\t\t}\n\n\t\treturn visible\n\t}\n\n\t/**\n\t * Toggles the classes\n\t * @private\n\t */\n\t_toggleClass() {\n\t\tif (this.visible) {\n\t\t\tif (Array.isArray(this.toggle.class.in)) {\n\t\t\t\tthis.toggle.class.in.each((className) => {\n\t\t\t\t\tthis.element.classList.add(className)\n\t\t\t\t})\n\t\t\t} else {\n \tthis.element.classList.add(this.toggle.class.in)\n }\n\n if (Array.isArray(this.toggle.class.out)) {\n \tthis.toggle.class.out.each((className) => {\n \tthis.element.classList.remove(className)\n })\n } else {\n \tthis.element.classList.remove(this.toggle.class.out)\n }\n\n return\n\t\t}\n\n\t\tif (Array.isArray(this.toggle.class.in)) {\n\t\t\tthis.toggle.class.in.each((className) => {\n\t\t\t\tthis.element.classList.remove(className)\n\t\t\t})\n\t\t} else {\n\t\t\tthis.element.classList.remove(this.toggle.class.in)\n }\n\n\t\tif (Array.isArray(this.toggle.class.out)) {\n\t\t\tthis.toggle.class.out.each((className) => {\n\t\t\t\tthis.element.classList.add(className)\n\t\t\t})\n\t\t} else {\n \tthis.element.classList.add(this.toggle.class.out)\n }\n\t}\n\n\t/**\n\t * Toggles the callback\n\t * @private\n\t * @return null|Promise\n\t */\n\t_toggleCallback() {\n\t\tif (this.visible) {\n\t\t\tif (typeof this.toggle.callback.in == 'function') {\n\t\t\t\treturn this.toggle.callback.in.call(this.element, this)\n\t\t\t}\n\t\t} else {\n\t\t\tif (typeof this.toggle.callback.out == 'function') {\n\t\t\t\treturn this.toggle.callback.out.call(this.element, this)\n\t\t\t}\n\t\t}\n\t}\n}\n","/*!\n * ScrollTrigger\n *\n *\n * http://github.com/terwanerik\n *\n * Copyright 2017, Erik Terwan \n * Released under the MIT license.\n *\n * Date: 2017-07-09\n */\n\n/**\n * Created by Erik on 09/07/2017.\n */\nimport DefaultOptions from './config/DefaultOptions'\nimport _Trigger from './scripts/Trigger'\nimport _TriggerCollection from './scripts/TriggerCollection'\nimport _ScrollAnimationLoop from './scripts/ScrollAnimationLoop'\n\nimport extend from 'object-extend'\nimport './extensions/Array'\n\nexport const Trigger = _Trigger\nexport const TriggerCollection = _TriggerCollection\nexport const ScrollAnimationLoop = _ScrollAnimationLoop\n\nexport default class ScrollTrigger {\n\t/**\n\t * Constructor for the scroll trigger\n\t * @param {DefaultOptions} [options=DefaultOptions] options\n\t */\n\tconstructor(options) {\n\t\tthis._parseOptions(options)\n\t\tthis._initCollection()\n\t\tthis._initLoop()\n\t}\n\n\t/**\n\t * Parses the options\n\t * @param {DefaultOptions} [options=DefaultOptions] options\n\t * @private\n\t */\n\t_parseOptions(options) {\n\t\toptions = extend(new DefaultOptions(), options)\n\n\t\tthis.defaultTrigger = options.trigger\n\t\tthis.scrollOptions = options.scroll\n\t}\n\n\t/**\n\t * Initializes the collection, picks all [data-scroll] elements as initial elements\n\t * @private\n\t */\n\t_initCollection() {\n\t\tconst scrollAttributes = document.querySelectorAll('[data-scroll]')\n\t\tlet elements = []\n\n\t\tif (scrollAttributes.length > 0) {\n\t\t\telements = this.createTriggers(scrollAttributes)\n\t\t}\n\n\t\tthis.collection = new TriggerCollection(elements)\n\t}\n\n\t/**\n\t * Initializes the scroll loop\n\t * @private\n\t */\n\t_initLoop() {\n\t\tthis.loop = new ScrollAnimationLoop({\n\t\t\tsustain: this.scrollOptions.sustain,\n\t\t\telement: this.scrollOptions.element,\n\t\t\tcallback: (position, direction) => {\n\t\t\t\tthis._scrollCallback(position, direction)\n\t\t\t},\n\t\t\tstart: () => {\n\t\t\t\tthis._scrollStart()\n\t\t\t},\n\t\t\tstop: () => {\n\t\t\t\tthis._scrollStop()\n\t\t\t},\n\t\t\tdirectionChange: (direction) => {\n\t\t\t\tthis._scrollDirectionChange(direction)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Callback for checking triggers\n\t * @param {{x: number, y: number}} position\n\t * @param {string} direction\n\t * @private\n\t */\n\t_scrollCallback(position, direction) {\n\t\tthis.collection.call((trigger) => {\n\t\t\ttrigger.checkVisibility(this.scrollOptions.element, direction)\n\t\t})\n\n\t\tthis.scrollOptions.callback(position, direction)\n\t}\n\n\t/**\n\t * When the scrolling started\n\t * @private\n\t */\n\t_scrollStart() {\n\t\tthis.scrollOptions.start()\n\t}\n\n\t/**\n\t * When the scrolling stopped\n\t * @private\n\t */\n\t_scrollStop() {\n\t\tthis.scrollOptions.stop()\n\t}\n\n\t/**\n\t * When the direction changes\n\t * @param {string} direction\n\t * @private\n\t */\n\t_scrollDirectionChange(direction) {\n\t\tthis.scrollOptions.directionChange(direction)\n\t}\n\n\t/**\n\t * Creates a Trigger object from a given element and optional option set\n\t * @param {HTMLElement} element\n\t * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options\n\t * @returns Trigger\n\t */\n\tcreateTrigger(element, options) {\n\t\treturn new Trigger(element, extend(this.defaultTrigger, options))\n\t}\n\n\t/**\n\t * Creates an array of triggers\n\t * @param {HTMLElement[]|NodeList} elements\n\t * @param {Object} [options=null] options\n\t * @returns {Trigger[]} Array of triggers\n\t */\n\tcreateTriggers(elements, options) {\n\t\tlet triggers = []\n\n\t\telements.each((element) => {\n\t\t\ttriggers.push(this.createTrigger(element, options))\n\t\t})\n\n\t\treturn triggers\n\t}\n\n\t/**\n\t * Adds triggers\n\t * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query\n\t * @param {Object} [options=null] options\n\t * @returns {ScrollTrigger}\n\t */\n\tadd(objects, options) {\n\t\tif (objects instanceof HTMLElement) {\n\t\t\tthis.collection.add(this.createTrigger(objects, options))\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (objects instanceof Trigger) {\n\t\t\tthis.collection.add(objects)\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (objects instanceof NodeList) {\n\t\t\tthis.collection.add(this.createTriggers(objects, options))\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) {\n\t\t\tthis.collection.add(objects)\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) {\n\t\t\tthis.collection.add(this.createTriggers(objects, options))\n\n\t\t\treturn this\n\t\t}\n\n\t\t// assume it's a query string\n\t\tthis.collection.add(this.createTriggers(document.querySelectorAll(objects), options))\n\n\t\treturn this\n\t}\n\n\t/**\n\t * Removes triggers\n\t * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query\n\t * @returns {ScrollTrigger}\n\t */\n\tremove(objects) {\n\t\tif (objects instanceof Trigger) {\n\t\t\tthis.collection.remove(objects)\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) {\n\t\t\tthis.collection.remove(objects)\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (objects instanceof HTMLElement) {\n\t\t\tthis.collection.remove(this.search(objects))\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) {\n\t\t\tthis.collection.remove(this.search(objects))\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (objects instanceof NodeList) {\n\t\t\tthis.collection.remove(this.search(objects))\n\n\t\t\treturn this\n\t\t}\n\n\t\tif (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) {\n\t\t\tthis.collection.remove(objects)\n\n\t\t\treturn this\n\t\t}\n\n\t\t// assume it's a query string\n\t\tthis.collection.remove(this.query(objects.toString()))\n\n\t\treturn this\n\t}\n\n\t/**\n\t * Lookup one or multiple triggers by a query string\n\t * @param {string} selector\n\t * @returns {Trigger[]}\n\t */\n\tquery(selector) {\n\t\treturn this.collection.query(selector)\n\t}\n\n\t/**\n\t * Lookup one or multiple triggers by a certain HTMLElement or NodeList\n\t * @param {HTMLElement|HTMLElement[]|NodeList} element\n\t * @returns {Trigger|Trigger[]|null}\n\t */\n\tsearch(element) {\n\t\treturn this.collection.search(element)\n\t}\n\n\t/**\n\t * Reattaches the scroll listener\n\t */\n\tlisten() {\n\t\tif (this.loop) { return }\n\n\t\tthis._initLoop()\n\t}\n\n\t/**\n\t * Kills the scroll listener\n\t */\n\tkill() {\n\t\tthis.loop.kill()\n\t\tthis.loop = null\n\t}\n}\n","import Trigger from './Trigger'\nimport '../extensions/Array'\n\nexport default class TriggerCollection {\n\t/**\n\t * Initializes the collection\n\t * @param {Trigger[]} [triggers=[]] triggers A set of triggers to init with, optional\n\t */\n\tconstructor(triggers) {\n\t\t/**\n\t\t * @member {Trigger[]}\n\t\t */\n\t\tthis.triggers = triggers instanceof Array ? triggers : []\n\t}\n\n\t/**\n\t * Adds one or multiple Trigger objects\n\t * @param {Trigger|Trigger[]} objects\n\t */\n\tadd(objects) {\n\t\tif (objects instanceof Trigger) {\n\t\t\t// single\n\t\t\treturn this.triggers.push(objects)\n\t\t}\n\n\t\tobjects.each((trigger) => {\n\t\t\tif (trigger instanceof Trigger) {\n\t\t\t\tthis.triggers.push(trigger)\n\t\t\t} else {\n\t\t\t\tconsole.error('Object added to TriggerCollection is not a Trigger. Object: ', trigger)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Removes one or multiple Trigger objects\n\t * @param {Trigger|Trigger[]} objects\n\t */\n\tremove(objects) {\n\t\tif (objects instanceof Trigger) {\n\t\t\tobjects = [objects]\n\t\t}\n\n\t\tthis.triggers = this.triggers.filter((trigger) => {\n\t\t\tlet hit = false\n\n\t\t\tobjects.each((object) => {\n\t\t\t\tif (object == trigger) {\n\t\t\t\t\thit = true\n\t\t\t\t}\n\t\t\t})\n\n\t\t\treturn !hit\n\t\t})\n\t}\n\n\t/**\n\t * Lookup one or multiple triggers by a query string\n\t * @param {string} selector\n\t * @returns {Trigger[]}\n\t */\n\tquery(selector) {\n\t\treturn this.triggers.filter((trigger) => {\n\t\t\tconst element = trigger.element\n\t\t\tconst parent = element.parentNode\n\t\t\tconst nodes = [].slice.call(parent.querySelectorAll(selector))\n\n\t\t\treturn nodes.indexOf(element) > -1\n\t\t})\n\t}\n\n\t/**\n\t * Lookup one or multiple triggers by a certain HTMLElement or NodeList\n\t * @param {HTMLElement|HTMLElement[]|NodeList} element\n\t * @returns {Trigger|Trigger[]|null}\n\t */\n\tsearch(element) {\n\t\tconst found = this.triggers.filter((trigger) => {\n\t\t\tif (element instanceof NodeList || Array.isArray(element)) {\n\t\t\t\tlet hit = false\n\n\t\t\t\telement.each((el) => {\n\t\t\t\t\tif (trigger.element == el) {\n\t\t\t\t\t\thit = true\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\treturn hit\n\t\t\t}\n\n\t\t\treturn trigger.element == element\n\t\t})\n\n\t\treturn found.length == 0 ? null : (found.length > 1 ? found : found[0])\n\t}\n\n\t/**\n\t * Calls a function on all triggers\n\t * @param {(function())} callback\n\t */\n\tcall(callback) {\n\t\tthis.triggers.each(callback)\n\t}\n}\n","import DefaultOptions from '../config/DefaultOptions'\nimport extend from 'object-extend'\nimport '../extensions/Array'\n\nexport default class ScrollAnimationLoop {\n\t/**\n\t * ScrollAnimationLoop constructor.\n\t * Starts a requestAnimationFrame loop as long as the user has scrolled the scrollElement. Stops after a certain time.\n\t *\n\t * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop\n\t * @param {ScrollCallback} callback [loop=null] The loop callback\n\t */\n\tconstructor(options, callback) {\n\t\tthis._parseOptions(options)\n\n\t\tif (typeof callback === 'function') {\n\t\t\tthis.callback = callback\n\t\t}\n\n\t\tthis.direction = 'none'\n\t\tthis.position = this.getPosition()\n\t\tthis.lastAction = this._getTimestamp()\n\n\t\tthis._startRun()\n\n\t\tthis._boundListener = this._didScroll.bind(this)\n\t\tthis.element.addEventListener('scroll', this._boundListener)\n\t}\n\n\t/**\n\t * Parses the options\n\t *\n\t * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop\n\t * @private\n\t */\n\t_parseOptions(options) {\n\t\tlet defaults = new DefaultOptions().scroll\n\n\t\tif (typeof options != 'function') {\n\t\t\tdefaults.callback = () => {}\n\n defaults = extend(defaults, options)\n\t\t} else {\n\t\t\tdefaults.callback = options\n\t\t}\n\n\t\tthis.element = defaults.element\n\t\tthis.sustain = defaults.sustain\n\t\tthis.callback = defaults.callback\n\t\tthis.startCallback = defaults.start\n\t\tthis.stopCallback = defaults.stop\n\t\tthis.directionChange = defaults.directionChange\n\t}\n\n\t/**\n\t * Callback when the user scrolled the element\n\t * @private\n\t */\n\t_didScroll() {\n\t\tconst newPosition = this.getPosition()\n\n\t\tif (this.position !== newPosition) {\n\t\t\tlet newDirection = this.direction\n\n\t\t\tif (newPosition.x !== this.position.x) {\n\t\t\t\tnewDirection = newPosition.x > this.position.x ? 'right' : 'left'\n\t\t\t} else if (newPosition.y !== this.position.y) {\n\t\t\t\tnewDirection = newPosition.y > this.position.y ? 'bottom' : 'top'\n\t\t\t} else {\n\t\t\t\tnewDirection = 'none'\n\t\t\t}\n\n\t\t\tif (newDirection !== this.direction) {\n\t\t\t\tthis.direction = newDirection\n\n if (typeof this.directionChange === 'function') {\n this.directionChange(this.direction)\n }\n\t\t\t}\n\n\t\t\tthis.position = newPosition\n\t\t\tthis.lastAction = this._getTimestamp()\n\t\t} else {\n this.direction = 'none'\n }\n\n\t\tif (!this.running) {\n\t\t\tthis._startRun()\n\t\t}\n\t}\n\n\t/**\n\t * Starts the loop, calls the start callback\n\t * @private\n\t */\n\t_startRun() {\n\t\tthis.running = true\n\n if (typeof this.startCallback === 'function') {\n this.startCallback()\n }\n\n\t\tthis._loop()\n\t}\n\n\t/**\n\t * Stops the loop, calls the stop callback\n\t * @private\n\t */\n\t_stopRun() {\n\t\tthis.running = false\n\n if (typeof this.stopCallback === 'function') {\n this.stopCallback()\n }\n\t}\n\n\t/**\n\t * The current position of the element\n\t * @returns {{x: number, y: number}}\n\t */\n\tgetPosition() {\n\t\tconst left = this.element.pageXOffset || this.element.scrollLeft || document.documentElement.scrollLeft || 0\n\t\tconst top = this.element.pageYOffset || this.element.scrollTop || document.documentElement.scrollTop || 0\n\n\t\treturn { x: left, y: top }\n\t}\n\n\t/**\n\t * The current timestamp in ms\n\t * @returns {number}\n\t * @private\n\t */\n\t_getTimestamp() {\n\t\treturn Number(Date.now())\n\t}\n\n\t/**\n\t * One single tick of the animation\n\t * @private\n\t */\n\t_tick() {\n\t\tthis.callback(this.position, this.direction)\n\n\t\tconst now = this._getTimestamp()\n\n\t\tif (now - this.lastAction > this.sustain) {\n\t\t\tthis._stopRun()\n\t\t}\n\n\t\tif (this.running) {\n\t\t\tthis._loop()\n\t\t}\n\t}\n\n\t/**\n\t * Requests an animation frame\n\t * @private\n\t */\n\t_loop() {\n\t\tconst frame = window.requestAnimationFrame ||\n\t\t\twindow.webkitRequestAnimationFrame ||\n\t\t\twindow.mozRequestAnimationFrame ||\n\t\t\twindow.msRequestAnimationFrame ||\n\t\t\twindow.oRequestAnimationFrame ||\n\t\t\t((callback) => { setTimeout(callback, 1000 / 60) })\n\n\t\tframe(this._tick.bind(this))\n\t}\n\n\t/**\n\t * Kills the loop forever\n\t */\n\tkill() {\n\t\tthis.running = false\n\t\tthis.element.removeEventListener('scroll', this._boundListener)\n\t}\n}\n"],"names":["root","factory","exports","module","define","amd","self","Array","prototype","each","fn","l","this","length","i","e","NodeList","filter","extend","a","b","Object","keys","forEach","key","toString","call","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","n","getter","__esModule","d","definition","o","defineProperty","enumerable","get","obj","prop","hasOwnProperty","r","Symbol","toStringTag","value","trigger","once","offset","viewport","x","y","element","toggle","class","in","out","callback","visible","scroll","sustain","window","start","stop","directionChange","isInt","Number","isFloat","Trigger","options","DefaultOptions","active","parent","direction","parentFrame","w","offsetWidth","innerWidth","h","offsetHeight","innerHeight","rect","getBounds","_checkVisibility","response","_toggleCallback","Promise","then","_toggleClass","bind","console","error","getBoundingClientRect","width","height","elementOffset","_getElementOffset","viewportOffset","_getViewportOffset","left","top","isArray","className","classList","add","remove","_Trigger","TriggerCollection","triggers","objects","push","hit","object","selector","parentNode","slice","querySelectorAll","indexOf","found","el","ScrollAnimationLoop","_parseOptions","position","getPosition","lastAction","_getTimestamp","_startRun","_boundListener","_didScroll","addEventListener","defaults","startCallback","stopCallback","newPosition","newDirection","running","_loop","pageXOffset","scrollLeft","document","documentElement","pageYOffset","scrollTop","Date","now","_stopRun","requestAnimationFrame","webkitRequestAnimationFrame","mozRequestAnimationFrame","msRequestAnimationFrame","oRequestAnimationFrame","setTimeout","_tick","removeEventListener","ScrollTrigger","_initCollection","_initLoop","defaultTrigger","scrollOptions","scrollAttributes","elements","createTriggers","collection","loop","_scrollCallback","_scrollStart","_scrollStop","_scrollDirectionChange","checkVisibility","createTrigger","HTMLElement","search","query","kill"],"sourceRoot":""} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terwanerik/scrolltrigger", 3 | "version": "1.0.6", 4 | "description": "Triggers classes on html elements based on the scroll position. It makes use of requestAnimationFrame so it doesn't jack the users scroll, that way the user / browser keeps their original scroll behaviour. Animations run when the browser is ready for it.", 5 | "main": "dist/ScrollTrigger.js", 6 | "directories": { 7 | "dist": "dist", 8 | "src": "src" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/terwanerik/ScrollTrigger.git" 13 | }, 14 | "keywords": [ 15 | "scroll", 16 | "trigger", 17 | "animation", 18 | "css3" 19 | ], 20 | "author": "Erik Terwan ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/terwanerik/ScrollTrigger/issues" 24 | }, 25 | "homepage": "https://github.com/terwanerik/ScrollTrigger#readme", 26 | "scripts": { 27 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 28 | "demo": "cross-env NODE_ENV=demo webpack-dev-server --open --hot", 29 | "build-demo": "cross-env NODE_ENV=demo webpack", 30 | "build": "cross-env NODE_ENV=production webpack" 31 | }, 32 | "engines": { 33 | "node": ">=6" 34 | }, 35 | "dependencies": { 36 | "object-extend": "^0.5.0" 37 | }, 38 | "devDependencies": { 39 | "@babel/core": "^7", 40 | "@babel/preset-env": "^7", 41 | "babel-loader": "^8", 42 | "babel-plugin-add-module-exports": "^1", 43 | "cross-env": "^5", 44 | "html-webpack-plugin": "^5", 45 | "unminified-webpack-plugin": "^3", 46 | "webpack": "^5", 47 | "webpack-cli": "^5", 48 | "webpack-dev-server": "^4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ScrollTrigger.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ScrollTrigger 3 | * 4 | * 5 | * http://github.com/terwanerik 6 | * 7 | * Copyright 2017, Erik Terwan 8 | * Released under the MIT license. 9 | * 10 | * Date: 2017-07-09 11 | */ 12 | 13 | /** 14 | * Created by Erik on 09/07/2017. 15 | */ 16 | import DefaultOptions from './config/DefaultOptions' 17 | import _Trigger from './scripts/Trigger' 18 | import _TriggerCollection from './scripts/TriggerCollection' 19 | import _ScrollAnimationLoop from './scripts/ScrollAnimationLoop' 20 | 21 | import extend from 'object-extend' 22 | import './extensions/Array' 23 | 24 | export const Trigger = _Trigger 25 | export const TriggerCollection = _TriggerCollection 26 | export const ScrollAnimationLoop = _ScrollAnimationLoop 27 | 28 | export default class ScrollTrigger { 29 | /** 30 | * Constructor for the scroll trigger 31 | * @param {DefaultOptions} [options=DefaultOptions] options 32 | */ 33 | constructor(options) { 34 | this._parseOptions(options) 35 | this._initCollection() 36 | this._initLoop() 37 | } 38 | 39 | /** 40 | * Parses the options 41 | * @param {DefaultOptions} [options=DefaultOptions] options 42 | * @private 43 | */ 44 | _parseOptions(options) { 45 | options = extend(new DefaultOptions(), options) 46 | 47 | this.defaultTrigger = options.trigger 48 | this.scrollOptions = options.scroll 49 | } 50 | 51 | /** 52 | * Initializes the collection, picks all [data-scroll] elements as initial elements 53 | * @private 54 | */ 55 | _initCollection() { 56 | const scrollAttributes = document.querySelectorAll('[data-scroll]') 57 | let elements = [] 58 | 59 | if (scrollAttributes.length > 0) { 60 | elements = this.createTriggers(scrollAttributes) 61 | } 62 | 63 | this.collection = new TriggerCollection(elements) 64 | } 65 | 66 | /** 67 | * Initializes the scroll loop 68 | * @private 69 | */ 70 | _initLoop() { 71 | this.loop = new ScrollAnimationLoop({ 72 | sustain: this.scrollOptions.sustain, 73 | element: this.scrollOptions.element, 74 | callback: (position, direction) => { 75 | this._scrollCallback(position, direction) 76 | }, 77 | start: () => { 78 | this._scrollStart() 79 | }, 80 | stop: () => { 81 | this._scrollStop() 82 | }, 83 | directionChange: (direction) => { 84 | this._scrollDirectionChange(direction) 85 | } 86 | }) 87 | } 88 | 89 | /** 90 | * Callback for checking triggers 91 | * @param {{x: number, y: number}} position 92 | * @param {string} direction 93 | * @private 94 | */ 95 | _scrollCallback(position, direction) { 96 | this.collection.call((trigger) => { 97 | trigger.checkVisibility(this.scrollOptions.element, direction) 98 | }) 99 | 100 | this.scrollOptions.callback(position, direction) 101 | } 102 | 103 | /** 104 | * When the scrolling started 105 | * @private 106 | */ 107 | _scrollStart() { 108 | this.scrollOptions.start() 109 | } 110 | 111 | /** 112 | * When the scrolling stopped 113 | * @private 114 | */ 115 | _scrollStop() { 116 | this.scrollOptions.stop() 117 | } 118 | 119 | /** 120 | * When the direction changes 121 | * @param {string} direction 122 | * @private 123 | */ 124 | _scrollDirectionChange(direction) { 125 | this.scrollOptions.directionChange(direction) 126 | } 127 | 128 | /** 129 | * Creates a Trigger object from a given element and optional option set 130 | * @param {HTMLElement} element 131 | * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options 132 | * @returns Trigger 133 | */ 134 | createTrigger(element, options) { 135 | return new Trigger(element, extend(this.defaultTrigger, options)) 136 | } 137 | 138 | /** 139 | * Creates an array of triggers 140 | * @param {HTMLElement[]|NodeList} elements 141 | * @param {Object} [options=null] options 142 | * @returns {Trigger[]} Array of triggers 143 | */ 144 | createTriggers(elements, options) { 145 | let triggers = [] 146 | 147 | elements.each((element) => { 148 | triggers.push(this.createTrigger(element, options)) 149 | }) 150 | 151 | return triggers 152 | } 153 | 154 | /** 155 | * Adds triggers 156 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 157 | * @param {Object} [options=null] options 158 | * @returns {ScrollTrigger} 159 | */ 160 | add(objects, options) { 161 | if (objects instanceof HTMLElement) { 162 | this.collection.add(this.createTrigger(objects, options)) 163 | 164 | return this 165 | } 166 | 167 | if (objects instanceof Trigger) { 168 | this.collection.add(objects) 169 | 170 | return this 171 | } 172 | 173 | if (objects instanceof NodeList) { 174 | this.collection.add(this.createTriggers(objects, options)) 175 | 176 | return this 177 | } 178 | 179 | if (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) { 180 | this.collection.add(objects) 181 | 182 | return this 183 | } 184 | 185 | if (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) { 186 | this.collection.add(this.createTriggers(objects, options)) 187 | 188 | return this 189 | } 190 | 191 | // assume it's a query string 192 | this.collection.add(this.createTriggers(document.querySelectorAll(objects), options)) 193 | 194 | return this 195 | } 196 | 197 | /** 198 | * Removes triggers 199 | * @param {string|HTMLElement|NodeList|Trigger|Trigger[]} objects A list of objects or a query 200 | * @returns {ScrollTrigger} 201 | */ 202 | remove(objects) { 203 | if (objects instanceof Trigger) { 204 | this.collection.remove(objects) 205 | 206 | return this 207 | } 208 | 209 | if (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) { 210 | this.collection.remove(objects) 211 | 212 | return this 213 | } 214 | 215 | if (objects instanceof HTMLElement) { 216 | this.collection.remove(this.search(objects)) 217 | 218 | return this 219 | } 220 | 221 | if (Array.isArray(objects) && objects.length && objects[0] instanceof HTMLElement) { 222 | this.collection.remove(this.search(objects)) 223 | 224 | return this 225 | } 226 | 227 | if (objects instanceof NodeList) { 228 | this.collection.remove(this.search(objects)) 229 | 230 | return this 231 | } 232 | 233 | if (Array.isArray(objects) && objects.length && objects[0] instanceof Trigger) { 234 | this.collection.remove(objects) 235 | 236 | return this 237 | } 238 | 239 | // assume it's a query string 240 | this.collection.remove(this.query(objects.toString())) 241 | 242 | return this 243 | } 244 | 245 | /** 246 | * Lookup one or multiple triggers by a query string 247 | * @param {string} selector 248 | * @returns {Trigger[]} 249 | */ 250 | query(selector) { 251 | return this.collection.query(selector) 252 | } 253 | 254 | /** 255 | * Lookup one or multiple triggers by a certain HTMLElement or NodeList 256 | * @param {HTMLElement|HTMLElement[]|NodeList} element 257 | * @returns {Trigger|Trigger[]|null} 258 | */ 259 | search(element) { 260 | return this.collection.search(element) 261 | } 262 | 263 | /** 264 | * Reattaches the scroll listener 265 | */ 266 | listen() { 267 | if (this.loop) { return } 268 | 269 | this._initLoop() 270 | } 271 | 272 | /** 273 | * Kills the scroll listener 274 | */ 275 | kill() { 276 | this.loop.kill() 277 | this.loop = null 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/config/DefaultOptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default options for ScrollTrigger 3 | */ 4 | export default function() { 5 | /** 6 | * The default options for a trigger 7 | * 8 | * @type { 9 | * { 10 | * once: boolean, 11 | * offset: { 12 | * viewport: { 13 | * x: number|(function(frame, direction)), 14 | * y: number|(function(frame, direction)) 15 | * }, 16 | * element: { 17 | * x: number|(function(rect, direction)), 18 | * y: number|(function(rect, direction)) 19 | * } 20 | * }, 21 | * toggle: { 22 | * class: { 23 | * in: string|string[], 24 | * out: string|string[] 25 | * }, 26 | * callback: { 27 | * in: {TriggerInCallback}, 28 | * visible: (function()), 29 | * out: (function()) 30 | * } 31 | * } 32 | * }} 33 | */ 34 | this.trigger = { 35 | once: false, 36 | offset: { 37 | viewport: { 38 | x: 0, 39 | y: 0 40 | }, 41 | element: { 42 | x: 0, 43 | y: 0 44 | } 45 | }, 46 | toggle: { 47 | class: { 48 | in: 'visible', 49 | out: 'invisible' 50 | }, 51 | callback: { 52 | in: null, 53 | visible: null, 54 | out: null 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * The `in` callback is called when the element enters the viewport 61 | * @callback TriggerInCallback 62 | * @param {{x: Number, y: Number}} position 63 | * @param {string} direction 64 | */ 65 | 66 | /** 67 | * The default options for the scroll behaviour 68 | * @type { 69 | * { 70 | * sustain: number, 71 | * element: Window|HTMLDocument|HTMLElement, 72 | * callback: {ScrollCallback}, 73 | * start: (function()), 74 | * stop: (function()), 75 | * directionChange: (function(direction: {string})) 76 | * } 77 | * } 78 | */ 79 | this.scroll = { 80 | sustain: 300, 81 | element: window, 82 | callback: () => {}, 83 | start: () => {}, 84 | stop: () => {}, 85 | directionChange: () => {} 86 | } 87 | 88 | /** 89 | * The scroll callback is called when the user scrolls 90 | * @callback ScrollCallback 91 | * @param {{x: Number, y: Number}} position 92 | * @param {string} direction 93 | */ 94 | } 95 | -------------------------------------------------------------------------------- /src/extensions/Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Faster than .forEach 3 | * @param {(function())} fn The function to call 4 | */ 5 | Array.prototype.each = function (fn) { 6 | const l = this.length 7 | 8 | for(let i = 0; i < l; i++) { 9 | const e = this[i] 10 | 11 | if (e) { 12 | fn(e,i) 13 | } 14 | } 15 | } 16 | 17 | /** 18 | * Give NodeList some Array functions 19 | */ 20 | NodeList.prototype.each = Array.prototype.each 21 | NodeList.prototype.filter = Array.prototype.filter 22 | -------------------------------------------------------------------------------- /src/scripts/ScrollAnimationLoop.js: -------------------------------------------------------------------------------- 1 | import DefaultOptions from '../config/DefaultOptions' 2 | import extend from 'object-extend' 3 | import '../extensions/Array' 4 | 5 | export default class ScrollAnimationLoop { 6 | /** 7 | * ScrollAnimationLoop constructor. 8 | * Starts a requestAnimationFrame loop as long as the user has scrolled the scrollElement. Stops after a certain time. 9 | * 10 | * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop 11 | * @param {ScrollCallback} callback [loop=null] The loop callback 12 | */ 13 | constructor(options, callback) { 14 | this._parseOptions(options) 15 | 16 | if (typeof callback === 'function') { 17 | this.callback = callback 18 | } 19 | 20 | this.direction = 'none' 21 | this.position = this.getPosition() 22 | this.lastAction = this._getTimestamp() 23 | 24 | this._startRun() 25 | 26 | this._boundListener = this._didScroll.bind(this) 27 | this.element.addEventListener('scroll', this._boundListener) 28 | } 29 | 30 | /** 31 | * Parses the options 32 | * 33 | * @param {DefaultOptions.scroll} [options=DefaultOptions.scroll] options The options for the loop 34 | * @private 35 | */ 36 | _parseOptions(options) { 37 | let defaults = new DefaultOptions().scroll 38 | 39 | if (typeof options != 'function') { 40 | defaults.callback = () => {} 41 | 42 | defaults = extend(defaults, options) 43 | } else { 44 | defaults.callback = options 45 | } 46 | 47 | this.element = defaults.element 48 | this.sustain = defaults.sustain 49 | this.callback = defaults.callback 50 | this.startCallback = defaults.start 51 | this.stopCallback = defaults.stop 52 | this.directionChange = defaults.directionChange 53 | } 54 | 55 | /** 56 | * Callback when the user scrolled the element 57 | * @private 58 | */ 59 | _didScroll() { 60 | const newPosition = this.getPosition() 61 | 62 | if (this.position !== newPosition) { 63 | let newDirection = this.direction 64 | 65 | if (newPosition.x !== this.position.x) { 66 | newDirection = newPosition.x > this.position.x ? 'right' : 'left' 67 | } else if (newPosition.y !== this.position.y) { 68 | newDirection = newPosition.y > this.position.y ? 'bottom' : 'top' 69 | } else { 70 | newDirection = 'none' 71 | } 72 | 73 | if (newDirection !== this.direction) { 74 | this.direction = newDirection 75 | 76 | if (typeof this.directionChange === 'function') { 77 | this.directionChange(this.direction) 78 | } 79 | } 80 | 81 | this.position = newPosition 82 | this.lastAction = this._getTimestamp() 83 | } else { 84 | this.direction = 'none' 85 | } 86 | 87 | if (!this.running) { 88 | this._startRun() 89 | } 90 | } 91 | 92 | /** 93 | * Starts the loop, calls the start callback 94 | * @private 95 | */ 96 | _startRun() { 97 | this.running = true 98 | 99 | if (typeof this.startCallback === 'function') { 100 | this.startCallback() 101 | } 102 | 103 | this._loop() 104 | } 105 | 106 | /** 107 | * Stops the loop, calls the stop callback 108 | * @private 109 | */ 110 | _stopRun() { 111 | this.running = false 112 | 113 | if (typeof this.stopCallback === 'function') { 114 | this.stopCallback() 115 | } 116 | } 117 | 118 | /** 119 | * The current position of the element 120 | * @returns {{x: number, y: number}} 121 | */ 122 | getPosition() { 123 | const left = this.element.pageXOffset || this.element.scrollLeft || document.documentElement.scrollLeft || 0 124 | const top = this.element.pageYOffset || this.element.scrollTop || document.documentElement.scrollTop || 0 125 | 126 | return { x: left, y: top } 127 | } 128 | 129 | /** 130 | * The current timestamp in ms 131 | * @returns {number} 132 | * @private 133 | */ 134 | _getTimestamp() { 135 | return Number(Date.now()) 136 | } 137 | 138 | /** 139 | * One single tick of the animation 140 | * @private 141 | */ 142 | _tick() { 143 | this.callback(this.position, this.direction) 144 | 145 | const now = this._getTimestamp() 146 | 147 | if (now - this.lastAction > this.sustain) { 148 | this._stopRun() 149 | } 150 | 151 | if (this.running) { 152 | this._loop() 153 | } 154 | } 155 | 156 | /** 157 | * Requests an animation frame 158 | * @private 159 | */ 160 | _loop() { 161 | const frame = window.requestAnimationFrame || 162 | window.webkitRequestAnimationFrame || 163 | window.mozRequestAnimationFrame || 164 | window.msRequestAnimationFrame || 165 | window.oRequestAnimationFrame || 166 | ((callback) => { setTimeout(callback, 1000 / 60) }) 167 | 168 | frame(this._tick.bind(this)) 169 | } 170 | 171 | /** 172 | * Kills the loop forever 173 | */ 174 | kill() { 175 | this.running = false 176 | this.element.removeEventListener('scroll', this._boundListener) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/scripts/Trigger.js: -------------------------------------------------------------------------------- 1 | import DefaultOptions from '../config/DefaultOptions' 2 | import extend from 'object-extend' 3 | import '../extensions/Array' 4 | 5 | function isInt(n) { 6 | return Number(n) === n && n % 1 === 0 7 | } 8 | 9 | function isFloat(n) { 10 | return Number(n) === n && n % 1 !== 0 11 | } 12 | 13 | export default class Trigger { 14 | /** 15 | * Creates a new Trigger from the given element and options 16 | * 17 | * @param {Element|HTMLElement} element 18 | * @param {DefaultOptions.trigger} [options=DefaultOptions.trigger] options 19 | */ 20 | constructor(element, options) { 21 | this.element = element 22 | 23 | options = extend(new DefaultOptions().trigger, options) 24 | 25 | this.offset = options.offset 26 | this.toggle = options.toggle 27 | this.once = options.once 28 | this.visible = null 29 | this.active = true 30 | } 31 | 32 | /** 33 | * Checks if the Trigger is in the viewport, calls the callbacks and toggles the classes 34 | * @param {HTMLElement|HTMLDocument|Window} parent 35 | * @param {string} direction top, bottom, left, right 36 | * @returns {boolean} If the element is visible 37 | */ 38 | checkVisibility(parent, direction) { 39 | if (!this.active) { 40 | return this.visible 41 | } 42 | 43 | const parentWidth = parent.offsetWidth || parent.innerWidth || 0 44 | const parentHeight = parent.offsetHeight || parent.innerHeight || 0 45 | 46 | const parentFrame = { w: parentWidth, h: parentHeight } 47 | const rect = this.getBounds() 48 | 49 | const visible = this._checkVisibility(rect, parentFrame, direction) 50 | 51 | if (visible !== this.visible) { 52 | this.visible = visible 53 | 54 | const response = this._toggleCallback() 55 | 56 | if (response instanceof Promise) { 57 | response.then(this._toggleClass.bind(this)).catch(e => { 58 | console.error('Trigger promise failed') 59 | console.error(e) 60 | }) 61 | } else { 62 | this._toggleClass() 63 | } 64 | 65 | if (this.visible && this.once) { 66 | this.active = false 67 | } 68 | } else if (visible) { 69 | if (typeof this.toggle.callback.visible == 'function') { 70 | return this.toggle.callback.visible.call(this.element, this) 71 | } 72 | } 73 | 74 | return visible 75 | } 76 | 77 | /** 78 | * Get the bounds of this element 79 | * @return {ClientRect | DOMRect} 80 | */ 81 | getBounds() { 82 | return this.element.getBoundingClientRect() 83 | } 84 | 85 | /** 86 | * Get the calculated offset to place on the element 87 | * @param {ClientRect} rect 88 | * @param {string} direction top, bottom, left, right 89 | * @returns {{x: number, y: number}} 90 | * @private 91 | */ 92 | _getElementOffset(rect, direction) { 93 | let offset = { x: 0, y: 0 } 94 | 95 | if (typeof this.offset.element.x === 'function') { 96 | offset.x = rect.width * this.offset.element.x(this, rect, direction) 97 | } else if (isFloat(this.offset.element.x)) { 98 | offset.x = rect.width * this.offset.element.x 99 | } else if (isInt(this.offset.element.x)) { 100 | offset.x = this.offset.element.x 101 | } 102 | 103 | if (typeof this.offset.element.y === 'function') { 104 | offset.y = rect.height * this.offset.element.y(this, rect, direction) 105 | } else if (isFloat(this.offset.element.y)) { 106 | offset.y = rect.height * this.offset.element.y 107 | } else if (isInt(this.offset.element.y)) { 108 | offset.y = this.offset.element.y 109 | } 110 | 111 | return offset 112 | } 113 | 114 | /** 115 | * Get the calculated offset to place on the viewport 116 | * @param {{w: number, h: number}} parent 117 | * @param {string} direction top, bottom, left, right 118 | * @returns {{x: number, y: number}} 119 | * @private 120 | */ 121 | _getViewportOffset(parent, direction) { 122 | let offset = { x: 0, y: 0 } 123 | 124 | if (typeof this.offset.viewport.x === 'function') { 125 | offset.x = parent.w * this.offset.viewport.x(this, parent, direction) 126 | } else if (isFloat(this.offset.viewport.x)) { 127 | offset.x = parent.w * this.offset.viewport.x 128 | } else if (isInt(this.offset.viewport.x)) { 129 | offset.x = this.offset.viewport.x 130 | } 131 | 132 | if (typeof this.offset.viewport.y === 'function') { 133 | offset.y = parent.h * this.offset.viewport.y(this, parent, direction) 134 | } else if (isFloat(this.offset.viewport.y)) { 135 | offset.y = parent.h * this.offset.viewport.y 136 | } else if (isInt(this.offset.viewport.y)) { 137 | offset.y = this.offset.viewport.y 138 | } 139 | 140 | return offset 141 | } 142 | 143 | /** 144 | * Check the visibility of the trigger in the viewport, with offsets applied 145 | * @param {ClientRect} rect 146 | * @param {{w: number, h: number}} parent 147 | * @param {string} direction top, bottom, left, right 148 | * @returns {boolean} 149 | * @private 150 | */ 151 | _checkVisibility(rect, parent, direction) { 152 | const elementOffset = this._getElementOffset(rect, direction) 153 | const viewportOffset = this._getViewportOffset(parent, direction) 154 | 155 | let visible = true 156 | 157 | if ((rect.left - viewportOffset.x) < -(rect.width - elementOffset.x)) { 158 | visible = false 159 | } 160 | 161 | if ((rect.left + viewportOffset.x) > (parent.w - elementOffset.x)) { 162 | visible = false 163 | } 164 | 165 | if ((rect.top - viewportOffset.y) < -(rect.height - elementOffset.y)) { 166 | visible = false 167 | } 168 | 169 | if ((rect.top + viewportOffset.y) > (parent.h - elementOffset.y)) { 170 | visible = false 171 | } 172 | 173 | return visible 174 | } 175 | 176 | /** 177 | * Toggles the classes 178 | * @private 179 | */ 180 | _toggleClass() { 181 | if (this.visible) { 182 | if (Array.isArray(this.toggle.class.in)) { 183 | this.toggle.class.in.each((className) => { 184 | this.element.classList.add(className) 185 | }) 186 | } else { 187 | this.element.classList.add(this.toggle.class.in) 188 | } 189 | 190 | if (Array.isArray(this.toggle.class.out)) { 191 | this.toggle.class.out.each((className) => { 192 | this.element.classList.remove(className) 193 | }) 194 | } else { 195 | this.element.classList.remove(this.toggle.class.out) 196 | } 197 | 198 | return 199 | } 200 | 201 | if (Array.isArray(this.toggle.class.in)) { 202 | this.toggle.class.in.each((className) => { 203 | this.element.classList.remove(className) 204 | }) 205 | } else { 206 | this.element.classList.remove(this.toggle.class.in) 207 | } 208 | 209 | if (Array.isArray(this.toggle.class.out)) { 210 | this.toggle.class.out.each((className) => { 211 | this.element.classList.add(className) 212 | }) 213 | } else { 214 | this.element.classList.add(this.toggle.class.out) 215 | } 216 | } 217 | 218 | /** 219 | * Toggles the callback 220 | * @private 221 | * @return null|Promise 222 | */ 223 | _toggleCallback() { 224 | if (this.visible) { 225 | if (typeof this.toggle.callback.in == 'function') { 226 | return this.toggle.callback.in.call(this.element, this) 227 | } 228 | } else { 229 | if (typeof this.toggle.callback.out == 'function') { 230 | return this.toggle.callback.out.call(this.element, this) 231 | } 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/scripts/TriggerCollection.js: -------------------------------------------------------------------------------- 1 | import Trigger from './Trigger' 2 | import '../extensions/Array' 3 | 4 | export default class TriggerCollection { 5 | /** 6 | * Initializes the collection 7 | * @param {Trigger[]} [triggers=[]] triggers A set of triggers to init with, optional 8 | */ 9 | constructor(triggers) { 10 | /** 11 | * @member {Trigger[]} 12 | */ 13 | this.triggers = triggers instanceof Array ? triggers : [] 14 | } 15 | 16 | /** 17 | * Adds one or multiple Trigger objects 18 | * @param {Trigger|Trigger[]} objects 19 | */ 20 | add(objects) { 21 | if (objects instanceof Trigger) { 22 | // single 23 | return this.triggers.push(objects) 24 | } 25 | 26 | objects.each((trigger) => { 27 | if (trigger instanceof Trigger) { 28 | this.triggers.push(trigger) 29 | } else { 30 | console.error('Object added to TriggerCollection is not a Trigger. Object: ', trigger) 31 | } 32 | }) 33 | } 34 | 35 | /** 36 | * Removes one or multiple Trigger objects 37 | * @param {Trigger|Trigger[]} objects 38 | */ 39 | remove(objects) { 40 | if (objects instanceof Trigger) { 41 | objects = [objects] 42 | } 43 | 44 | this.triggers = this.triggers.filter((trigger) => { 45 | let hit = false 46 | 47 | objects.each((object) => { 48 | if (object == trigger) { 49 | hit = true 50 | } 51 | }) 52 | 53 | return !hit 54 | }) 55 | } 56 | 57 | /** 58 | * Lookup one or multiple triggers by a query string 59 | * @param {string} selector 60 | * @returns {Trigger[]} 61 | */ 62 | query(selector) { 63 | return this.triggers.filter((trigger) => { 64 | const element = trigger.element 65 | const parent = element.parentNode 66 | const nodes = [].slice.call(parent.querySelectorAll(selector)) 67 | 68 | return nodes.indexOf(element) > -1 69 | }) 70 | } 71 | 72 | /** 73 | * Lookup one or multiple triggers by a certain HTMLElement or NodeList 74 | * @param {HTMLElement|HTMLElement[]|NodeList} element 75 | * @returns {Trigger|Trigger[]|null} 76 | */ 77 | search(element) { 78 | const found = this.triggers.filter((trigger) => { 79 | if (element instanceof NodeList || Array.isArray(element)) { 80 | let hit = false 81 | 82 | element.each((el) => { 83 | if (trigger.element == el) { 84 | hit = true 85 | } 86 | }) 87 | 88 | return hit 89 | } 90 | 91 | return trigger.element == element 92 | }) 93 | 94 | return found.length == 0 ? null : (found.length > 1 ? found : found[0]) 95 | } 96 | 97 | /** 98 | * Calls a function on all triggers 99 | * @param {(function())} callback 100 | */ 101 | call(callback) { 102 | this.triggers.each(callback) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const UnminifiedWebpackPlugin = require('unminified-webpack-plugin') 5 | 6 | const libraryName = 'ScrollTrigger' 7 | 8 | let plugins = [new UnminifiedWebpackPlugin()] 9 | let mode = 'development' 10 | let entry = __dirname + '/src/ScrollTrigger.js' 11 | let outputFile = libraryName + '.js?[hash]' 12 | let outputPath = __dirname + '/dist' 13 | let contentBase = 'dev' 14 | 15 | switch (process.env.NODE_ENV) { 16 | case 'demo': 17 | entry = __dirname + '/demo/main.js' 18 | outputPath = __dirname + '/public' 19 | contentBase = 'demo' 20 | 21 | plugins.push(new HtmlWebpackPlugin({ 22 | inject: 'body', 23 | template: 'demo/index.html' 24 | })) 25 | 26 | break 27 | case 'development': 28 | entry = __dirname + '/dev/main.js' 29 | 30 | plugins.push(new HtmlWebpackPlugin({ 31 | inject: 'body', 32 | template: 'dev/index.html' 33 | })) 34 | 35 | break 36 | default: 37 | outputFile = libraryName + '.min.js' 38 | mode = 'production' 39 | 40 | break 41 | } 42 | 43 | module.exports = { 44 | entry: entry, 45 | mode: mode, 46 | devtool: 'source-map', 47 | output: { 48 | path: outputPath, 49 | filename: outputFile, 50 | library: libraryName, 51 | libraryTarget: 'umd', 52 | umdNamedDefine: true 53 | }, 54 | plugins: plugins, 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.m?js$/, 59 | exclude: /(node_modules|bower_components)/, 60 | use: { 61 | loader: 'babel-loader', 62 | options: { 63 | presets: ['@babel/preset-env'] 64 | } 65 | } 66 | } 67 | ] 68 | }, 69 | devServer: { 70 | static: contentBase, 71 | host: '127.0.0.1', 72 | port: 8010 73 | } 74 | } 75 | --------------------------------------------------------------------------------