├── .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 | [](https://github.com/terwanerik/ScrollTrigger/actions?query=workflow%3ABuild)
3 | [](https://github.com/terwanerik/ScrollTrigger/actions?query=workflow%3ADeploy)
4 | [](https://github.com/terwanerik/ScrollTrigger/blob/master/LICENSE)
5 | [](https://github.com/terwanerik/ScrollTrigger/issues)
6 | [](https://github.com/terwanerik/ScrollTrigger/releases)
7 | [](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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------