The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .eslintrc.json
├── .gitattributes
├── .github
    ├── ISSUE_TEMPLATE.md
    └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── build
    ├── rollup.conf.banner.js
    ├── rollup.conf.js
    └── rollup.conf.min.js
├── dist
    ├── scrollreveal.es.js
    ├── scrollreveal.js
    └── scrollreveal.min.js
├── package.json
├── src
    ├── index.js
    ├── instance
    │   ├── constructor.js
    │   ├── defaults.js
    │   ├── functions
    │   │   ├── animate.js
    │   │   ├── delegate.js
    │   │   ├── initialize.js
    │   │   ├── rinse.js
    │   │   ├── sequence.js
    │   │   └── style.js
    │   ├── methods
    │   │   ├── clean.js
    │   │   ├── destroy.js
    │   │   ├── reveal.js
    │   │   └── sync.js
    │   └── mount.js
    ├── polyfills
    │   └── math-sign.js
    └── utils
    │   ├── deep-assign.js
    │   ├── each.js
    │   ├── get-geometry.js
    │   ├── get-prefixed-css-prop.js
    │   ├── get-scrolled.js
    │   ├── is-element-visible.js
    │   ├── is-mobile.js
    │   ├── is-object.js
    │   ├── is-transform-supported.js
    │   ├── is-transition-supported.js
    │   ├── logger.js
    │   └── next-unique-id.js
└── test
    ├── instance
        └── constructor.spec.js
    ├── karma.conf.js
    ├── polyfills
        └── math-sign.spec.js
    ├── sauce.conf.js
    ├── timeout.spec.js
    └── utils
        ├── deep-assign.spec.js
        ├── each.spec.js
        ├── get-prefixed-css-prop.spec.js
        ├── is-mobile.spec.js
        ├── is-object.spec.js
        ├── is-transform-supported.spec.js
        ├── is-transition-supported.spec.js
        ├── logger.spec.js
        └── next-unique-id.spec.js


/.eslintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"env": {
 3 | 		"es6": true,
 4 | 		"amd": true,
 5 | 		"browser": true,
 6 | 		"mocha": true,
 7 | 		"node": true
 8 | 	},
 9 | 	"extends": "eslint:recommended",
10 | 	"parserOptions": {
11 | 		"sourceType": "module"
12 | 	},
13 | 	"globals": {
14 | 		"describe": true,
15 | 		"it": true,
16 | 		"expect": true,
17 | 		"sinon": true
18 | 	},
19 | 	"rules": {
20 | 		"no-cond-assign": 2,
21 | 		"no-console": 1,
22 | 		"no-const-assign": 2,
23 | 		"no-class-assign": 2,
24 | 		"no-this-before-super": 2,
25 | 		"no-unused-vars": 1,
26 | 		"no-var": 2,
27 | 		"object-shorthand": [2, "always"]
28 | 	}
29 | }
30 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js eol=lf
2 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!--
 2 | 
 3 | Thanks for raising an issue! To help us help you, if you've found a bug please consider the following:
 4 | 
 5 | * If you can demonstrate the bug using JSBin: https://goo.gl/6b4OeX — please do.
 6 | * If that's not possible, perhaps because your bug involves plugins, we recommend creating a small repo that illustrates the problem.
 7 | 
 8 | And please, search existing issues before creating a new one.
 9 | 
10 | -->
11 | 
12 | ### Environment
13 | 
14 | * Operating System:
15 | * Browser Version:
16 | * ScrollReveal Version:
17 | 


--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!--
 2 | 
 3 | Thank you for creating a pull request. Before submitting, please note the following:
 4 | 
 5 | * If your pull request implements a new feature, please raise an issue to discuss it before sending code.
 6 | * This message body should clearly illustrate what problems it solves.
 7 | * If there are related issues, remember to reference them.
 8 | * Ideally, include a test that fails without this PR but passes with it. PRs will only be merged once they pass CI.
 9 | 
10 | -->
11 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ignore/
3 | .vscode/
4 | node_modules/
5 | yarn.lock
6 | package-lock.json
7 | 


--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
 1 | language: node_js
 2 | dist: trusty
 3 | node_js:
 4 |   - '9'
 5 | addons:
 6 |   chrome: stable
 7 |   hosts: localsauce
 8 | sudo: required
 9 | before_script:
10 |   - 'sudo chown root /opt/google/chrome/chrome-sandbox'
11 |   - 'sudo chmod 4755 /opt/google/chrome/chrome-sandbox'
12 | after_success:
13 |   - npm run coverage
14 | 


--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | # Change Log
  2 | 
  3 | ## [4.0.9] - 2021-03-04
  4 | 
  5 | ### Fixed
  6 | 
  7 | - Styles applied using CSSOM don't drop `:` characters.
  8 | 
  9 | ## [4.0.8] - 2021-03-02
 10 | 
 11 | ### Fixed
 12 | 
 13 | - Avoid Content Security Policy (CSP) violations. [@lambdacasserole](https://github.com/lambdacasserole) [#431](https://github.com/jlmakes/scrollreveal/pull/431)
 14 | 
 15 | ## [4.0.7] - 2020-07-15
 16 | 
 17 | ### Fixed
 18 | 
 19 | - Ensure element geometry exists. [#437](https://github.com/jlmakes/scrollreveal/issues/437)
 20 | 
 21 | ## [4.0.6] - 2020-03-15
 22 | 
 23 | ### Fixed
 24 | 
 25 | - Default transition values of `none` are now correctly ignored. [#231](https://github.com/jlmakes/scrollreveal/issues/231)
 26 | 
 27 | ### Fixed
 28 | 
 29 | ## [4.0.5] - 2018-10-20
 30 | 
 31 | ### Fixed
 32 | 
 33 | - Calling `reveal()` on the same `target` breaking animation. [#468](https://github.com/jlmakes/scrollreveal/issues/468)
 34 | 
 35 | ## [4.0.4] - 2018-09-22
 36 | 
 37 | ### Fixed
 38 | 
 39 | - Malformed `package.json`
 40 | 
 41 | ## [4.0.3] - 2018-09-21
 42 | 
 43 | ### Fixed
 44 | 
 45 | – `options.cleanup` is now correctly set to `false` by default. [#457](https://github.com/jlmakes/scrollreveal/issues/457)
 46 | 
 47 | ## [4.0.2] - 2018-09-11
 48 | 
 49 | ### Fixed
 50 | 
 51 | - Null property assignment regression in mount function. [#456](https://github.com/jlmakes/scrollreveal/issues/456)
 52 | 
 53 | ## [4.0.1] - 2018-09-09
 54 | 
 55 | ### Fixed
 56 | 
 57 | - Noop instances were not correctly unmounting from the DOM. [#455](https://github.com/jlmakes/scrollreveal/issues/455)
 58 | - Readme links to pricing page no longer 404.
 59 | 
 60 | ## [4.0.0] - 2018-08-06
 61 | 
 62 | ### Added
 63 | 
 64 | - ScrollReveal can be enabled/disabled on desktops using `options.desktop`.
 65 | - The class `sr` is added to `<html>` during instantiation when supported. [#294](https://github.com/jlmakes/scrollreveal/issues/294)
 66 | - `height: 100%` is added to `<body>` during instantiation when supported. [#298](https://github.com/jlmakes/scrollreveal/issues/298)
 67 | - Unused containers are removed from the store, and their event listeners destroyed.
 68 | - ScrollReveal skips generating opacity styles when `options.opacity` is set to `null`.
 69 | - ScrollReveal retains element CSS transformations. [#251](https://github.comjlmakes/scrollreveal/issues/251)
 70 | - New `options.cleanup` toggles whether generated styles are removed upon reveal completion (when `options.reset` is `false`). [#292](https://github.comjlmakes/scrollreveal/issues/292)
 71 | - ScrollReveal tracks scroll direction as container store data. [#384](https://github.com/jlmakes/scrollreveal/issues/384)
 72 | - New `clean()` method removes specific generated styles and event listeners. [#227](https://github.com/jlmakes/scrollreveal/issues/227)
 73 | - New `destroy()` method removes all generated styles and event listeners. [#227](https://github.com/jlmakes/scrollreveal/issues/227)
 74 | - New `debug` static property toggles error messages in console. [#351](https://github.com/jlmakes/scrollreveal/issues/351)
 75 | - Instance methods now accept native arrays of HTML elements.
 76 | 
 77 | ### Changed
 78 | 
 79 | - **Breaking:** The `reveal()` method no longer accepts an `interval` parameter. Instead, sequence intervals are now defined with `options.interval`.
 80 | - **Breaking:** The instance method `isSupported()` is now static.
 81 | - **Breaking:** `options.distance` supports only `em` `px` and `%` values.
 82 | - **Breaking:** ScrollReveal methods are no longer chainable.
 83 | - **Breaking:** ScrollReveal requires a commercial license, unless for [GPL-3.0](https://opensource.org/licenses/GPL-3.0) compatible open source projects.
 84 | - Elements in a reveal sequence are no longer grouped, and reveal progressively when visible.
 85 | - ScrollReveal uses a single `matrix3d()` property, with the correct prefix and only when necessary. [#292](https://github.com/jlmakes/scrollreveal/issues/292)
 86 | - ScrollReveal returns a non-operational instance when instantiated in unsupported browsers.
 87 | - ScrollReveal `version` is now a read-only instance property.
 88 | - ScrollReveal methods are now bound read-only instance properties.
 89 | - `options.viewFactor` clamps values outside of `0.0` to `1.0`.
 90 | - ScrollReveal constructor now returns a singleton.
 91 | 
 92 | ### Fixed
 93 | 
 94 | - The `requestAnimationFrame` polyfill now reliably throttles callback invocations.
 95 | 
 96 | ## [3.3.6] - 2017-06-23
 97 | 
 98 | ### Fixed
 99 | 
100 | - Element visibility now checks left and right boundaries correctly. [#352](https://github.com/jlmakes/scrollreveal/issues/352)
101 | - Library version instance property is again accurate.
102 | 
103 | ## [3.3.5] - 2017-04-05
104 | 
105 | ### Fixed
106 | 
107 | - Patched to ensure version 3 is the default NPM package.
108 | 
109 | ## [3.3.4] - 2017-02-18
110 | 
111 | ### Fixed
112 | 
113 | - Update stale CDN link in README.
114 | 
115 | ### Changed
116 | 
117 | - Add deprecation warnings to README.
118 | 
119 | ## [3.3.3] - 2017-02-18
120 | 
121 | ### Fixed
122 | 
123 | - Fix error when using Bower and Wordpress due to missing semi-colon. [#278](https://github.com/jlmakes/scrollreveal/issues/278)
124 | 
125 | ## [3.3.2] - 2016-10-02
126 | 
127 | ### Changed
128 | 
129 | - Updated Starting Defaults section in README. [#273](https://github.comjlmakes/scrollreveal/issues/273)
130 | 
131 | ### Fixed
132 | 
133 | - Using a selector to define a default container during instantiation now works. [#289](https://github.com/jlmakes/scrollreveal/issues/289)
134 | 
135 | ## [3.3.1] - 2016-07-22
136 | 
137 | ### Fixed
138 | 
139 | - Instance variable `version` updated with correct library version.
140 | 
141 | ## [3.3.0] - 2016-07-22
142 | 
143 | ### Added
144 | 
145 | - New callback `beforeReveal(el)`. [#273](https://github.comjlmakes/scrollreveal/issues/273)
146 | - New callback `beforeReset(el)`. [#273](https://github.com/jlmakes/scrollreveal/issues/273)
147 | 
148 | ## [3.2.0] - 2016-07-08
149 | 
150 | ### Added
151 | 
152 | - New `isNodeList()` method added to `Tools`.
153 | - New `version` instance variable contains library version.
154 | - HTML Collections are now supported as the first argument in `reveal()`. [#246](https://github.com/jlmakes/scrollreveal/issues/246)
155 | - Added fallback for `requestAnimationFrame`. [#267](https://github.comjlmakes/scrollreveal/issues/267)
156 | 
157 | ### Changed
158 | 
159 | - Updated Starting Defaults section in README.
160 | 
161 | ### Fixed
162 | 
163 | - Calling `reveal()` multiple times on an element with `config.origin` as `top` or `left` no longer produces invalid CSS. [#270](https://github.com/jlmakes/scrollreveal/issues/270)
164 | - Refactored AMD/CommonJS module wrapper to work with Codekit. [#253](https://github.com/jlmakes/scrollreveal/issues/253)
165 | 
166 | ## [3.1.5] - 2016-07-06
167 | 
168 | ### Fixed
169 | 
170 | - `sync()` method now properly supports sequences.
171 | 
172 | ## [3.1.4] - 2016-03-28
173 | 
174 | ### Changed
175 | 
176 | - Added `console.log` calls back to non-minified distribution. [#235](https://github.com/jlmakes/scrollreveal/issues/235)
177 | 
178 | ## [3.1.3] - 2016-03-28
179 | 
180 | ### Removed
181 | 
182 | - Removed `console.log` calls from distribution. [#235](https://github.comjlmakes/scrollreveal/issues/235)
183 | 
184 | ## [3.1.2] - 2016-03-23
185 | 
186 | ### Fixed
187 | 
188 | - Removed stray quotation mark in `reveal()` error message.
189 | 
190 | ## [3.1.1] - 2016-03-08
191 | 
192 | ### Fixed
193 | 
194 | - `config.reset` now works properly with sequences. [#241](https://github.comjlmakes/scrollreveal/issues/241)
195 | 
196 | ## [3.1.0] - 2016-03-07
197 | 
198 | ### Added
199 | 
200 | - New `isNode()` method added to `Tools`.
201 | - HTML elements are now supported as the first argument in `reveal()`.
202 | - Selector strings assigned to `config.container` are now supported.
203 | - `reveal()` now accepts an `interval` as it's last argument to create sequences. [#86](https://github.com/jlmakes/scrollreveal/issues/86) [#180](https://github.com/jlmakes/scrollreveal/issues/180) [#187](https://github.comjlmakes/scrollreveal/issues/187) [#215](https://github.com/jlmakes/scrollreveal/issues/215) [#234](https://github.com/jlmakes/scrollreveal/issues/234)
204 | - New section on sequenced animations added to README.
205 | 
206 | ### Changed
207 | 
208 | - Messages logged to console are now prepended with `ScrollReveal:` for clarity.
209 | - Revised and renamed `supported()` method to `isSupported()`.
210 | - Updated Custom Containers section in README with an example using a selector.
211 | - Updated Tips section in README.
212 | 
213 | ### Fixed
214 | 
215 | - Added semi-colon before global IIFE to improve reliability. [#228](https://github.com/jlmakes/scrollreveal/issues/228)
216 | - The existence of `console.log` is now confirmed for IE9. [#230](https://github.com/jlmakes/scrollreveal/issues/230)
217 | - Typos, indentation and semicolons corrected in README.
218 | 
219 | ## [3.0.9] - 2016-01-14
220 | 
221 | ### Changed
222 | 
223 | - Updated example site links in the README.
224 | 
225 | ### Fixed
226 | 
227 | - Fixed operator mismatch inside `supported()`. [#220](https://github.comjlmakes/scrollreveal/issues/220)
228 | 
229 | ## [3.0.8] - 2016-01-13
230 | 
231 | ### Changed
232 | 
233 | - Public methods now verify that ScrollReveal is supported.
234 | 
235 | ### Fixed
236 | 
237 | - Updated Tips section in README.
238 | 
239 | ## [3.0.7] - 2016-01-13
240 | 
241 | ### Added
242 | 
243 | - Added brower support information to README. [#219](https://github.comjlmakes/scrollreveal/issues/219)
244 | 
245 | ### Changed
246 | 
247 | - `console.log` is now used instead of `console.warn`. [#215](https://github.com/jlmakes/scrollreveal/issues/215)
248 | - Moved `tools.isSupported` method to `ScrollReveal.prototype.supported`.
249 | - Updated the configuration and tips documentation in the README.
250 | 
251 | ### Removed
252 | 
253 | - The `init()` method was removed.
254 | 
255 | ### Fixed
256 | 
257 | - Using `config.mobile` in `reveal()` now works. [#216](https://github.comjlmakes/scrollreveal/issues/216)
258 | 
259 | ## [3.0.6] - 2016-01-02
260 | 
261 | ### Fixed
262 | 
263 | - Custom default containers are now used.
264 | - Critical issues affecting Chrome on iOS were (finally) solved. [#196](https://github.com/jlmakes/scrollreveal/issues/196)
265 | - Revisited `3.0.4` changes to chaining `reveal()` calls. [#212](https://github.com/jlmakes/scrollreveal/issues/212)
266 | 
267 | ## [3.0.5] - 2015-12-30
268 | 
269 | ### Fixed
270 | 
271 | - Fixed compatibility issues with Webpack. [#209](https://github.comjlmakes/scrollreveal/issues/209)
272 | 
273 | ## [3.0.4] - 2015-12-30
274 | 
275 | ### Fixed
276 | 
277 | - Squashed Webkit browser bugs due to syntax errors. [#208](https://github.comjlmakes/scrollreveal/issues/208)
278 | - Chaining `reveal()` calls no longer prematurely initialize animation.
279 | - Cleaned up README typos, and stale reference to `config.wait`.
280 | 
281 | ## [3.0.3] - 2015-12-22
282 | 
283 | ### Changed
284 | 
285 | - `reveal()` and `sync()` now return the ScrollReveal instance even on failure. [#198](https://github.com/jlmakes/scrollreveal/issues/198)
286 | 
287 | ## [3.0.2] - 2015-12-22
288 | 
289 | ### Added
290 | 
291 | - Added `bower.json` to release package. [#199](https://github.comjlmakes/scrollreveal/issues/199)
292 | 
293 | ### Fixed
294 | 
295 | - Preexisting CSS transition styles are no longer destroyed. [#197](https://github.com/jlmakes/scrollreveal/issues/197)
296 | 
297 | ## [3.0.1] - 2015-12-21
298 | 
299 | ### Changed
300 | 
301 | - Updated Getting Started section in the README.
302 | 
303 | ### Fixed
304 | 
305 | - Hard learned NPM and Bower issues related to release management were endured.
306 | - Issues related to element visibility and animation behavior were addressed. [#193](https://github.com/jlmakes/scrollreveal/issues/193) [#196](https://github.comjlmakes/scrollreveal/issues/196)
307 | 
308 | ## [3.0.0] - 2015-12-15
309 | 
310 | This version marks a significant change in how developers use ScrollReveal, introducing a JavaScript API to replace the inline attribute parser. It's a big shift, but prioritizes maintainability and flexibility over the novelty of natural language parsing.
311 | 
312 | ### Added
313 | 
314 | - New method `reveal()`. [#1](https://github.com/jlmakes/scrollreveal/issues/1) [#122](https://github.com/jlmakes/scrollreveal/issues/122)
315 | - New method `sync()`.
316 | - New callback `config.afterReset`.
317 | - Horizontal scrolling is now supported. [#184](https://github.comjlmakes/scrollreveal/issues/184)
318 | 
319 | ### Changed
320 | 
321 | - **Breaking:** `config.enter` renamed `config.origin`.
322 | - **Breaking:** `config.wait` renamed `config.delay`.
323 | - **Breaking:** `config.delay` renamed `config.useDelay`.
324 | - **Breaking:** `config.over` renamed `config.duration`.
325 | - **Breaking:** `config.move` renamed `config.distance`.
326 | - **Breaking:** `config.viewport` renamed `config.container`.
327 | - **Breaking:** `config.vFactor` renamed `config.viewFactor`.
328 | - **Breaking:** `config.complete` renamed `config.afterReveal`.
329 | - **Breaking:** Time values are now expected in milliseconds (instead of `string`).
330 | - **Breaking:** `config.scale` expects value type `number` (instead of `object`).
331 | - **Breaking:** `config.rotation` axis values require `string` with unit type (instead of `number`).
332 | - **Breaking:** ScrollReveal constructor is now capitalized.
333 | - Reveals now resolve to element's computed opacity, instead of `1`. [#185](https://github.com/jlmakes/scrollreveal/issues/185)
334 | 
335 | ### Removed
336 | 
337 | - ScrollReveal no longer recognizes `data-sr` attributes.
338 | 
339 | ### Fixed
340 | 
341 | - Improved reliability of callback timers.
342 | 
343 | ## [2.3.2] - 2015-06-15
344 | 
345 | ### Changed
346 | 
347 | - Updated `bower.json` syntax. [#150](https://github.com/jlmakes/scrollreveal/issues/150)
348 | 
349 | ## [2.3.1] - 2015-06-04
350 | 
351 | ### Added
352 | 
353 | - Simple instantiation (without `new` keyword) is now supported. [#148](https://github.com/jlmakes/scrollreveal/issues/148)
354 | 
355 | ## [2.3.0] - 2015-04-25
356 | 
357 | ### Added
358 | 
359 | - New keyword `vFactor` and alias `vF` control when an element is considered visible.
360 | - New keyword `opacity` controls starting opacity.
361 | 
362 | ### Removed
363 | 
364 | - The easing keyword `hustle` was removed.
365 | 
366 | ## [2.2.0] - 2015-03-18
367 | 
368 | ### Added
369 | 
370 | - New keyword `spin` controls yaw.
371 | - New keyword `roll` controls roll.
372 | - New keyword `flip` controls pitch.
373 | 
374 | ### Changed
375 | 
376 | - Improved Basic Usage examples in README.
377 | 
378 | ## [2.1.0] - 2014-11-25
379 | 
380 | ### Added
381 | 
382 | - Various tablets added to mobile device detection. [#32](https://github.comjlmakes/scrollreveal/issues/32) [#81](https://github.com/jlmakes/scrollreveal/issues/81)
383 | - CSS Transition support is now confirmed during instantiation. [#109](https://github.com/jlmakes/scrollreveal/issues/109)
384 | 
385 | ## [2.0.5] - 2014-11-23
386 | 
387 | ### Changed
388 | 
389 | - Reverted `2.0.4` change to element animation logic. [#108](https://github.comjlmakes/scrollreveal/issues/108)
390 | 
391 | ## [2.0.4] - 2014-11-21
392 | 
393 | ### Changed
394 | 
395 | - Revised how element animations are handled.
396 | - Reverted `2.0.3` change to element visibility logic. [#106](https://github.com/jlmakes/scrollreveal/issues/106)
397 | 
398 | ## [2.0.3] - 2014-11-14
399 | 
400 | ### Added
401 | 
402 | - `data-sr` attributes are now stripped from initialized elements. [#100](https://github.com/jlmakes/scrollreveal/issues/100) @orapouso.
403 | - Live Reload added to development environment.
404 | 
405 | ### Changed
406 | 
407 | - Revised how element visibility is determined.
408 | 
409 | ### Removed
410 | 
411 | - Multiple instances sharing the same viewport element no longer throw an error. [#98](https://github.com/jlmakes/scrollreveal/issues/98) @orapouso.
412 | 
413 | ### Fixed
414 | 
415 | - Incomplete support for `config.delay = "onload"` was addressed.
416 | - Issues related to `setTimeout`, `config.complete` and incorrect animation timing were addressed. [#96](https://github.com/jlmakes/scrollreveal/issues/96)
417 | 
418 | ## [2.0.2] - 2014-10-23
419 | 
420 | ### Added
421 | 
422 | - An error is now thrown when multiple instances share the same viewport element. [#91](https://github.com/jlmakes/scrollreveal/issues/91)
423 | 
424 | ### Fixed
425 | 
426 | - Updated NPM and Bower references with new distribution path.
427 | 
428 | ## [2.0.1] - 2014-10-18
429 | 
430 | ### Fixed
431 | 
432 | - Incomplete support for `config.viewport` was addressed. [#67](https://github.com/jlmakes/scrollreveal/issues/67) [#68](https://github.comjlmakes/scrollreveal/issues/68)
433 | 
434 | ## [2.0.0] - 2014-10-17
435 | 
436 | ### Added
437 | 
438 | - New keyword `scale` controls element starting size.
439 | - New option `config.complete` defines a callback for when reveals finish.
440 | - New option `config.viewport` defines custom viewports.
441 | - New option `config.mobile` enables/disables ScrollReveal on mobile devices.
442 | - New option `config.delay` controls when animations are delayed.
443 | 
444 | ### Changed
445 | 
446 | - **BREAKING:** ScrollReveal now uses the `data-sr` instead of `data-scroll-reveal`.
447 | - Repository now follows [Semantic Versioning](http://semver.org/).
448 | 
449 | ### Removed
450 | 
451 | - The `after` keyword was removed.
452 | 
453 | ## 0.1.3 - 2014-05-26 [YANKED]
454 | 
455 | ### Added
456 | 
457 | - Configuration now includes starting opacity. [#33](https://github.comjlmakes/scrollreveal/issues/33) @kierzniak
458 | - New `data-scroll-reveal-id` attribute added to revealed DOM elements.
459 | 
460 | ### Changed
461 | 
462 | - Scroll event handling now uses `requestAnimationFrame`. [#48](https://github.com/jlmakes/scrollreveal/issues/48) @pazguille
463 | - Generated styles are now stored in an object corresponding to the `data-scroll-reveal-id` attribute on each element. [#38](https://github.com/jlmakes/scrollreveal/pull/38) @georgelee1
464 | 
465 | ## 0.1.2 - 2014-03-13 [YANKED]
466 | 
467 | ### Added
468 | 
469 | - Elements with `position: fixed` are now supported. [#35](https://github.comjlmakes/scrollreveal/issues/35)
470 | 
471 | ### Fixed
472 | 
473 | - Generated styles are now more specific. [#37](https://github.comjlmakes/scrollreveal/issues/37)
474 | 
475 | ## 0.1.1 - 2014-03-06 [YANKED]
476 | 
477 | ### Fixed
478 | 
479 | - Squashed bug with `enter top` and `enter left`. [#13](https://github.comjlmakes/scrollreveal/issues/13) [#31](https://github.com/jlmakes/scrollreveal/issues/31) @sherban @danycerone
480 | 
481 | ## 0.1.0 - 2014-03-05 [YANKED]
482 | 
483 | ### Added
484 | 
485 | - Distribution now supports AMD/CommonJS.
486 | - Repository now uses Gulp.
487 | - Boilerplate Testline suite added to repository.
488 | 
489 | ### Changed
490 | 
491 | - **BREAKING:** ScrollReveal now uses the `data-scroll-reveal` attribute to parse animation instructions, in place of `data-scrollReveal`.
492 | 
493 | ## 0.0.4 - 2014-02-28 [YANKED]
494 | 
495 | ### Fixed
496 | 
497 | - ScrollReveal no longer destroys the existing style attribute on revealed elements, but instead, now appends the necessary animation styles to existing inline styles.
498 | 
499 | ## 0.0.3 - 2014-02-22 [YANKED]
500 | 
501 | ### Fixed
502 | 
503 | - Removed unused CSS Transition/Transform prefixes for Mozilla and Opera.
504 | 
505 | ## 0.0.2 - 2014-02-13 [YANKED]
506 | 
507 | ### Added
508 | 
509 | - Constructor now accepts a configuration object to customize defaults.
510 | - New `reset` keyword allows elements to reveal each time they enter the viewport.
511 | - The `move` keyword can now be replaced with with CSS easing keywords (e.g. `ease-in-out`).
512 | - Library documentation and code examples added to README.
513 | 
514 | ### Changed
515 | 
516 | - ScrollReveal is no longer automatically instantiated by the `DOMContentLoaded` event.
517 | 
518 | ## 0.0.1 - 2014-01-22 [YANKED]
519 | 
520 | ### Hello World
521 | 
522 | [4.0.9]: https://github.com/jlmakes/scrollreveal/compare/v4.0.8...v4.0.9
523 | [4.0.8]: https://github.com/jlmakes/scrollreveal/compare/v4.0.7...v4.0.8
524 | [4.0.7]: https://github.com/jlmakes/scrollreveal/compare/v4.0.6...v4.0.7
525 | [4.0.6]: https://github.com/jlmakes/scrollreveal/compare/v4.0.5...v4.0.6
526 | [4.0.5]: https://github.com/jlmakes/scrollreveal/compare/v4.0.4...v4.0.5
527 | [4.0.4]: https://github.com/jlmakes/scrollreveal/compare/v4.0.3...v4.0.4
528 | [4.0.3]: https://github.com/jlmakes/scrollreveal/compare/v4.0.2...v4.0.3
529 | [4.0.2]: https://github.com/jlmakes/scrollreveal/compare/v4.0.1...v4.0.2
530 | [4.0.1]: https://github.com/jlmakes/scrollreveal/compare/v4.0.0...v4.0.1
531 | [4.0.0]: https://github.com/jlmakes/scrollreveal/compare/v3.3.6...v4.0.0
532 | [3.3.6]: https://github.com/jlmakes/scrollreveal/compare/v3.3.5...v3.3.6
533 | [3.3.5]: https://github.com/jlmakes/scrollreveal/compare/v3.3.4...v3.3.5
534 | [3.3.4]: https://github.com/jlmakes/scrollreveal/compare/v3.3.3...v3.3.4
535 | [3.3.3]: https://github.com/jlmakes/scrollreveal/compare/v3.2.2...v3.3.3
536 | [3.3.2]: https://github.com/jlmakes/scrollreveal/compare/v3.3.1...v3.3.2
537 | [3.3.1]: https://github.com/jlmakes/scrollreveal/compare/v3.3.0...v3.3.1
538 | [3.3.0]: https://github.com/jlmakes/scrollreveal/compare/v3.2.0...v3.3.0
539 | [3.2.0]: https://github.com/jlmakes/scrollreveal/compare/v3.1.5...v3.2.0
540 | [3.1.5]: https://github.com/jlmakes/scrollreveal/compare/v3.1.4...v3.1.5
541 | [3.1.4]: https://github.com/jlmakes/scrollreveal/compare/v3.1.3...v3.1.4
542 | [3.1.3]: https://github.com/jlmakes/scrollreveal/compare/v3.1.2...v3.1.3
543 | [3.1.2]: https://github.com/jlmakes/scrollreveal/compare/v3.1.1...v3.1.2
544 | [3.1.1]: https://github.com/jlmakes/scrollreveal/compare/v3.1.0...v3.1.1
545 | [3.1.0]: https://github.com/jlmakes/scrollreveal/compare/v3.0.9...v3.1.0
546 | [3.0.9]: https://github.com/jlmakes/scrollreveal/compare/v3.0.8...v3.0.9
547 | [3.0.8]: https://github.com/jlmakes/scrollreveal/compare/v3.0.7...v3.0.8
548 | [3.0.7]: https://github.com/jlmakes/scrollreveal/compare/v3.0.6...v3.0.7
549 | [3.0.6]: https://github.com/jlmakes/scrollreveal/compare/v3.0.5...v3.0.6
550 | [3.0.5]: https://github.com/jlmakes/scrollreveal/compare/v3.0.4...v3.0.5
551 | [3.0.4]: https://github.com/jlmakes/scrollreveal/compare/v3.0.3...v3.0.4
552 | [3.0.3]: https://github.com/jlmakes/scrollreveal/compare/v3.0.2...v3.0.3
553 | [3.0.2]: https://github.com/jlmakes/scrollreveal/compare/v3.0.1...v3.0.2
554 | [3.0.1]: https://github.com/jlmakes/scrollreveal/compare/v3.0.0...v3.0.1
555 | [3.0.0]: https://github.com/jlmakes/scrollreveal/compare/v2.3.2...v3.0.0
556 | [2.3.2]: https://github.com/jlmakes/scrollreveal/compare/v2.3.1...v2.3.2
557 | [2.3.1]: https://github.com/jlmakes/scrollreveal/compare/v2.3.0...v2.3.1
558 | [2.3.0]: https://github.com/jlmakes/scrollreveal/compare/v2.2.0...v2.3.0
559 | [2.2.0]: https://github.com/jlmakes/scrollreveal/compare/v2.1.0...v2.2.0
560 | [2.1.0]: https://github.com/jlmakes/scrollreveal/compare/v2.0.5...v2.1.0
561 | [2.0.5]: https://github.com/jlmakes/scrollreveal/compare/v2.0.4...v2.0.5
562 | [2.0.4]: https://github.com/jlmakes/scrollreveal/compare/v2.0.3...v2.0.4
563 | [2.0.3]: https://github.com/jlmakes/scrollreveal/compare/v2.0.2...v2.0.3
564 | [2.0.2]: https://github.com/jlmakes/scrollreveal/compare/v2.0.1...v2.0.2
565 | [2.0.1]: https://github.com/jlmakes/scrollreveal/compare/v2.0.0...v2.0.1
566 | [2.0.0]: https://github.com/jlmakes/scrollreveal/tree/v2.0.0
567 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | <p align="center">
  2 | 	<a href="https://scrollrevealjs.org" title="Visit ScrollReveal home page">
  3 | 		<img src="https://scrollrevealjs.org/img/logomark.svg" alt="ScrollReveal" width="120">
  4 | 	</a>
  5 | </p>
  6 | <br>
  7 | <p align="center">
  8 | 	<a href="https://scrollrevealjs.org" title="Visit ScrollReveal home page">
  9 | 		<img width="200" src="https://scrollrevealjs.org/img/scrollreveal-logotype-dark.svg" alt="ScrollReveal">
 10 | 	</a>
 11 | </p>
 12 | <p align="center">Animate elements as they scroll into view.</p>
 13 | 
 14 | <p align="center">
 15 | 	<a href="https://travis-ci.org/jlmakes/scrollreveal">
 16 | 		<img src="https://img.shields.io/travis/jlmakes/scrollreveal.svg" alt="Build status">
 17 | 	</a>
 18 | 	<a href="https://www.npmjs.com/package/scrollreveal">
 19 | 		<img src="https://img.shields.io/npm/dm/scrollreveal.svg" alt="Monthly downloads">
 20 | 	</a>
 21 | 	<a href="https://www.npmjs.com/package/scrollreveal">
 22 | 		<img src="https://img.shields.io/npm/v/scrollreveal.svg" alt="Version">
 23 | 	</a>
 24 | 	<img src="https://img.shields.io/badge/min+gzip-5.7_kB-blue.svg" alt="5.7 kB min+gzip">
 25 | 	<a href="https://opensource.org/licenses/GPL-3.0">
 26 | 		<img src="https://img.shields.io/badge/license-GPLv3-blue.svg" alt="GPLv3 License">
 27 | 	</a>
 28 | </p>
 29 | 
 30 | <br>
 31 | 
 32 | # Installation
 33 | 
 34 | ## Browser
 35 | 
 36 | A simple and fast way to get started is to include this script on your page:
 37 | 
 38 | ```html
 39 | <script src="https://unpkg.com/scrollreveal"></script>
 40 | ```
 41 | 
 42 | This will create the global variable `ScrollReveal`
 43 | 
 44 | > Be careful using this method in production. Without specifying a fixed version number, Unpkg may delay your page load while it resolves the latest version. Learn more at [unpkg.com](https://unpkg.com)
 45 | 
 46 | ## Module
 47 | 
 48 | ```bash
 49 | $ npm install scrollreveal
 50 | ```
 51 | 
 52 | #### CommonJS
 53 | 
 54 | ```js
 55 | const ScrollReveal = require('scrollreveal')
 56 | ```
 57 | 
 58 | #### ES2015
 59 | 
 60 | ```js
 61 | import ScrollReveal from 'scrollreveal'
 62 | ```
 63 | 
 64 | <br>
 65 | 
 66 | # Usage
 67 | 
 68 | Installation provides us with the constructor function [`ScrollReveal()`](https://scrollrevealjs.org/api/constructor.html). Calling this function returns the ScrollReveal instance, the “brain” behind the magic.
 69 | 
 70 | > ScrollReveal employs the singleton pattern; no matter how many times the constructor is called, it will always return the same instance. This means we can call it anywhere, worry-free.
 71 | 
 72 | There’s a lot we can do with this instance, but most of the time we’ll be using the [`reveal()`](https://scrollrevealjs.org/api/reveal.html) method to create animation. Fundamentally, this is how to use ScrollReveal:
 73 | 
 74 | ```html
 75 | <h1 class="headline">
 76 | 	Widget Inc.
 77 | </h1>
 78 | ```
 79 | 
 80 | ```js
 81 | ScrollReveal().reveal('.headline')
 82 | ```
 83 | 
 84 | **🔎 See this demo live on [JSBin](http://jsbin.com/jufohaxonu/edit?html,output)**
 85 | 
 86 | <br>
 87 | 
 88 | ---
 89 | 
 90 | ### The full documentation can be found at [https://scrollrevealjs.org](https://scrollrevealjs.org)
 91 | 
 92 | > If you’re using an older version of ScrollReveal, you can find legacy documentation in the [wiki](https://github.com/jlmakes/scrollreveal/wiki)
 93 | 
 94 | ---
 95 | 
 96 | <br>
 97 | 
 98 | <a href="https://scrollrevealjs.org/pricing/" title="Visit ScrollReveal pricing page">
 99 | 	<img align="right" height="300" src="https://scrollrevealjs.org/img/license.svg" alt="Commercial License Badge">
100 | </a>
101 | 
102 | <br>
103 | 
104 | # License
105 | 
106 | **For commercial sites, themes, projects, and applications, keep your source code private/proprietary by purchasing a [Commercial License](https://scrollrevealjs.org/pricing/).**
107 | 
108 | Licensed under the GNU General Public License 3.0 for compatible open source projects and non-commercial use.
109 | 
110 | <br>
111 | 
112 | Copyright 2023 Fisssion LLC
113 | 


--------------------------------------------------------------------------------
/build/rollup.conf.banner.js:
--------------------------------------------------------------------------------
 1 | const { version } = require('../package.json')
 2 | 
 3 | const banner = `/*! @license ScrollReveal v${version}
 4 | 
 5 | 	Copyright 2021 Fisssion LLC.
 6 | 
 7 | 	Licensed under the GNU General Public License 3.0 for
 8 | 	compatible open source projects and non-commercial use.
 9 | 
10 | 	For commercial sites, themes, projects, and applications,
11 | 	keep your source code private/proprietary by purchasing
12 | 	a commercial license from https://scrollrevealjs.org/
13 | */`
14 | 
15 | export default banner
16 | 


--------------------------------------------------------------------------------
/build/rollup.conf.js:
--------------------------------------------------------------------------------
 1 | import buble from 'rollup-plugin-buble'
 2 | import json from 'rollup-plugin-json'
 3 | import pkg from '../package.json'
 4 | import nodeResolve from 'rollup-plugin-node-resolve'
 5 | import banner from './rollup.conf.banner'
 6 | 
 7 | const base = {
 8 | 	input: './src/index.js',
 9 | 	plugins: [json(), nodeResolve(), buble()]
10 | }
11 | 
12 | const es = Object.assign({}, base, {
13 | 	external: [...Object.keys(pkg.dependencies || {})],
14 | 	output: { banner, format: 'es', file: './dist/scrollreveal.es.js' }
15 | })
16 | 
17 | const umd = Object.assign({}, base, {
18 | 	output: {
19 | 		banner,
20 | 		format: 'umd',
21 | 		file: './dist/scrollreveal.js',
22 | 		name: 'ScrollReveal'
23 | 	}
24 | })
25 | 
26 | export default [es, umd]
27 | 


--------------------------------------------------------------------------------
/build/rollup.conf.min.js:
--------------------------------------------------------------------------------
 1 | import buble from 'rollup-plugin-buble'
 2 | import json from 'rollup-plugin-json'
 3 | import nodeResolve from 'rollup-plugin-node-resolve'
 4 | import strip from 'rollup-plugin-strip'
 5 | import { uglify } from 'rollup-plugin-uglify'
 6 | import banner from './rollup.conf.banner'
 7 | 
 8 | export default {
 9 | 	input: 'src/index.js',
10 | 	plugins: [
11 | 		json(),
12 | 		nodeResolve(),
13 | 		buble(),
14 | 		strip({
15 | 			functions: ['logger'],
16 | 			sourceMaps: false
17 | 		}),
18 | 		uglify({
19 | 			output: {
20 | 				comments: /@license ScrollReveal/
21 | 			}
22 | 		})
23 | 	],
24 | 	output: {
25 | 		banner,
26 | 		format: 'iife',
27 | 		file: 'dist/scrollreveal.min.js',
28 | 		name: 'ScrollReveal'
29 | 	}
30 | }
31 | 


--------------------------------------------------------------------------------
/dist/scrollreveal.es.js:
--------------------------------------------------------------------------------
   1 | /*! @license ScrollReveal v4.0.9
   2 | 
   3 | 	Copyright 2021 Fisssion LLC.
   4 | 
   5 | 	Licensed under the GNU General Public License 3.0 for
   6 | 	compatible open source projects and non-commercial use.
   7 | 
   8 | 	For commercial sites, themes, projects, and applications,
   9 | 	keep your source code private/proprietary by purchasing
  10 | 	a commercial license from https://scrollrevealjs.org/
  11 | */
  12 | import $ from 'tealight';
  13 | import { translateY, translateX, rotateX, rotateY, rotateZ, scale, parse, multiply } from 'rematrix';
  14 | import raf from 'miniraf';
  15 | 
  16 | var defaults = {
  17 | 	delay: 0,
  18 | 	distance: '0',
  19 | 	duration: 600,
  20 | 	easing: 'cubic-bezier(0.5, 0, 0, 1)',
  21 | 	interval: 0,
  22 | 	opacity: 0,
  23 | 	origin: 'bottom',
  24 | 	rotate: {
  25 | 		x: 0,
  26 | 		y: 0,
  27 | 		z: 0
  28 | 	},
  29 | 	scale: 1,
  30 | 	cleanup: false,
  31 | 	container: document.documentElement,
  32 | 	desktop: true,
  33 | 	mobile: true,
  34 | 	reset: false,
  35 | 	useDelay: 'always',
  36 | 	viewFactor: 0.0,
  37 | 	viewOffset: {
  38 | 		top: 0,
  39 | 		right: 0,
  40 | 		bottom: 0,
  41 | 		left: 0
  42 | 	},
  43 | 	afterReset: function afterReset() {},
  44 | 	afterReveal: function afterReveal() {},
  45 | 	beforeReset: function beforeReset() {},
  46 | 	beforeReveal: function beforeReveal() {}
  47 | };
  48 | 
  49 | function failure() {
  50 | 	document.documentElement.classList.remove('sr');
  51 | 
  52 | 	return {
  53 | 		clean: function clean() {},
  54 | 		destroy: function destroy() {},
  55 | 		reveal: function reveal() {},
  56 | 		sync: function sync() {},
  57 | 		get noop() {
  58 | 			return true
  59 | 		}
  60 | 	}
  61 | }
  62 | 
  63 | function success() {
  64 | 	document.documentElement.classList.add('sr');
  65 | 
  66 | 	if (document.body) {
  67 | 		document.body.style.height = '100%';
  68 | 	} else {
  69 | 		document.addEventListener('DOMContentLoaded', function () {
  70 | 			document.body.style.height = '100%';
  71 | 		});
  72 | 	}
  73 | }
  74 | 
  75 | var mount = { success: success, failure: failure };
  76 | 
  77 | function isObject(x) {
  78 | 	return (
  79 | 		x !== null &&
  80 | 		x instanceof Object &&
  81 | 		(x.constructor === Object ||
  82 | 			Object.prototype.toString.call(x) === '[object Object]')
  83 | 	)
  84 | }
  85 | 
  86 | function each(collection, callback) {
  87 | 	if (isObject(collection)) {
  88 | 		var keys = Object.keys(collection);
  89 | 		return keys.forEach(function (key) { return callback(collection[key], key, collection); })
  90 | 	}
  91 | 	if (collection instanceof Array) {
  92 | 		return collection.forEach(function (item, i) { return callback(item, i, collection); })
  93 | 	}
  94 | 	throw new TypeError('Expected either an array or object literal.')
  95 | }
  96 | 
  97 | function logger(message) {
  98 | 	var details = [], len = arguments.length - 1;
  99 | 	while ( len-- > 0 ) details[ len ] = arguments[ len + 1 ];
 100 | 
 101 | 	if (this.constructor.debug && console) {
 102 | 		var report = "%cScrollReveal: " + message;
 103 | 		details.forEach(function (detail) { return (report += "\n — " + detail); });
 104 | 		console.log(report, 'color: #ea654b;'); // eslint-disable-line no-console
 105 | 	}
 106 | }
 107 | 
 108 | function rinse() {
 109 | 	var this$1 = this;
 110 | 
 111 | 	var struct = function () { return ({
 112 | 		active: [],
 113 | 		stale: []
 114 | 	}); };
 115 | 
 116 | 	var elementIds = struct();
 117 | 	var sequenceIds = struct();
 118 | 	var containerIds = struct();
 119 | 
 120 | 	/**
 121 | 	 * Take stock of active element IDs.
 122 | 	 */
 123 | 	try {
 124 | 		each($('[data-sr-id]'), function (node) {
 125 | 			var id = parseInt(node.getAttribute('data-sr-id'));
 126 | 			elementIds.active.push(id);
 127 | 		});
 128 | 	} catch (e) {
 129 | 		throw e
 130 | 	}
 131 | 	/**
 132 | 	 * Destroy stale elements.
 133 | 	 */
 134 | 	each(this.store.elements, function (element) {
 135 | 		if (elementIds.active.indexOf(element.id) === -1) {
 136 | 			elementIds.stale.push(element.id);
 137 | 		}
 138 | 	});
 139 | 
 140 | 	each(elementIds.stale, function (staleId) { return delete this$1.store.elements[staleId]; });
 141 | 
 142 | 	/**
 143 | 	 * Take stock of active container and sequence IDs.
 144 | 	 */
 145 | 	each(this.store.elements, function (element) {
 146 | 		if (containerIds.active.indexOf(element.containerId) === -1) {
 147 | 			containerIds.active.push(element.containerId);
 148 | 		}
 149 | 		if (element.hasOwnProperty('sequence')) {
 150 | 			if (sequenceIds.active.indexOf(element.sequence.id) === -1) {
 151 | 				sequenceIds.active.push(element.sequence.id);
 152 | 			}
 153 | 		}
 154 | 	});
 155 | 
 156 | 	/**
 157 | 	 * Destroy stale containers.
 158 | 	 */
 159 | 	each(this.store.containers, function (container) {
 160 | 		if (containerIds.active.indexOf(container.id) === -1) {
 161 | 			containerIds.stale.push(container.id);
 162 | 		}
 163 | 	});
 164 | 
 165 | 	each(containerIds.stale, function (staleId) {
 166 | 		var stale = this$1.store.containers[staleId].node;
 167 | 		stale.removeEventListener('scroll', this$1.delegate);
 168 | 		stale.removeEventListener('resize', this$1.delegate);
 169 | 		delete this$1.store.containers[staleId];
 170 | 	});
 171 | 
 172 | 	/**
 173 | 	 * Destroy stale sequences.
 174 | 	 */
 175 | 	each(this.store.sequences, function (sequence) {
 176 | 		if (sequenceIds.active.indexOf(sequence.id) === -1) {
 177 | 			sequenceIds.stale.push(sequence.id);
 178 | 		}
 179 | 	});
 180 | 
 181 | 	each(sequenceIds.stale, function (staleId) { return delete this$1.store.sequences[staleId]; });
 182 | }
 183 | 
 184 | var getPrefixedCssProp = (function () {
 185 | 	var properties = {};
 186 | 	var style = document.documentElement.style;
 187 | 
 188 | 	function getPrefixedCssProperty(name, source) {
 189 | 		if ( source === void 0 ) source = style;
 190 | 
 191 | 		if (name && typeof name === 'string') {
 192 | 			if (properties[name]) {
 193 | 				return properties[name]
 194 | 			}
 195 | 			if (typeof source[name] === 'string') {
 196 | 				return (properties[name] = name)
 197 | 			}
 198 | 			if (typeof source[("-webkit-" + name)] === 'string') {
 199 | 				return (properties[name] = "-webkit-" + name)
 200 | 			}
 201 | 			throw new RangeError(("Unable to find \"" + name + "\" style property."))
 202 | 		}
 203 | 		throw new TypeError('Expected a string.')
 204 | 	}
 205 | 
 206 | 	getPrefixedCssProperty.clearCache = function () { return (properties = {}); };
 207 | 
 208 | 	return getPrefixedCssProperty
 209 | })();
 210 | 
 211 | function style(element) {
 212 | 	var computed = window.getComputedStyle(element.node);
 213 | 	var position = computed.position;
 214 | 	var config = element.config;
 215 | 
 216 | 	/**
 217 | 	 * Generate inline styles
 218 | 	 */
 219 | 	var inline = {};
 220 | 	var inlineStyle = element.node.getAttribute('style') || '';
 221 | 	var inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || [];
 222 | 
 223 | 	inline.computed = inlineMatch ? inlineMatch.map(function (m) { return m.trim(); }).join('; ') + ';' : '';
 224 | 
 225 | 	inline.generated = inlineMatch.some(function (m) { return m.match(/visibility\s?:\s?visible/i); })
 226 | 		? inline.computed
 227 | 		: inlineMatch.concat( ['visibility: visible']).map(function (m) { return m.trim(); }).join('; ') + ';';
 228 | 
 229 | 	/**
 230 | 	 * Generate opacity styles
 231 | 	 */
 232 | 	var computedOpacity = parseFloat(computed.opacity);
 233 | 	var configOpacity = !isNaN(parseFloat(config.opacity))
 234 | 		? parseFloat(config.opacity)
 235 | 		: parseFloat(computed.opacity);
 236 | 
 237 | 	var opacity = {
 238 | 		computed: computedOpacity !== configOpacity ? ("opacity: " + computedOpacity + ";") : '',
 239 | 		generated: computedOpacity !== configOpacity ? ("opacity: " + configOpacity + ";") : ''
 240 | 	};
 241 | 
 242 | 	/**
 243 | 	 * Generate transformation styles
 244 | 	 */
 245 | 	var transformations = [];
 246 | 
 247 | 	if (parseFloat(config.distance)) {
 248 | 		var axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X';
 249 | 
 250 | 		/**
 251 | 		 * Let’s make sure our our pixel distances are negative for top and left.
 252 | 		 * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS.
 253 | 		 */
 254 | 		var distance = config.distance;
 255 | 		if (config.origin === 'top' || config.origin === 'left') {
 256 | 			distance = /^-/.test(distance) ? distance.substr(1) : ("-" + distance);
 257 | 		}
 258 | 
 259 | 		var ref = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g);
 260 | 		var value = ref[0];
 261 | 		var unit = ref[1];
 262 | 
 263 | 		switch (unit) {
 264 | 			case 'em':
 265 | 				distance = parseInt(computed.fontSize) * value;
 266 | 				break
 267 | 			case 'px':
 268 | 				distance = value;
 269 | 				break
 270 | 			case '%':
 271 | 				/**
 272 | 				 * Here we use `getBoundingClientRect` instead of
 273 | 				 * the existing data attached to `element.geometry`
 274 | 				 * because only the former includes any transformations
 275 | 				 * current applied to the element.
 276 | 				 *
 277 | 				 * If that behavior ends up being unintuitive, this
 278 | 				 * logic could instead utilize `element.geometry.height`
 279 | 				 * and `element.geoemetry.width` for the distance calculation
 280 | 				 */
 281 | 				distance =
 282 | 					axis === 'Y'
 283 | 						? (element.node.getBoundingClientRect().height * value) / 100
 284 | 						: (element.node.getBoundingClientRect().width * value) / 100;
 285 | 				break
 286 | 			default:
 287 | 				throw new RangeError('Unrecognized or missing distance unit.')
 288 | 		}
 289 | 
 290 | 		if (axis === 'Y') {
 291 | 			transformations.push(translateY(distance));
 292 | 		} else {
 293 | 			transformations.push(translateX(distance));
 294 | 		}
 295 | 	}
 296 | 
 297 | 	if (config.rotate.x) { transformations.push(rotateX(config.rotate.x)); }
 298 | 	if (config.rotate.y) { transformations.push(rotateY(config.rotate.y)); }
 299 | 	if (config.rotate.z) { transformations.push(rotateZ(config.rotate.z)); }
 300 | 	if (config.scale !== 1) {
 301 | 		if (config.scale === 0) {
 302 | 			/**
 303 | 			 * The CSS Transforms matrix interpolation specification
 304 | 			 * basically disallows transitions of non-invertible
 305 | 			 * matrixes, which means browsers won't transition
 306 | 			 * elements with zero scale.
 307 | 			 *
 308 | 			 * That’s inconvenient for the API and developer
 309 | 			 * experience, so we simply nudge their value
 310 | 			 * slightly above zero; this allows browsers
 311 | 			 * to transition our element as expected.
 312 | 			 *
 313 | 			 * `0.0002` was the smallest number
 314 | 			 * that performed across browsers.
 315 | 			 */
 316 | 			transformations.push(scale(0.0002));
 317 | 		} else {
 318 | 			transformations.push(scale(config.scale));
 319 | 		}
 320 | 	}
 321 | 
 322 | 	var transform = {};
 323 | 	if (transformations.length) {
 324 | 		transform.property = getPrefixedCssProp('transform');
 325 | 		/**
 326 | 		 * The default computed transform value should be one of:
 327 | 		 * undefined || 'none' || 'matrix()' || 'matrix3d()'
 328 | 		 */
 329 | 		transform.computed = {
 330 | 			raw: computed[transform.property],
 331 | 			matrix: parse(computed[transform.property])
 332 | 		};
 333 | 
 334 | 		transformations.unshift(transform.computed.matrix);
 335 | 		var product = transformations.reduce(multiply);
 336 | 
 337 | 		transform.generated = {
 338 | 			initial: ((transform.property) + ": matrix3d(" + (product.join(', ')) + ");"),
 339 | 			final: ((transform.property) + ": matrix3d(" + (transform.computed.matrix.join(', ')) + ");")
 340 | 		};
 341 | 	} else {
 342 | 		transform.generated = {
 343 | 			initial: '',
 344 | 			final: ''
 345 | 		};
 346 | 	}
 347 | 
 348 | 	/**
 349 | 	 * Generate transition styles
 350 | 	 */
 351 | 	var transition = {};
 352 | 	if (opacity.generated || transform.generated.initial) {
 353 | 		transition.property = getPrefixedCssProp('transition');
 354 | 		transition.computed = computed[transition.property];
 355 | 		transition.fragments = [];
 356 | 
 357 | 		var delay = config.delay;
 358 | 		var duration = config.duration;
 359 | 		var easing = config.easing;
 360 | 
 361 | 		if (opacity.generated) {
 362 | 			transition.fragments.push({
 363 | 				delayed: ("opacity " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"),
 364 | 				instant: ("opacity " + (duration / 1000) + "s " + easing + " 0s")
 365 | 			});
 366 | 		}
 367 | 
 368 | 		if (transform.generated.initial) {
 369 | 			transition.fragments.push({
 370 | 				delayed: ((transform.property) + " " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"),
 371 | 				instant: ((transform.property) + " " + (duration / 1000) + "s " + easing + " 0s")
 372 | 			});
 373 | 		}
 374 | 
 375 | 		/**
 376 | 		 * The default computed transition property should be undefined, or one of:
 377 | 		 * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()'
 378 | 		 */
 379 | 		var hasCustomTransition =
 380 | 			transition.computed && !transition.computed.match(/all 0s|none 0s/);
 381 | 
 382 | 		if (hasCustomTransition) {
 383 | 			transition.fragments.unshift({
 384 | 				delayed: transition.computed,
 385 | 				instant: transition.computed
 386 | 			});
 387 | 		}
 388 | 
 389 | 		var composed = transition.fragments.reduce(
 390 | 			function (composition, fragment, i) {
 391 | 				composition.delayed += i === 0 ? fragment.delayed : (", " + (fragment.delayed));
 392 | 				composition.instant += i === 0 ? fragment.instant : (", " + (fragment.instant));
 393 | 				return composition
 394 | 			},
 395 | 			{
 396 | 				delayed: '',
 397 | 				instant: ''
 398 | 			}
 399 | 		);
 400 | 
 401 | 		transition.generated = {
 402 | 			delayed: ((transition.property) + ": " + (composed.delayed) + ";"),
 403 | 			instant: ((transition.property) + ": " + (composed.instant) + ";")
 404 | 		};
 405 | 	} else {
 406 | 		transition.generated = {
 407 | 			delayed: '',
 408 | 			instant: ''
 409 | 		};
 410 | 	}
 411 | 
 412 | 	return {
 413 | 		inline: inline,
 414 | 		opacity: opacity,
 415 | 		position: position,
 416 | 		transform: transform,
 417 | 		transition: transition
 418 | 	}
 419 | }
 420 | 
 421 | /**
 422 |  * apply a CSS string to an element using the CSSOM (element.style) rather
 423 |  * than setAttribute, which may violate the content security policy.
 424 |  *
 425 |  * @param {Node}   [el]  Element to receive styles.
 426 |  * @param {string} [declaration] Styles to apply.
 427 |  */
 428 | function applyStyle (el, declaration) {
 429 | 	declaration.split(';').forEach(function (pair) {
 430 | 		var ref = pair.split(':');
 431 | 		var property = ref[0];
 432 | 		var value = ref.slice(1);
 433 | 		if (property && value) {
 434 | 			el.style[property.trim()] = value.join(':');
 435 | 		}
 436 | 	});
 437 | }
 438 | 
 439 | function clean(target) {
 440 | 	var this$1 = this;
 441 | 
 442 | 	var dirty;
 443 | 	try {
 444 | 		each($(target), function (node) {
 445 | 			var id = node.getAttribute('data-sr-id');
 446 | 			if (id !== null) {
 447 | 				dirty = true;
 448 | 				var element = this$1.store.elements[id];
 449 | 				if (element.callbackTimer) {
 450 | 					window.clearTimeout(element.callbackTimer.clock);
 451 | 				}
 452 | 				applyStyle(element.node, element.styles.inline.generated);
 453 | 				node.removeAttribute('data-sr-id');
 454 | 				delete this$1.store.elements[id];
 455 | 			}
 456 | 		});
 457 | 	} catch (e) {
 458 | 		return logger.call(this, 'Clean failed.', e.message)
 459 | 	}
 460 | 
 461 | 	if (dirty) {
 462 | 		try {
 463 | 			rinse.call(this);
 464 | 		} catch (e) {
 465 | 			return logger.call(this, 'Clean failed.', e.message)
 466 | 		}
 467 | 	}
 468 | }
 469 | 
 470 | function destroy() {
 471 | 	var this$1 = this;
 472 | 
 473 | 	/**
 474 | 	 * Remove all generated styles and element ids
 475 | 	 */
 476 | 	each(this.store.elements, function (element) {
 477 | 		applyStyle(element.node, element.styles.inline.generated);
 478 | 		element.node.removeAttribute('data-sr-id');
 479 | 	});
 480 | 
 481 | 	/**
 482 | 	 * Remove all event listeners.
 483 | 	 */
 484 | 	each(this.store.containers, function (container) {
 485 | 		var target =
 486 | 			container.node === document.documentElement ? window : container.node;
 487 | 		target.removeEventListener('scroll', this$1.delegate);
 488 | 		target.removeEventListener('resize', this$1.delegate);
 489 | 	});
 490 | 
 491 | 	/**
 492 | 	 * Clear all data from the store
 493 | 	 */
 494 | 	this.store = {
 495 | 		containers: {},
 496 | 		elements: {},
 497 | 		history: [],
 498 | 		sequences: {}
 499 | 	};
 500 | }
 501 | 
 502 | function deepAssign(target) {
 503 | 	var sources = [], len = arguments.length - 1;
 504 | 	while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ];
 505 | 
 506 | 	if (isObject(target)) {
 507 | 		each(sources, function (source) {
 508 | 			each(source, function (data, key) {
 509 | 				if (isObject(data)) {
 510 | 					if (!target[key] || !isObject(target[key])) {
 511 | 						target[key] = {};
 512 | 					}
 513 | 					deepAssign(target[key], data);
 514 | 				} else {
 515 | 					target[key] = data;
 516 | 				}
 517 | 			});
 518 | 		});
 519 | 		return target
 520 | 	} else {
 521 | 		throw new TypeError('Target must be an object literal.')
 522 | 	}
 523 | }
 524 | 
 525 | function isMobile(agent) {
 526 | 	if ( agent === void 0 ) agent = navigator.userAgent;
 527 | 
 528 | 	return /Android|iPhone|iPad|iPod/i.test(agent)
 529 | }
 530 | 
 531 | var nextUniqueId = (function () {
 532 | 	var uid = 0;
 533 | 	return function () { return uid++; }
 534 | })();
 535 | 
 536 | function initialize() {
 537 | 	var this$1 = this;
 538 | 
 539 | 	rinse.call(this);
 540 | 
 541 | 	each(this.store.elements, function (element) {
 542 | 		var styles = [element.styles.inline.generated];
 543 | 
 544 | 		if (element.visible) {
 545 | 			styles.push(element.styles.opacity.computed);
 546 | 			styles.push(element.styles.transform.generated.final);
 547 | 			element.revealed = true;
 548 | 		} else {
 549 | 			styles.push(element.styles.opacity.generated);
 550 | 			styles.push(element.styles.transform.generated.initial);
 551 | 			element.revealed = false;
 552 | 		}
 553 | 
 554 | 		applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 555 | 	});
 556 | 
 557 | 	each(this.store.containers, function (container) {
 558 | 		var target =
 559 | 			container.node === document.documentElement ? window : container.node;
 560 | 		target.addEventListener('scroll', this$1.delegate);
 561 | 		target.addEventListener('resize', this$1.delegate);
 562 | 	});
 563 | 
 564 | 	/**
 565 | 	 * Manually invoke delegate once to capture
 566 | 	 * element and container dimensions, container
 567 | 	 * scroll position, and trigger any valid reveals
 568 | 	 */
 569 | 	this.delegate();
 570 | 
 571 | 	/**
 572 | 	 * Wipe any existing `setTimeout` now
 573 | 	 * that initialization has completed.
 574 | 	 */
 575 | 	this.initTimeout = null;
 576 | }
 577 | 
 578 | function animate(element, force) {
 579 | 	if ( force === void 0 ) force = {};
 580 | 
 581 | 	var pristine = force.pristine || this.pristine;
 582 | 	var delayed =
 583 | 		element.config.useDelay === 'always' ||
 584 | 		(element.config.useDelay === 'onload' && pristine) ||
 585 | 		(element.config.useDelay === 'once' && !element.seen);
 586 | 
 587 | 	var shouldReveal = element.visible && !element.revealed;
 588 | 	var shouldReset = !element.visible && element.revealed && element.config.reset;
 589 | 
 590 | 	if (force.reveal || shouldReveal) {
 591 | 		return triggerReveal.call(this, element, delayed)
 592 | 	}
 593 | 
 594 | 	if (force.reset || shouldReset) {
 595 | 		return triggerReset.call(this, element)
 596 | 	}
 597 | }
 598 | 
 599 | function triggerReveal(element, delayed) {
 600 | 	var styles = [
 601 | 		element.styles.inline.generated,
 602 | 		element.styles.opacity.computed,
 603 | 		element.styles.transform.generated.final
 604 | 	];
 605 | 	if (delayed) {
 606 | 		styles.push(element.styles.transition.generated.delayed);
 607 | 	} else {
 608 | 		styles.push(element.styles.transition.generated.instant);
 609 | 	}
 610 | 	element.revealed = element.seen = true;
 611 | 	applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 612 | 	registerCallbacks.call(this, element, delayed);
 613 | }
 614 | 
 615 | function triggerReset(element) {
 616 | 	var styles = [
 617 | 		element.styles.inline.generated,
 618 | 		element.styles.opacity.generated,
 619 | 		element.styles.transform.generated.initial,
 620 | 		element.styles.transition.generated.instant
 621 | 	];
 622 | 	element.revealed = false;
 623 | 	applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 624 | 	registerCallbacks.call(this, element);
 625 | }
 626 | 
 627 | function registerCallbacks(element, isDelayed) {
 628 | 	var this$1 = this;
 629 | 
 630 | 	var duration = isDelayed
 631 | 		? element.config.duration + element.config.delay
 632 | 		: element.config.duration;
 633 | 
 634 | 	var beforeCallback = element.revealed
 635 | 		? element.config.beforeReveal
 636 | 		: element.config.beforeReset;
 637 | 
 638 | 	var afterCallback = element.revealed
 639 | 		? element.config.afterReveal
 640 | 		: element.config.afterReset;
 641 | 
 642 | 	var elapsed = 0;
 643 | 	if (element.callbackTimer) {
 644 | 		elapsed = Date.now() - element.callbackTimer.start;
 645 | 		window.clearTimeout(element.callbackTimer.clock);
 646 | 	}
 647 | 
 648 | 	beforeCallback(element.node);
 649 | 
 650 | 	element.callbackTimer = {
 651 | 		start: Date.now(),
 652 | 		clock: window.setTimeout(function () {
 653 | 			afterCallback(element.node);
 654 | 			element.callbackTimer = null;
 655 | 			if (element.revealed && !element.config.reset && element.config.cleanup) {
 656 | 				clean.call(this$1, element.node);
 657 | 			}
 658 | 		}, duration - elapsed)
 659 | 	};
 660 | }
 661 | 
 662 | function sequence(element, pristine) {
 663 | 	if ( pristine === void 0 ) pristine = this.pristine;
 664 | 
 665 | 	/**
 666 | 	 * We first check if the element should reset.
 667 | 	 */
 668 | 	if (!element.visible && element.revealed && element.config.reset) {
 669 | 		return animate.call(this, element, { reset: true })
 670 | 	}
 671 | 
 672 | 	var seq = this.store.sequences[element.sequence.id];
 673 | 	var i = element.sequence.index;
 674 | 
 675 | 	if (seq) {
 676 | 		var visible = new SequenceModel(seq, 'visible', this.store);
 677 | 		var revealed = new SequenceModel(seq, 'revealed', this.store);
 678 | 
 679 | 		seq.models = { visible: visible, revealed: revealed };
 680 | 
 681 | 		/**
 682 | 		 * If the sequence has no revealed members,
 683 | 		 * then we reveal the first visible element
 684 | 		 * within that sequence.
 685 | 		 *
 686 | 		 * The sequence then cues a recursive call
 687 | 		 * in both directions.
 688 | 		 */
 689 | 		if (!revealed.body.length) {
 690 | 			var nextId = seq.members[visible.body[0]];
 691 | 			var nextElement = this.store.elements[nextId];
 692 | 
 693 | 			if (nextElement) {
 694 | 				cue.call(this, seq, visible.body[0], -1, pristine);
 695 | 				cue.call(this, seq, visible.body[0], +1, pristine);
 696 | 				return animate.call(this, nextElement, { reveal: true, pristine: pristine })
 697 | 			}
 698 | 		}
 699 | 
 700 | 		/**
 701 | 		 * If our element isn’t resetting, we check the
 702 | 		 * element sequence index against the head, and
 703 | 		 * then the foot of the sequence.
 704 | 		 */
 705 | 		if (
 706 | 			!seq.blocked.head &&
 707 | 			i === [].concat( revealed.head ).pop() &&
 708 | 			i >= [].concat( visible.body ).shift()
 709 | 		) {
 710 | 			cue.call(this, seq, i, -1, pristine);
 711 | 			return animate.call(this, element, { reveal: true, pristine: pristine })
 712 | 		}
 713 | 
 714 | 		if (
 715 | 			!seq.blocked.foot &&
 716 | 			i === [].concat( revealed.foot ).shift() &&
 717 | 			i <= [].concat( visible.body ).pop()
 718 | 		) {
 719 | 			cue.call(this, seq, i, +1, pristine);
 720 | 			return animate.call(this, element, { reveal: true, pristine: pristine })
 721 | 		}
 722 | 	}
 723 | }
 724 | 
 725 | function Sequence(interval) {
 726 | 	var i = Math.abs(interval);
 727 | 	if (!isNaN(i)) {
 728 | 		this.id = nextUniqueId();
 729 | 		this.interval = Math.max(i, 16);
 730 | 		this.members = [];
 731 | 		this.models = {};
 732 | 		this.blocked = {
 733 | 			head: false,
 734 | 			foot: false
 735 | 		};
 736 | 	} else {
 737 | 		throw new RangeError('Invalid sequence interval.')
 738 | 	}
 739 | }
 740 | 
 741 | function SequenceModel(seq, prop, store) {
 742 | 	var this$1 = this;
 743 | 
 744 | 	this.head = [];
 745 | 	this.body = [];
 746 | 	this.foot = [];
 747 | 
 748 | 	each(seq.members, function (id, index) {
 749 | 		var element = store.elements[id];
 750 | 		if (element && element[prop]) {
 751 | 			this$1.body.push(index);
 752 | 		}
 753 | 	});
 754 | 
 755 | 	if (this.body.length) {
 756 | 		each(seq.members, function (id, index) {
 757 | 			var element = store.elements[id];
 758 | 			if (element && !element[prop]) {
 759 | 				if (index < this$1.body[0]) {
 760 | 					this$1.head.push(index);
 761 | 				} else {
 762 | 					this$1.foot.push(index);
 763 | 				}
 764 | 			}
 765 | 		});
 766 | 	}
 767 | }
 768 | 
 769 | function cue(seq, i, direction, pristine) {
 770 | 	var this$1 = this;
 771 | 
 772 | 	var blocked = ['head', null, 'foot'][1 + direction];
 773 | 	var nextId = seq.members[i + direction];
 774 | 	var nextElement = this.store.elements[nextId];
 775 | 
 776 | 	seq.blocked[blocked] = true;
 777 | 
 778 | 	setTimeout(function () {
 779 | 		seq.blocked[blocked] = false;
 780 | 		if (nextElement) {
 781 | 			sequence.call(this$1, nextElement, pristine);
 782 | 		}
 783 | 	}, seq.interval);
 784 | }
 785 | 
 786 | function reveal(target, options, syncing) {
 787 | 	var this$1 = this;
 788 | 	if ( options === void 0 ) options = {};
 789 | 	if ( syncing === void 0 ) syncing = false;
 790 | 
 791 | 	var containerBuffer = [];
 792 | 	var sequence$1;
 793 | 	var interval = options.interval || defaults.interval;
 794 | 
 795 | 	try {
 796 | 		if (interval) {
 797 | 			sequence$1 = new Sequence(interval);
 798 | 		}
 799 | 
 800 | 		var nodes = $(target);
 801 | 		if (!nodes.length) {
 802 | 			throw new Error('Invalid reveal target.')
 803 | 		}
 804 | 
 805 | 		var elements = nodes.reduce(function (elementBuffer, elementNode) {
 806 | 			var element = {};
 807 | 			var existingId = elementNode.getAttribute('data-sr-id');
 808 | 
 809 | 			if (existingId) {
 810 | 				deepAssign(element, this$1.store.elements[existingId]);
 811 | 
 812 | 				/**
 813 | 				 * In order to prevent previously generated styles
 814 | 				 * from throwing off the new styles, the style tag
 815 | 				 * has to be reverted to its pre-reveal state.
 816 | 				 */
 817 | 				applyStyle(element.node, element.styles.inline.computed);
 818 | 			} else {
 819 | 				element.id = nextUniqueId();
 820 | 				element.node = elementNode;
 821 | 				element.seen = false;
 822 | 				element.revealed = false;
 823 | 				element.visible = false;
 824 | 			}
 825 | 
 826 | 			var config = deepAssign({}, element.config || this$1.defaults, options);
 827 | 
 828 | 			if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
 829 | 				if (existingId) {
 830 | 					clean.call(this$1, element);
 831 | 				}
 832 | 				return elementBuffer // skip elements that are disabled
 833 | 			}
 834 | 
 835 | 			var containerNode = $(config.container)[0];
 836 | 			if (!containerNode) {
 837 | 				throw new Error('Invalid container.')
 838 | 			}
 839 | 			if (!containerNode.contains(elementNode)) {
 840 | 				return elementBuffer // skip elements found outside the container
 841 | 			}
 842 | 
 843 | 			var containerId;
 844 | 			{
 845 | 				containerId = getContainerId(
 846 | 					containerNode,
 847 | 					containerBuffer,
 848 | 					this$1.store.containers
 849 | 				);
 850 | 				if (containerId === null) {
 851 | 					containerId = nextUniqueId();
 852 | 					containerBuffer.push({ id: containerId, node: containerNode });
 853 | 				}
 854 | 			}
 855 | 
 856 | 			element.config = config;
 857 | 			element.containerId = containerId;
 858 | 			element.styles = style(element);
 859 | 
 860 | 			if (sequence$1) {
 861 | 				element.sequence = {
 862 | 					id: sequence$1.id,
 863 | 					index: sequence$1.members.length
 864 | 				};
 865 | 				sequence$1.members.push(element.id);
 866 | 			}
 867 | 
 868 | 			elementBuffer.push(element);
 869 | 			return elementBuffer
 870 | 		}, []);
 871 | 
 872 | 		/**
 873 | 		 * Modifying the DOM via setAttribute needs to be handled
 874 | 		 * separately from reading computed styles in the map above
 875 | 		 * for the browser to batch DOM changes (limiting reflows)
 876 | 		 */
 877 | 		each(elements, function (element) {
 878 | 			this$1.store.elements[element.id] = element;
 879 | 			element.node.setAttribute('data-sr-id', element.id);
 880 | 		});
 881 | 	} catch (e) {
 882 | 		return logger.call(this, 'Reveal failed.', e.message)
 883 | 	}
 884 | 
 885 | 	/**
 886 | 	 * Now that element set-up is complete...
 887 | 	 * Let’s commit any container and sequence data we have to the store.
 888 | 	 */
 889 | 	each(containerBuffer, function (container) {
 890 | 		this$1.store.containers[container.id] = {
 891 | 			id: container.id,
 892 | 			node: container.node
 893 | 		};
 894 | 	});
 895 | 	if (sequence$1) {
 896 | 		this.store.sequences[sequence$1.id] = sequence$1;
 897 | 	}
 898 | 
 899 | 	/**
 900 | 	 * If reveal wasn't invoked by sync, we want to
 901 | 	 * make sure to add this call to the history.
 902 | 	 */
 903 | 	if (syncing !== true) {
 904 | 		this.store.history.push({ target: target, options: options });
 905 | 
 906 | 		/**
 907 | 		 * Push initialization to the event queue, giving
 908 | 		 * multiple reveal calls time to be interpreted.
 909 | 		 */
 910 | 		if (this.initTimeout) {
 911 | 			window.clearTimeout(this.initTimeout);
 912 | 		}
 913 | 		this.initTimeout = window.setTimeout(initialize.bind(this), 0);
 914 | 	}
 915 | }
 916 | 
 917 | function getContainerId(node) {
 918 | 	var collections = [], len = arguments.length - 1;
 919 | 	while ( len-- > 0 ) collections[ len ] = arguments[ len + 1 ];
 920 | 
 921 | 	var id = null;
 922 | 	each(collections, function (collection) {
 923 | 		each(collection, function (container) {
 924 | 			if (id === null && container.node === node) {
 925 | 				id = container.id;
 926 | 			}
 927 | 		});
 928 | 	});
 929 | 	return id
 930 | }
 931 | 
 932 | /**
 933 |  * Re-runs the reveal method for each record stored in history,
 934 |  * for capturing new content asynchronously loaded into the DOM.
 935 |  */
 936 | function sync() {
 937 | 	var this$1 = this;
 938 | 
 939 | 	each(this.store.history, function (record) {
 940 | 		reveal.call(this$1, record.target, record.options, true);
 941 | 	});
 942 | 
 943 | 	initialize.call(this);
 944 | }
 945 | 
 946 | var polyfill = function (x) { return (x > 0) - (x < 0) || +x; };
 947 | var mathSign = Math.sign || polyfill;
 948 | 
 949 | function getGeometry(target, isContainer) {
 950 | 	/**
 951 | 	 * We want to ignore padding and scrollbars for container elements.
 952 | 	 * More information here: https://goo.gl/vOZpbz
 953 | 	 */
 954 | 	var height = isContainer ? target.node.clientHeight : target.node.offsetHeight;
 955 | 	var width = isContainer ? target.node.clientWidth : target.node.offsetWidth;
 956 | 
 957 | 	var offsetTop = 0;
 958 | 	var offsetLeft = 0;
 959 | 	var node = target.node;
 960 | 
 961 | 	do {
 962 | 		if (!isNaN(node.offsetTop)) {
 963 | 			offsetTop += node.offsetTop;
 964 | 		}
 965 | 		if (!isNaN(node.offsetLeft)) {
 966 | 			offsetLeft += node.offsetLeft;
 967 | 		}
 968 | 		node = node.offsetParent;
 969 | 	} while (node)
 970 | 
 971 | 	return {
 972 | 		bounds: {
 973 | 			top: offsetTop,
 974 | 			right: offsetLeft + width,
 975 | 			bottom: offsetTop + height,
 976 | 			left: offsetLeft
 977 | 		},
 978 | 		height: height,
 979 | 		width: width
 980 | 	}
 981 | }
 982 | 
 983 | function getScrolled(container) {
 984 | 	var top, left;
 985 | 	if (container.node === document.documentElement) {
 986 | 		top = window.pageYOffset;
 987 | 		left = window.pageXOffset;
 988 | 	} else {
 989 | 		top = container.node.scrollTop;
 990 | 		left = container.node.scrollLeft;
 991 | 	}
 992 | 	return { top: top, left: left }
 993 | }
 994 | 
 995 | function isElementVisible(element) {
 996 | 	if ( element === void 0 ) element = {};
 997 | 
 998 | 	var container = this.store.containers[element.containerId];
 999 | 	if (!container) { return }
1000 | 
1001 | 	var viewFactor = Math.max(0, Math.min(1, element.config.viewFactor));
1002 | 	var viewOffset = element.config.viewOffset;
1003 | 
1004 | 	var elementBounds = {
1005 | 		top: element.geometry.bounds.top + element.geometry.height * viewFactor,
1006 | 		right: element.geometry.bounds.right - element.geometry.width * viewFactor,
1007 | 		bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor,
1008 | 		left: element.geometry.bounds.left + element.geometry.width * viewFactor
1009 | 	};
1010 | 
1011 | 	var containerBounds = {
1012 | 		top: container.geometry.bounds.top + container.scroll.top + viewOffset.top,
1013 | 		right: container.geometry.bounds.right + container.scroll.left - viewOffset.right,
1014 | 		bottom:
1015 | 			container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom,
1016 | 		left: container.geometry.bounds.left + container.scroll.left + viewOffset.left
1017 | 	};
1018 | 
1019 | 	return (
1020 | 		(elementBounds.top < containerBounds.bottom &&
1021 | 			elementBounds.right > containerBounds.left &&
1022 | 			elementBounds.bottom > containerBounds.top &&
1023 | 			elementBounds.left < containerBounds.right) ||
1024 | 		element.styles.position === 'fixed'
1025 | 	)
1026 | }
1027 | 
1028 | function delegate(
1029 | 	event,
1030 | 	elements
1031 | ) {
1032 | 	var this$1 = this;
1033 | 	if ( event === void 0 ) event = { type: 'init' };
1034 | 	if ( elements === void 0 ) elements = this.store.elements;
1035 | 
1036 | 	raf(function () {
1037 | 		var stale = event.type === 'init' || event.type === 'resize';
1038 | 
1039 | 		each(this$1.store.containers, function (container) {
1040 | 			if (stale) {
1041 | 				container.geometry = getGeometry.call(this$1, container, true);
1042 | 			}
1043 | 			var scroll = getScrolled.call(this$1, container);
1044 | 			if (container.scroll) {
1045 | 				container.direction = {
1046 | 					x: mathSign(scroll.left - container.scroll.left),
1047 | 					y: mathSign(scroll.top - container.scroll.top)
1048 | 				};
1049 | 			}
1050 | 			container.scroll = scroll;
1051 | 		});
1052 | 
1053 | 		/**
1054 | 		 * Due to how the sequencer is implemented, it’s
1055 | 		 * important that we update the state of all
1056 | 		 * elements, before any animation logic is
1057 | 		 * evaluated (in the second loop below).
1058 | 		 */
1059 | 		each(elements, function (element) {
1060 | 			if (stale || element.geometry === undefined) {
1061 | 				element.geometry = getGeometry.call(this$1, element);
1062 | 			}
1063 | 			element.visible = isElementVisible.call(this$1, element);
1064 | 		});
1065 | 
1066 | 		each(elements, function (element) {
1067 | 			if (element.sequence) {
1068 | 				sequence.call(this$1, element);
1069 | 			} else {
1070 | 				animate.call(this$1, element);
1071 | 			}
1072 | 		});
1073 | 
1074 | 		this$1.pristine = false;
1075 | 	});
1076 | }
1077 | 
1078 | function isTransformSupported() {
1079 | 	var style = document.documentElement.style;
1080 | 	return 'transform' in style || 'WebkitTransform' in style
1081 | }
1082 | 
1083 | function isTransitionSupported() {
1084 | 	var style = document.documentElement.style;
1085 | 	return 'transition' in style || 'WebkitTransition' in style
1086 | }
1087 | 
1088 | var version = "4.0.9";
1089 | 
1090 | var boundDelegate;
1091 | var boundDestroy;
1092 | var boundReveal;
1093 | var boundClean;
1094 | var boundSync;
1095 | var config;
1096 | var debug;
1097 | var instance;
1098 | 
1099 | function ScrollReveal(options) {
1100 | 	if ( options === void 0 ) options = {};
1101 | 
1102 | 	var invokedWithoutNew =
1103 | 		typeof this === 'undefined' ||
1104 | 		Object.getPrototypeOf(this) !== ScrollReveal.prototype;
1105 | 
1106 | 	if (invokedWithoutNew) {
1107 | 		return new ScrollReveal(options)
1108 | 	}
1109 | 
1110 | 	if (!ScrollReveal.isSupported()) {
1111 | 		logger.call(this, 'Instantiation failed.', 'This browser is not supported.');
1112 | 		return mount.failure()
1113 | 	}
1114 | 
1115 | 	var buffer;
1116 | 	try {
1117 | 		buffer = config
1118 | 			? deepAssign({}, config, options)
1119 | 			: deepAssign({}, defaults, options);
1120 | 	} catch (e) {
1121 | 		logger.call(this, 'Invalid configuration.', e.message);
1122 | 		return mount.failure()
1123 | 	}
1124 | 
1125 | 	try {
1126 | 		var container = $(buffer.container)[0];
1127 | 		if (!container) {
1128 | 			throw new Error('Invalid container.')
1129 | 		}
1130 | 	} catch (e) {
1131 | 		logger.call(this, e.message);
1132 | 		return mount.failure()
1133 | 	}
1134 | 
1135 | 	config = buffer;
1136 | 
1137 | 	if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
1138 | 		logger.call(
1139 | 			this,
1140 | 			'This device is disabled.',
1141 | 			("desktop: " + (config.desktop)),
1142 | 			("mobile: " + (config.mobile))
1143 | 		);
1144 | 		return mount.failure()
1145 | 	}
1146 | 
1147 | 	mount.success();
1148 | 
1149 | 	this.store = {
1150 | 		containers: {},
1151 | 		elements: {},
1152 | 		history: [],
1153 | 		sequences: {}
1154 | 	};
1155 | 
1156 | 	this.pristine = true;
1157 | 
1158 | 	boundDelegate = boundDelegate || delegate.bind(this);
1159 | 	boundDestroy = boundDestroy || destroy.bind(this);
1160 | 	boundReveal = boundReveal || reveal.bind(this);
1161 | 	boundClean = boundClean || clean.bind(this);
1162 | 	boundSync = boundSync || sync.bind(this);
1163 | 
1164 | 	Object.defineProperty(this, 'delegate', { get: function () { return boundDelegate; } });
1165 | 	Object.defineProperty(this, 'destroy', { get: function () { return boundDestroy; } });
1166 | 	Object.defineProperty(this, 'reveal', { get: function () { return boundReveal; } });
1167 | 	Object.defineProperty(this, 'clean', { get: function () { return boundClean; } });
1168 | 	Object.defineProperty(this, 'sync', { get: function () { return boundSync; } });
1169 | 
1170 | 	Object.defineProperty(this, 'defaults', { get: function () { return config; } });
1171 | 	Object.defineProperty(this, 'version', { get: function () { return version; } });
1172 | 	Object.defineProperty(this, 'noop', { get: function () { return false; } });
1173 | 
1174 | 	return instance ? instance : (instance = this)
1175 | }
1176 | 
1177 | ScrollReveal.isSupported = function () { return isTransformSupported() && isTransitionSupported(); };
1178 | 
1179 | Object.defineProperty(ScrollReveal, 'debug', {
1180 | 	get: function () { return debug || false; },
1181 | 	set: function (value) { return (debug = typeof value === 'boolean' ? value : debug); }
1182 | });
1183 | 
1184 | ScrollReveal();
1185 | 
1186 | export default ScrollReveal;
1187 | 


--------------------------------------------------------------------------------
/dist/scrollreveal.js:
--------------------------------------------------------------------------------
   1 | /*! @license ScrollReveal v4.0.9
   2 | 
   3 | 	Copyright 2021 Fisssion LLC.
   4 | 
   5 | 	Licensed under the GNU General Public License 3.0 for
   6 | 	compatible open source projects and non-commercial use.
   7 | 
   8 | 	For commercial sites, themes, projects, and applications,
   9 | 	keep your source code private/proprietary by purchasing
  10 | 	a commercial license from https://scrollrevealjs.org/
  11 | */
  12 | (function (global, factory) {
  13 | 	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  14 | 	typeof define === 'function' && define.amd ? define(factory) :
  15 | 	(global = global || self, global.ScrollReveal = factory());
  16 | }(this, function () { 'use strict';
  17 | 
  18 | 	var defaults = {
  19 | 		delay: 0,
  20 | 		distance: '0',
  21 | 		duration: 600,
  22 | 		easing: 'cubic-bezier(0.5, 0, 0, 1)',
  23 | 		interval: 0,
  24 | 		opacity: 0,
  25 | 		origin: 'bottom',
  26 | 		rotate: {
  27 | 			x: 0,
  28 | 			y: 0,
  29 | 			z: 0
  30 | 		},
  31 | 		scale: 1,
  32 | 		cleanup: false,
  33 | 		container: document.documentElement,
  34 | 		desktop: true,
  35 | 		mobile: true,
  36 | 		reset: false,
  37 | 		useDelay: 'always',
  38 | 		viewFactor: 0.0,
  39 | 		viewOffset: {
  40 | 			top: 0,
  41 | 			right: 0,
  42 | 			bottom: 0,
  43 | 			left: 0
  44 | 		},
  45 | 		afterReset: function afterReset() {},
  46 | 		afterReveal: function afterReveal() {},
  47 | 		beforeReset: function beforeReset() {},
  48 | 		beforeReveal: function beforeReveal() {}
  49 | 	};
  50 | 
  51 | 	function failure() {
  52 | 		document.documentElement.classList.remove('sr');
  53 | 
  54 | 		return {
  55 | 			clean: function clean() {},
  56 | 			destroy: function destroy() {},
  57 | 			reveal: function reveal() {},
  58 | 			sync: function sync() {},
  59 | 			get noop() {
  60 | 				return true
  61 | 			}
  62 | 		}
  63 | 	}
  64 | 
  65 | 	function success() {
  66 | 		document.documentElement.classList.add('sr');
  67 | 
  68 | 		if (document.body) {
  69 | 			document.body.style.height = '100%';
  70 | 		} else {
  71 | 			document.addEventListener('DOMContentLoaded', function () {
  72 | 				document.body.style.height = '100%';
  73 | 			});
  74 | 		}
  75 | 	}
  76 | 
  77 | 	var mount = { success: success, failure: failure };
  78 | 
  79 | 	/*! @license is-dom-node v1.0.4
  80 | 
  81 | 		Copyright 2018 Fisssion LLC.
  82 | 
  83 | 		Permission is hereby granted, free of charge, to any person obtaining a copy
  84 | 		of this software and associated documentation files (the "Software"), to deal
  85 | 		in the Software without restriction, including without limitation the rights
  86 | 		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  87 | 		copies of the Software, and to permit persons to whom the Software is
  88 | 		furnished to do so, subject to the following conditions:
  89 | 
  90 | 		The above copyright notice and this permission notice shall be included in all
  91 | 		copies or substantial portions of the Software.
  92 | 
  93 | 		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  94 | 		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  95 | 		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  96 | 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  97 | 		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  98 | 		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  99 | 		SOFTWARE.
 100 | 
 101 | 	*/
 102 | 	function isDomNode(x) {
 103 | 		return typeof window.Node === 'object'
 104 | 			? x instanceof window.Node
 105 | 			: x !== null &&
 106 | 					typeof x === 'object' &&
 107 | 					typeof x.nodeType === 'number' &&
 108 | 					typeof x.nodeName === 'string'
 109 | 	}
 110 | 
 111 | 	/*! @license is-dom-node-list v1.2.1
 112 | 
 113 | 		Copyright 2018 Fisssion LLC.
 114 | 
 115 | 		Permission is hereby granted, free of charge, to any person obtaining a copy
 116 | 		of this software and associated documentation files (the "Software"), to deal
 117 | 		in the Software without restriction, including without limitation the rights
 118 | 		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 119 | 		copies of the Software, and to permit persons to whom the Software is
 120 | 		furnished to do so, subject to the following conditions:
 121 | 
 122 | 		The above copyright notice and this permission notice shall be included in all
 123 | 		copies or substantial portions of the Software.
 124 | 
 125 | 		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 126 | 		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 127 | 		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 128 | 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 129 | 		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 130 | 		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 131 | 		SOFTWARE.
 132 | 
 133 | 	*/
 134 | 
 135 | 	function isDomNodeList(x) {
 136 | 		var prototypeToString = Object.prototype.toString.call(x);
 137 | 		var regex = /^\[object (HTMLCollection|NodeList|Object)\]$/;
 138 | 
 139 | 		return typeof window.NodeList === 'object'
 140 | 			? x instanceof window.NodeList
 141 | 			: x !== null &&
 142 | 					typeof x === 'object' &&
 143 | 					typeof x.length === 'number' &&
 144 | 					regex.test(prototypeToString) &&
 145 | 					(x.length === 0 || isDomNode(x[0]))
 146 | 	}
 147 | 
 148 | 	/*! @license Tealight v0.3.6
 149 | 
 150 | 		Copyright 2018 Fisssion LLC.
 151 | 
 152 | 		Permission is hereby granted, free of charge, to any person obtaining a copy
 153 | 		of this software and associated documentation files (the "Software"), to deal
 154 | 		in the Software without restriction, including without limitation the rights
 155 | 		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 156 | 		copies of the Software, and to permit persons to whom the Software is
 157 | 		furnished to do so, subject to the following conditions:
 158 | 
 159 | 		The above copyright notice and this permission notice shall be included in all
 160 | 		copies or substantial portions of the Software.
 161 | 
 162 | 		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 163 | 		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 164 | 		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 165 | 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 166 | 		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 167 | 		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 168 | 		SOFTWARE.
 169 | 
 170 | 	*/
 171 | 
 172 | 	function tealight(target, context) {
 173 | 	  if ( context === void 0 ) { context = document; }
 174 | 
 175 | 	  if (target instanceof Array) { return target.filter(isDomNode); }
 176 | 	  if (isDomNode(target)) { return [target]; }
 177 | 	  if (isDomNodeList(target)) { return Array.prototype.slice.call(target); }
 178 | 	  if (typeof target === "string") {
 179 | 	    try {
 180 | 	      var query = context.querySelectorAll(target);
 181 | 	      return Array.prototype.slice.call(query);
 182 | 	    } catch (err) {
 183 | 	      return [];
 184 | 	    }
 185 | 	  }
 186 | 	  return [];
 187 | 	}
 188 | 
 189 | 	function isObject(x) {
 190 | 		return (
 191 | 			x !== null &&
 192 | 			x instanceof Object &&
 193 | 			(x.constructor === Object ||
 194 | 				Object.prototype.toString.call(x) === '[object Object]')
 195 | 		)
 196 | 	}
 197 | 
 198 | 	function each(collection, callback) {
 199 | 		if (isObject(collection)) {
 200 | 			var keys = Object.keys(collection);
 201 | 			return keys.forEach(function (key) { return callback(collection[key], key, collection); })
 202 | 		}
 203 | 		if (collection instanceof Array) {
 204 | 			return collection.forEach(function (item, i) { return callback(item, i, collection); })
 205 | 		}
 206 | 		throw new TypeError('Expected either an array or object literal.')
 207 | 	}
 208 | 
 209 | 	function logger(message) {
 210 | 		var details = [], len = arguments.length - 1;
 211 | 		while ( len-- > 0 ) details[ len ] = arguments[ len + 1 ];
 212 | 
 213 | 		if (this.constructor.debug && console) {
 214 | 			var report = "%cScrollReveal: " + message;
 215 | 			details.forEach(function (detail) { return (report += "\n — " + detail); });
 216 | 			console.log(report, 'color: #ea654b;'); // eslint-disable-line no-console
 217 | 		}
 218 | 	}
 219 | 
 220 | 	function rinse() {
 221 | 		var this$1 = this;
 222 | 
 223 | 		var struct = function () { return ({
 224 | 			active: [],
 225 | 			stale: []
 226 | 		}); };
 227 | 
 228 | 		var elementIds = struct();
 229 | 		var sequenceIds = struct();
 230 | 		var containerIds = struct();
 231 | 
 232 | 		/**
 233 | 		 * Take stock of active element IDs.
 234 | 		 */
 235 | 		try {
 236 | 			each(tealight('[data-sr-id]'), function (node) {
 237 | 				var id = parseInt(node.getAttribute('data-sr-id'));
 238 | 				elementIds.active.push(id);
 239 | 			});
 240 | 		} catch (e) {
 241 | 			throw e
 242 | 		}
 243 | 		/**
 244 | 		 * Destroy stale elements.
 245 | 		 */
 246 | 		each(this.store.elements, function (element) {
 247 | 			if (elementIds.active.indexOf(element.id) === -1) {
 248 | 				elementIds.stale.push(element.id);
 249 | 			}
 250 | 		});
 251 | 
 252 | 		each(elementIds.stale, function (staleId) { return delete this$1.store.elements[staleId]; });
 253 | 
 254 | 		/**
 255 | 		 * Take stock of active container and sequence IDs.
 256 | 		 */
 257 | 		each(this.store.elements, function (element) {
 258 | 			if (containerIds.active.indexOf(element.containerId) === -1) {
 259 | 				containerIds.active.push(element.containerId);
 260 | 			}
 261 | 			if (element.hasOwnProperty('sequence')) {
 262 | 				if (sequenceIds.active.indexOf(element.sequence.id) === -1) {
 263 | 					sequenceIds.active.push(element.sequence.id);
 264 | 				}
 265 | 			}
 266 | 		});
 267 | 
 268 | 		/**
 269 | 		 * Destroy stale containers.
 270 | 		 */
 271 | 		each(this.store.containers, function (container) {
 272 | 			if (containerIds.active.indexOf(container.id) === -1) {
 273 | 				containerIds.stale.push(container.id);
 274 | 			}
 275 | 		});
 276 | 
 277 | 		each(containerIds.stale, function (staleId) {
 278 | 			var stale = this$1.store.containers[staleId].node;
 279 | 			stale.removeEventListener('scroll', this$1.delegate);
 280 | 			stale.removeEventListener('resize', this$1.delegate);
 281 | 			delete this$1.store.containers[staleId];
 282 | 		});
 283 | 
 284 | 		/**
 285 | 		 * Destroy stale sequences.
 286 | 		 */
 287 | 		each(this.store.sequences, function (sequence) {
 288 | 			if (sequenceIds.active.indexOf(sequence.id) === -1) {
 289 | 				sequenceIds.stale.push(sequence.id);
 290 | 			}
 291 | 		});
 292 | 
 293 | 		each(sequenceIds.stale, function (staleId) { return delete this$1.store.sequences[staleId]; });
 294 | 	}
 295 | 
 296 | 	/*! @license Rematrix v0.3.0
 297 | 
 298 | 		Copyright 2018 Julian Lloyd.
 299 | 
 300 | 		Permission is hereby granted, free of charge, to any person obtaining a copy
 301 | 		of this software and associated documentation files (the "Software"), to deal
 302 | 		in the Software without restriction, including without limitation the rights
 303 | 		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 304 | 		copies of the Software, and to permit persons to whom the Software is
 305 | 		furnished to do so, subject to the following conditions:
 306 | 
 307 | 		The above copyright notice and this permission notice shall be included in
 308 | 		all copies or substantial portions of the Software.
 309 | 
 310 | 		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 311 | 		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 312 | 		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 313 | 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 314 | 		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 315 | 		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 316 | 		THE SOFTWARE.
 317 | 	*/
 318 | 	/**
 319 | 	 * @module Rematrix
 320 | 	 */
 321 | 
 322 | 	/**
 323 | 	 * Transformation matrices in the browser come in two flavors:
 324 | 	 *
 325 | 	 *  - `matrix` using 6 values (short)
 326 | 	 *  - `matrix3d` using 16 values (long)
 327 | 	 *
 328 | 	 * This utility follows this [conversion guide](https://goo.gl/EJlUQ1)
 329 | 	 * to expand short form matrices to their equivalent long form.
 330 | 	 *
 331 | 	 * @param  {array} source - Accepts both short and long form matrices.
 332 | 	 * @return {array}
 333 | 	 */
 334 | 	function format(source) {
 335 | 		if (source.constructor !== Array) {
 336 | 			throw new TypeError('Expected array.')
 337 | 		}
 338 | 		if (source.length === 16) {
 339 | 			return source
 340 | 		}
 341 | 		if (source.length === 6) {
 342 | 			var matrix = identity();
 343 | 			matrix[0] = source[0];
 344 | 			matrix[1] = source[1];
 345 | 			matrix[4] = source[2];
 346 | 			matrix[5] = source[3];
 347 | 			matrix[12] = source[4];
 348 | 			matrix[13] = source[5];
 349 | 			return matrix
 350 | 		}
 351 | 		throw new RangeError('Expected array with either 6 or 16 values.')
 352 | 	}
 353 | 
 354 | 	/**
 355 | 	 * Returns a matrix representing no transformation. The product of any matrix
 356 | 	 * multiplied by the identity matrix will be the original matrix.
 357 | 	 *
 358 | 	 * > **Tip:** Similar to how `5 * 1 === 5`, where `1` is the identity.
 359 | 	 *
 360 | 	 * @return {array}
 361 | 	 */
 362 | 	function identity() {
 363 | 		var matrix = [];
 364 | 		for (var i = 0; i < 16; i++) {
 365 | 			i % 5 == 0 ? matrix.push(1) : matrix.push(0);
 366 | 		}
 367 | 		return matrix
 368 | 	}
 369 | 
 370 | 	/**
 371 | 	 * Returns a 4x4 matrix describing the combined transformations
 372 | 	 * of both arguments.
 373 | 	 *
 374 | 	 * > **Note:** Order is very important. For example, rotating 45°
 375 | 	 * along the Z-axis, followed by translating 500 pixels along the
 376 | 	 * Y-axis... is not the same as translating 500 pixels along the
 377 | 	 * Y-axis, followed by rotating 45° along on the Z-axis.
 378 | 	 *
 379 | 	 * @param  {array} m - Accepts both short and long form matrices.
 380 | 	 * @param  {array} x - Accepts both short and long form matrices.
 381 | 	 * @return {array}
 382 | 	 */
 383 | 	function multiply(m, x) {
 384 | 		var fm = format(m);
 385 | 		var fx = format(x);
 386 | 		var product = [];
 387 | 
 388 | 		for (var i = 0; i < 4; i++) {
 389 | 			var row = [fm[i], fm[i + 4], fm[i + 8], fm[i + 12]];
 390 | 			for (var j = 0; j < 4; j++) {
 391 | 				var k = j * 4;
 392 | 				var col = [fx[k], fx[k + 1], fx[k + 2], fx[k + 3]];
 393 | 				var result =
 394 | 					row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3];
 395 | 
 396 | 				product[i + k] = result;
 397 | 			}
 398 | 		}
 399 | 
 400 | 		return product
 401 | 	}
 402 | 
 403 | 	/**
 404 | 	 * Attempts to return a 4x4 matrix describing the CSS transform
 405 | 	 * matrix passed in, but will return the identity matrix as a
 406 | 	 * fallback.
 407 | 	 *
 408 | 	 * > **Tip:** This method is used to convert a CSS matrix (retrieved as a
 409 | 	 * `string` from computed styles) to its equivalent array format.
 410 | 	 *
 411 | 	 * @param  {string} source - `matrix` or `matrix3d` CSS Transform value.
 412 | 	 * @return {array}
 413 | 	 */
 414 | 	function parse(source) {
 415 | 		if (typeof source === 'string') {
 416 | 			var match = source.match(/matrix(3d)?\(([^)]+)\)/);
 417 | 			if (match) {
 418 | 				var raw = match[2].split(', ').map(parseFloat);
 419 | 				return format(raw)
 420 | 			}
 421 | 		}
 422 | 		return identity()
 423 | 	}
 424 | 
 425 | 	/**
 426 | 	 * Returns a 4x4 matrix describing X-axis rotation.
 427 | 	 *
 428 | 	 * @param  {number} angle - Measured in degrees.
 429 | 	 * @return {array}
 430 | 	 */
 431 | 	function rotateX(angle) {
 432 | 		var theta = Math.PI / 180 * angle;
 433 | 		var matrix = identity();
 434 | 
 435 | 		matrix[5] = matrix[10] = Math.cos(theta);
 436 | 		matrix[6] = matrix[9] = Math.sin(theta);
 437 | 		matrix[9] *= -1;
 438 | 
 439 | 		return matrix
 440 | 	}
 441 | 
 442 | 	/**
 443 | 	 * Returns a 4x4 matrix describing Y-axis rotation.
 444 | 	 *
 445 | 	 * @param  {number} angle - Measured in degrees.
 446 | 	 * @return {array}
 447 | 	 */
 448 | 	function rotateY(angle) {
 449 | 		var theta = Math.PI / 180 * angle;
 450 | 		var matrix = identity();
 451 | 
 452 | 		matrix[0] = matrix[10] = Math.cos(theta);
 453 | 		matrix[2] = matrix[8] = Math.sin(theta);
 454 | 		matrix[2] *= -1;
 455 | 
 456 | 		return matrix
 457 | 	}
 458 | 
 459 | 	/**
 460 | 	 * Returns a 4x4 matrix describing Z-axis rotation.
 461 | 	 *
 462 | 	 * @param  {number} angle - Measured in degrees.
 463 | 	 * @return {array}
 464 | 	 */
 465 | 	function rotateZ(angle) {
 466 | 		var theta = Math.PI / 180 * angle;
 467 | 		var matrix = identity();
 468 | 
 469 | 		matrix[0] = matrix[5] = Math.cos(theta);
 470 | 		matrix[1] = matrix[4] = Math.sin(theta);
 471 | 		matrix[4] *= -1;
 472 | 
 473 | 		return matrix
 474 | 	}
 475 | 
 476 | 	/**
 477 | 	 * Returns a 4x4 matrix describing 2D scaling. The first argument
 478 | 	 * is used for both X and Y-axis scaling, unless an optional
 479 | 	 * second argument is provided to explicitly define Y-axis scaling.
 480 | 	 *
 481 | 	 * @param  {number} scalar    - Decimal multiplier.
 482 | 	 * @param  {number} [scalarY] - Decimal multiplier.
 483 | 	 * @return {array}
 484 | 	 */
 485 | 	function scale(scalar, scalarY) {
 486 | 		var matrix = identity();
 487 | 
 488 | 		matrix[0] = scalar;
 489 | 		matrix[5] = typeof scalarY === 'number' ? scalarY : scalar;
 490 | 
 491 | 		return matrix
 492 | 	}
 493 | 
 494 | 	/**
 495 | 	 * Returns a 4x4 matrix describing X-axis translation.
 496 | 	 *
 497 | 	 * @param  {number} distance - Measured in pixels.
 498 | 	 * @return {array}
 499 | 	 */
 500 | 	function translateX(distance) {
 501 | 		var matrix = identity();
 502 | 		matrix[12] = distance;
 503 | 		return matrix
 504 | 	}
 505 | 
 506 | 	/**
 507 | 	 * Returns a 4x4 matrix describing Y-axis translation.
 508 | 	 *
 509 | 	 * @param  {number} distance - Measured in pixels.
 510 | 	 * @return {array}
 511 | 	 */
 512 | 	function translateY(distance) {
 513 | 		var matrix = identity();
 514 | 		matrix[13] = distance;
 515 | 		return matrix
 516 | 	}
 517 | 
 518 | 	var getPrefixedCssProp = (function () {
 519 | 		var properties = {};
 520 | 		var style = document.documentElement.style;
 521 | 
 522 | 		function getPrefixedCssProperty(name, source) {
 523 | 			if ( source === void 0 ) source = style;
 524 | 
 525 | 			if (name && typeof name === 'string') {
 526 | 				if (properties[name]) {
 527 | 					return properties[name]
 528 | 				}
 529 | 				if (typeof source[name] === 'string') {
 530 | 					return (properties[name] = name)
 531 | 				}
 532 | 				if (typeof source[("-webkit-" + name)] === 'string') {
 533 | 					return (properties[name] = "-webkit-" + name)
 534 | 				}
 535 | 				throw new RangeError(("Unable to find \"" + name + "\" style property."))
 536 | 			}
 537 | 			throw new TypeError('Expected a string.')
 538 | 		}
 539 | 
 540 | 		getPrefixedCssProperty.clearCache = function () { return (properties = {}); };
 541 | 
 542 | 		return getPrefixedCssProperty
 543 | 	})();
 544 | 
 545 | 	function style(element) {
 546 | 		var computed = window.getComputedStyle(element.node);
 547 | 		var position = computed.position;
 548 | 		var config = element.config;
 549 | 
 550 | 		/**
 551 | 		 * Generate inline styles
 552 | 		 */
 553 | 		var inline = {};
 554 | 		var inlineStyle = element.node.getAttribute('style') || '';
 555 | 		var inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || [];
 556 | 
 557 | 		inline.computed = inlineMatch ? inlineMatch.map(function (m) { return m.trim(); }).join('; ') + ';' : '';
 558 | 
 559 | 		inline.generated = inlineMatch.some(function (m) { return m.match(/visibility\s?:\s?visible/i); })
 560 | 			? inline.computed
 561 | 			: inlineMatch.concat( ['visibility: visible']).map(function (m) { return m.trim(); }).join('; ') + ';';
 562 | 
 563 | 		/**
 564 | 		 * Generate opacity styles
 565 | 		 */
 566 | 		var computedOpacity = parseFloat(computed.opacity);
 567 | 		var configOpacity = !isNaN(parseFloat(config.opacity))
 568 | 			? parseFloat(config.opacity)
 569 | 			: parseFloat(computed.opacity);
 570 | 
 571 | 		var opacity = {
 572 | 			computed: computedOpacity !== configOpacity ? ("opacity: " + computedOpacity + ";") : '',
 573 | 			generated: computedOpacity !== configOpacity ? ("opacity: " + configOpacity + ";") : ''
 574 | 		};
 575 | 
 576 | 		/**
 577 | 		 * Generate transformation styles
 578 | 		 */
 579 | 		var transformations = [];
 580 | 
 581 | 		if (parseFloat(config.distance)) {
 582 | 			var axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X';
 583 | 
 584 | 			/**
 585 | 			 * Let’s make sure our our pixel distances are negative for top and left.
 586 | 			 * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS.
 587 | 			 */
 588 | 			var distance = config.distance;
 589 | 			if (config.origin === 'top' || config.origin === 'left') {
 590 | 				distance = /^-/.test(distance) ? distance.substr(1) : ("-" + distance);
 591 | 			}
 592 | 
 593 | 			var ref = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g);
 594 | 			var value = ref[0];
 595 | 			var unit = ref[1];
 596 | 
 597 | 			switch (unit) {
 598 | 				case 'em':
 599 | 					distance = parseInt(computed.fontSize) * value;
 600 | 					break
 601 | 				case 'px':
 602 | 					distance = value;
 603 | 					break
 604 | 				case '%':
 605 | 					/**
 606 | 					 * Here we use `getBoundingClientRect` instead of
 607 | 					 * the existing data attached to `element.geometry`
 608 | 					 * because only the former includes any transformations
 609 | 					 * current applied to the element.
 610 | 					 *
 611 | 					 * If that behavior ends up being unintuitive, this
 612 | 					 * logic could instead utilize `element.geometry.height`
 613 | 					 * and `element.geoemetry.width` for the distance calculation
 614 | 					 */
 615 | 					distance =
 616 | 						axis === 'Y'
 617 | 							? (element.node.getBoundingClientRect().height * value) / 100
 618 | 							: (element.node.getBoundingClientRect().width * value) / 100;
 619 | 					break
 620 | 				default:
 621 | 					throw new RangeError('Unrecognized or missing distance unit.')
 622 | 			}
 623 | 
 624 | 			if (axis === 'Y') {
 625 | 				transformations.push(translateY(distance));
 626 | 			} else {
 627 | 				transformations.push(translateX(distance));
 628 | 			}
 629 | 		}
 630 | 
 631 | 		if (config.rotate.x) { transformations.push(rotateX(config.rotate.x)); }
 632 | 		if (config.rotate.y) { transformations.push(rotateY(config.rotate.y)); }
 633 | 		if (config.rotate.z) { transformations.push(rotateZ(config.rotate.z)); }
 634 | 		if (config.scale !== 1) {
 635 | 			if (config.scale === 0) {
 636 | 				/**
 637 | 				 * The CSS Transforms matrix interpolation specification
 638 | 				 * basically disallows transitions of non-invertible
 639 | 				 * matrixes, which means browsers won't transition
 640 | 				 * elements with zero scale.
 641 | 				 *
 642 | 				 * That’s inconvenient for the API and developer
 643 | 				 * experience, so we simply nudge their value
 644 | 				 * slightly above zero; this allows browsers
 645 | 				 * to transition our element as expected.
 646 | 				 *
 647 | 				 * `0.0002` was the smallest number
 648 | 				 * that performed across browsers.
 649 | 				 */
 650 | 				transformations.push(scale(0.0002));
 651 | 			} else {
 652 | 				transformations.push(scale(config.scale));
 653 | 			}
 654 | 		}
 655 | 
 656 | 		var transform = {};
 657 | 		if (transformations.length) {
 658 | 			transform.property = getPrefixedCssProp('transform');
 659 | 			/**
 660 | 			 * The default computed transform value should be one of:
 661 | 			 * undefined || 'none' || 'matrix()' || 'matrix3d()'
 662 | 			 */
 663 | 			transform.computed = {
 664 | 				raw: computed[transform.property],
 665 | 				matrix: parse(computed[transform.property])
 666 | 			};
 667 | 
 668 | 			transformations.unshift(transform.computed.matrix);
 669 | 			var product = transformations.reduce(multiply);
 670 | 
 671 | 			transform.generated = {
 672 | 				initial: ((transform.property) + ": matrix3d(" + (product.join(', ')) + ");"),
 673 | 				final: ((transform.property) + ": matrix3d(" + (transform.computed.matrix.join(', ')) + ");")
 674 | 			};
 675 | 		} else {
 676 | 			transform.generated = {
 677 | 				initial: '',
 678 | 				final: ''
 679 | 			};
 680 | 		}
 681 | 
 682 | 		/**
 683 | 		 * Generate transition styles
 684 | 		 */
 685 | 		var transition = {};
 686 | 		if (opacity.generated || transform.generated.initial) {
 687 | 			transition.property = getPrefixedCssProp('transition');
 688 | 			transition.computed = computed[transition.property];
 689 | 			transition.fragments = [];
 690 | 
 691 | 			var delay = config.delay;
 692 | 			var duration = config.duration;
 693 | 			var easing = config.easing;
 694 | 
 695 | 			if (opacity.generated) {
 696 | 				transition.fragments.push({
 697 | 					delayed: ("opacity " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"),
 698 | 					instant: ("opacity " + (duration / 1000) + "s " + easing + " 0s")
 699 | 				});
 700 | 			}
 701 | 
 702 | 			if (transform.generated.initial) {
 703 | 				transition.fragments.push({
 704 | 					delayed: ((transform.property) + " " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"),
 705 | 					instant: ((transform.property) + " " + (duration / 1000) + "s " + easing + " 0s")
 706 | 				});
 707 | 			}
 708 | 
 709 | 			/**
 710 | 			 * The default computed transition property should be undefined, or one of:
 711 | 			 * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()'
 712 | 			 */
 713 | 			var hasCustomTransition =
 714 | 				transition.computed && !transition.computed.match(/all 0s|none 0s/);
 715 | 
 716 | 			if (hasCustomTransition) {
 717 | 				transition.fragments.unshift({
 718 | 					delayed: transition.computed,
 719 | 					instant: transition.computed
 720 | 				});
 721 | 			}
 722 | 
 723 | 			var composed = transition.fragments.reduce(
 724 | 				function (composition, fragment, i) {
 725 | 					composition.delayed += i === 0 ? fragment.delayed : (", " + (fragment.delayed));
 726 | 					composition.instant += i === 0 ? fragment.instant : (", " + (fragment.instant));
 727 | 					return composition
 728 | 				},
 729 | 				{
 730 | 					delayed: '',
 731 | 					instant: ''
 732 | 				}
 733 | 			);
 734 | 
 735 | 			transition.generated = {
 736 | 				delayed: ((transition.property) + ": " + (composed.delayed) + ";"),
 737 | 				instant: ((transition.property) + ": " + (composed.instant) + ";")
 738 | 			};
 739 | 		} else {
 740 | 			transition.generated = {
 741 | 				delayed: '',
 742 | 				instant: ''
 743 | 			};
 744 | 		}
 745 | 
 746 | 		return {
 747 | 			inline: inline,
 748 | 			opacity: opacity,
 749 | 			position: position,
 750 | 			transform: transform,
 751 | 			transition: transition
 752 | 		}
 753 | 	}
 754 | 
 755 | 	/**
 756 | 	 * apply a CSS string to an element using the CSSOM (element.style) rather
 757 | 	 * than setAttribute, which may violate the content security policy.
 758 | 	 *
 759 | 	 * @param {Node}   [el]  Element to receive styles.
 760 | 	 * @param {string} [declaration] Styles to apply.
 761 | 	 */
 762 | 	function applyStyle (el, declaration) {
 763 | 		declaration.split(';').forEach(function (pair) {
 764 | 			var ref = pair.split(':');
 765 | 			var property = ref[0];
 766 | 			var value = ref.slice(1);
 767 | 			if (property && value) {
 768 | 				el.style[property.trim()] = value.join(':');
 769 | 			}
 770 | 		});
 771 | 	}
 772 | 
 773 | 	function clean(target) {
 774 | 		var this$1 = this;
 775 | 
 776 | 		var dirty;
 777 | 		try {
 778 | 			each(tealight(target), function (node) {
 779 | 				var id = node.getAttribute('data-sr-id');
 780 | 				if (id !== null) {
 781 | 					dirty = true;
 782 | 					var element = this$1.store.elements[id];
 783 | 					if (element.callbackTimer) {
 784 | 						window.clearTimeout(element.callbackTimer.clock);
 785 | 					}
 786 | 					applyStyle(element.node, element.styles.inline.generated);
 787 | 					node.removeAttribute('data-sr-id');
 788 | 					delete this$1.store.elements[id];
 789 | 				}
 790 | 			});
 791 | 		} catch (e) {
 792 | 			return logger.call(this, 'Clean failed.', e.message)
 793 | 		}
 794 | 
 795 | 		if (dirty) {
 796 | 			try {
 797 | 				rinse.call(this);
 798 | 			} catch (e) {
 799 | 				return logger.call(this, 'Clean failed.', e.message)
 800 | 			}
 801 | 		}
 802 | 	}
 803 | 
 804 | 	function destroy() {
 805 | 		var this$1 = this;
 806 | 
 807 | 		/**
 808 | 		 * Remove all generated styles and element ids
 809 | 		 */
 810 | 		each(this.store.elements, function (element) {
 811 | 			applyStyle(element.node, element.styles.inline.generated);
 812 | 			element.node.removeAttribute('data-sr-id');
 813 | 		});
 814 | 
 815 | 		/**
 816 | 		 * Remove all event listeners.
 817 | 		 */
 818 | 		each(this.store.containers, function (container) {
 819 | 			var target =
 820 | 				container.node === document.documentElement ? window : container.node;
 821 | 			target.removeEventListener('scroll', this$1.delegate);
 822 | 			target.removeEventListener('resize', this$1.delegate);
 823 | 		});
 824 | 
 825 | 		/**
 826 | 		 * Clear all data from the store
 827 | 		 */
 828 | 		this.store = {
 829 | 			containers: {},
 830 | 			elements: {},
 831 | 			history: [],
 832 | 			sequences: {}
 833 | 		};
 834 | 	}
 835 | 
 836 | 	function deepAssign(target) {
 837 | 		var sources = [], len = arguments.length - 1;
 838 | 		while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ];
 839 | 
 840 | 		if (isObject(target)) {
 841 | 			each(sources, function (source) {
 842 | 				each(source, function (data, key) {
 843 | 					if (isObject(data)) {
 844 | 						if (!target[key] || !isObject(target[key])) {
 845 | 							target[key] = {};
 846 | 						}
 847 | 						deepAssign(target[key], data);
 848 | 					} else {
 849 | 						target[key] = data;
 850 | 					}
 851 | 				});
 852 | 			});
 853 | 			return target
 854 | 		} else {
 855 | 			throw new TypeError('Target must be an object literal.')
 856 | 		}
 857 | 	}
 858 | 
 859 | 	function isMobile(agent) {
 860 | 		if ( agent === void 0 ) agent = navigator.userAgent;
 861 | 
 862 | 		return /Android|iPhone|iPad|iPod/i.test(agent)
 863 | 	}
 864 | 
 865 | 	var nextUniqueId = (function () {
 866 | 		var uid = 0;
 867 | 		return function () { return uid++; }
 868 | 	})();
 869 | 
 870 | 	function initialize() {
 871 | 		var this$1 = this;
 872 | 
 873 | 		rinse.call(this);
 874 | 
 875 | 		each(this.store.elements, function (element) {
 876 | 			var styles = [element.styles.inline.generated];
 877 | 
 878 | 			if (element.visible) {
 879 | 				styles.push(element.styles.opacity.computed);
 880 | 				styles.push(element.styles.transform.generated.final);
 881 | 				element.revealed = true;
 882 | 			} else {
 883 | 				styles.push(element.styles.opacity.generated);
 884 | 				styles.push(element.styles.transform.generated.initial);
 885 | 				element.revealed = false;
 886 | 			}
 887 | 
 888 | 			applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 889 | 		});
 890 | 
 891 | 		each(this.store.containers, function (container) {
 892 | 			var target =
 893 | 				container.node === document.documentElement ? window : container.node;
 894 | 			target.addEventListener('scroll', this$1.delegate);
 895 | 			target.addEventListener('resize', this$1.delegate);
 896 | 		});
 897 | 
 898 | 		/**
 899 | 		 * Manually invoke delegate once to capture
 900 | 		 * element and container dimensions, container
 901 | 		 * scroll position, and trigger any valid reveals
 902 | 		 */
 903 | 		this.delegate();
 904 | 
 905 | 		/**
 906 | 		 * Wipe any existing `setTimeout` now
 907 | 		 * that initialization has completed.
 908 | 		 */
 909 | 		this.initTimeout = null;
 910 | 	}
 911 | 
 912 | 	function animate(element, force) {
 913 | 		if ( force === void 0 ) force = {};
 914 | 
 915 | 		var pristine = force.pristine || this.pristine;
 916 | 		var delayed =
 917 | 			element.config.useDelay === 'always' ||
 918 | 			(element.config.useDelay === 'onload' && pristine) ||
 919 | 			(element.config.useDelay === 'once' && !element.seen);
 920 | 
 921 | 		var shouldReveal = element.visible && !element.revealed;
 922 | 		var shouldReset = !element.visible && element.revealed && element.config.reset;
 923 | 
 924 | 		if (force.reveal || shouldReveal) {
 925 | 			return triggerReveal.call(this, element, delayed)
 926 | 		}
 927 | 
 928 | 		if (force.reset || shouldReset) {
 929 | 			return triggerReset.call(this, element)
 930 | 		}
 931 | 	}
 932 | 
 933 | 	function triggerReveal(element, delayed) {
 934 | 		var styles = [
 935 | 			element.styles.inline.generated,
 936 | 			element.styles.opacity.computed,
 937 | 			element.styles.transform.generated.final
 938 | 		];
 939 | 		if (delayed) {
 940 | 			styles.push(element.styles.transition.generated.delayed);
 941 | 		} else {
 942 | 			styles.push(element.styles.transition.generated.instant);
 943 | 		}
 944 | 		element.revealed = element.seen = true;
 945 | 		applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 946 | 		registerCallbacks.call(this, element, delayed);
 947 | 	}
 948 | 
 949 | 	function triggerReset(element) {
 950 | 		var styles = [
 951 | 			element.styles.inline.generated,
 952 | 			element.styles.opacity.generated,
 953 | 			element.styles.transform.generated.initial,
 954 | 			element.styles.transition.generated.instant
 955 | 		];
 956 | 		element.revealed = false;
 957 | 		applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' '));
 958 | 		registerCallbacks.call(this, element);
 959 | 	}
 960 | 
 961 | 	function registerCallbacks(element, isDelayed) {
 962 | 		var this$1 = this;
 963 | 
 964 | 		var duration = isDelayed
 965 | 			? element.config.duration + element.config.delay
 966 | 			: element.config.duration;
 967 | 
 968 | 		var beforeCallback = element.revealed
 969 | 			? element.config.beforeReveal
 970 | 			: element.config.beforeReset;
 971 | 
 972 | 		var afterCallback = element.revealed
 973 | 			? element.config.afterReveal
 974 | 			: element.config.afterReset;
 975 | 
 976 | 		var elapsed = 0;
 977 | 		if (element.callbackTimer) {
 978 | 			elapsed = Date.now() - element.callbackTimer.start;
 979 | 			window.clearTimeout(element.callbackTimer.clock);
 980 | 		}
 981 | 
 982 | 		beforeCallback(element.node);
 983 | 
 984 | 		element.callbackTimer = {
 985 | 			start: Date.now(),
 986 | 			clock: window.setTimeout(function () {
 987 | 				afterCallback(element.node);
 988 | 				element.callbackTimer = null;
 989 | 				if (element.revealed && !element.config.reset && element.config.cleanup) {
 990 | 					clean.call(this$1, element.node);
 991 | 				}
 992 | 			}, duration - elapsed)
 993 | 		};
 994 | 	}
 995 | 
 996 | 	function sequence(element, pristine) {
 997 | 		if ( pristine === void 0 ) pristine = this.pristine;
 998 | 
 999 | 		/**
1000 | 		 * We first check if the element should reset.
1001 | 		 */
1002 | 		if (!element.visible && element.revealed && element.config.reset) {
1003 | 			return animate.call(this, element, { reset: true })
1004 | 		}
1005 | 
1006 | 		var seq = this.store.sequences[element.sequence.id];
1007 | 		var i = element.sequence.index;
1008 | 
1009 | 		if (seq) {
1010 | 			var visible = new SequenceModel(seq, 'visible', this.store);
1011 | 			var revealed = new SequenceModel(seq, 'revealed', this.store);
1012 | 
1013 | 			seq.models = { visible: visible, revealed: revealed };
1014 | 
1015 | 			/**
1016 | 			 * If the sequence has no revealed members,
1017 | 			 * then we reveal the first visible element
1018 | 			 * within that sequence.
1019 | 			 *
1020 | 			 * The sequence then cues a recursive call
1021 | 			 * in both directions.
1022 | 			 */
1023 | 			if (!revealed.body.length) {
1024 | 				var nextId = seq.members[visible.body[0]];
1025 | 				var nextElement = this.store.elements[nextId];
1026 | 
1027 | 				if (nextElement) {
1028 | 					cue.call(this, seq, visible.body[0], -1, pristine);
1029 | 					cue.call(this, seq, visible.body[0], +1, pristine);
1030 | 					return animate.call(this, nextElement, { reveal: true, pristine: pristine })
1031 | 				}
1032 | 			}
1033 | 
1034 | 			/**
1035 | 			 * If our element isn’t resetting, we check the
1036 | 			 * element sequence index against the head, and
1037 | 			 * then the foot of the sequence.
1038 | 			 */
1039 | 			if (
1040 | 				!seq.blocked.head &&
1041 | 				i === [].concat( revealed.head ).pop() &&
1042 | 				i >= [].concat( visible.body ).shift()
1043 | 			) {
1044 | 				cue.call(this, seq, i, -1, pristine);
1045 | 				return animate.call(this, element, { reveal: true, pristine: pristine })
1046 | 			}
1047 | 
1048 | 			if (
1049 | 				!seq.blocked.foot &&
1050 | 				i === [].concat( revealed.foot ).shift() &&
1051 | 				i <= [].concat( visible.body ).pop()
1052 | 			) {
1053 | 				cue.call(this, seq, i, +1, pristine);
1054 | 				return animate.call(this, element, { reveal: true, pristine: pristine })
1055 | 			}
1056 | 		}
1057 | 	}
1058 | 
1059 | 	function Sequence(interval) {
1060 | 		var i = Math.abs(interval);
1061 | 		if (!isNaN(i)) {
1062 | 			this.id = nextUniqueId();
1063 | 			this.interval = Math.max(i, 16);
1064 | 			this.members = [];
1065 | 			this.models = {};
1066 | 			this.blocked = {
1067 | 				head: false,
1068 | 				foot: false
1069 | 			};
1070 | 		} else {
1071 | 			throw new RangeError('Invalid sequence interval.')
1072 | 		}
1073 | 	}
1074 | 
1075 | 	function SequenceModel(seq, prop, store) {
1076 | 		var this$1 = this;
1077 | 
1078 | 		this.head = [];
1079 | 		this.body = [];
1080 | 		this.foot = [];
1081 | 
1082 | 		each(seq.members, function (id, index) {
1083 | 			var element = store.elements[id];
1084 | 			if (element && element[prop]) {
1085 | 				this$1.body.push(index);
1086 | 			}
1087 | 		});
1088 | 
1089 | 		if (this.body.length) {
1090 | 			each(seq.members, function (id, index) {
1091 | 				var element = store.elements[id];
1092 | 				if (element && !element[prop]) {
1093 | 					if (index < this$1.body[0]) {
1094 | 						this$1.head.push(index);
1095 | 					} else {
1096 | 						this$1.foot.push(index);
1097 | 					}
1098 | 				}
1099 | 			});
1100 | 		}
1101 | 	}
1102 | 
1103 | 	function cue(seq, i, direction, pristine) {
1104 | 		var this$1 = this;
1105 | 
1106 | 		var blocked = ['head', null, 'foot'][1 + direction];
1107 | 		var nextId = seq.members[i + direction];
1108 | 		var nextElement = this.store.elements[nextId];
1109 | 
1110 | 		seq.blocked[blocked] = true;
1111 | 
1112 | 		setTimeout(function () {
1113 | 			seq.blocked[blocked] = false;
1114 | 			if (nextElement) {
1115 | 				sequence.call(this$1, nextElement, pristine);
1116 | 			}
1117 | 		}, seq.interval);
1118 | 	}
1119 | 
1120 | 	function reveal(target, options, syncing) {
1121 | 		var this$1 = this;
1122 | 		if ( options === void 0 ) options = {};
1123 | 		if ( syncing === void 0 ) syncing = false;
1124 | 
1125 | 		var containerBuffer = [];
1126 | 		var sequence$1;
1127 | 		var interval = options.interval || defaults.interval;
1128 | 
1129 | 		try {
1130 | 			if (interval) {
1131 | 				sequence$1 = new Sequence(interval);
1132 | 			}
1133 | 
1134 | 			var nodes = tealight(target);
1135 | 			if (!nodes.length) {
1136 | 				throw new Error('Invalid reveal target.')
1137 | 			}
1138 | 
1139 | 			var elements = nodes.reduce(function (elementBuffer, elementNode) {
1140 | 				var element = {};
1141 | 				var existingId = elementNode.getAttribute('data-sr-id');
1142 | 
1143 | 				if (existingId) {
1144 | 					deepAssign(element, this$1.store.elements[existingId]);
1145 | 
1146 | 					/**
1147 | 					 * In order to prevent previously generated styles
1148 | 					 * from throwing off the new styles, the style tag
1149 | 					 * has to be reverted to its pre-reveal state.
1150 | 					 */
1151 | 					applyStyle(element.node, element.styles.inline.computed);
1152 | 				} else {
1153 | 					element.id = nextUniqueId();
1154 | 					element.node = elementNode;
1155 | 					element.seen = false;
1156 | 					element.revealed = false;
1157 | 					element.visible = false;
1158 | 				}
1159 | 
1160 | 				var config = deepAssign({}, element.config || this$1.defaults, options);
1161 | 
1162 | 				if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
1163 | 					if (existingId) {
1164 | 						clean.call(this$1, element);
1165 | 					}
1166 | 					return elementBuffer // skip elements that are disabled
1167 | 				}
1168 | 
1169 | 				var containerNode = tealight(config.container)[0];
1170 | 				if (!containerNode) {
1171 | 					throw new Error('Invalid container.')
1172 | 				}
1173 | 				if (!containerNode.contains(elementNode)) {
1174 | 					return elementBuffer // skip elements found outside the container
1175 | 				}
1176 | 
1177 | 				var containerId;
1178 | 				{
1179 | 					containerId = getContainerId(
1180 | 						containerNode,
1181 | 						containerBuffer,
1182 | 						this$1.store.containers
1183 | 					);
1184 | 					if (containerId === null) {
1185 | 						containerId = nextUniqueId();
1186 | 						containerBuffer.push({ id: containerId, node: containerNode });
1187 | 					}
1188 | 				}
1189 | 
1190 | 				element.config = config;
1191 | 				element.containerId = containerId;
1192 | 				element.styles = style(element);
1193 | 
1194 | 				if (sequence$1) {
1195 | 					element.sequence = {
1196 | 						id: sequence$1.id,
1197 | 						index: sequence$1.members.length
1198 | 					};
1199 | 					sequence$1.members.push(element.id);
1200 | 				}
1201 | 
1202 | 				elementBuffer.push(element);
1203 | 				return elementBuffer
1204 | 			}, []);
1205 | 
1206 | 			/**
1207 | 			 * Modifying the DOM via setAttribute needs to be handled
1208 | 			 * separately from reading computed styles in the map above
1209 | 			 * for the browser to batch DOM changes (limiting reflows)
1210 | 			 */
1211 | 			each(elements, function (element) {
1212 | 				this$1.store.elements[element.id] = element;
1213 | 				element.node.setAttribute('data-sr-id', element.id);
1214 | 			});
1215 | 		} catch (e) {
1216 | 			return logger.call(this, 'Reveal failed.', e.message)
1217 | 		}
1218 | 
1219 | 		/**
1220 | 		 * Now that element set-up is complete...
1221 | 		 * Let’s commit any container and sequence data we have to the store.
1222 | 		 */
1223 | 		each(containerBuffer, function (container) {
1224 | 			this$1.store.containers[container.id] = {
1225 | 				id: container.id,
1226 | 				node: container.node
1227 | 			};
1228 | 		});
1229 | 		if (sequence$1) {
1230 | 			this.store.sequences[sequence$1.id] = sequence$1;
1231 | 		}
1232 | 
1233 | 		/**
1234 | 		 * If reveal wasn't invoked by sync, we want to
1235 | 		 * make sure to add this call to the history.
1236 | 		 */
1237 | 		if (syncing !== true) {
1238 | 			this.store.history.push({ target: target, options: options });
1239 | 
1240 | 			/**
1241 | 			 * Push initialization to the event queue, giving
1242 | 			 * multiple reveal calls time to be interpreted.
1243 | 			 */
1244 | 			if (this.initTimeout) {
1245 | 				window.clearTimeout(this.initTimeout);
1246 | 			}
1247 | 			this.initTimeout = window.setTimeout(initialize.bind(this), 0);
1248 | 		}
1249 | 	}
1250 | 
1251 | 	function getContainerId(node) {
1252 | 		var collections = [], len = arguments.length - 1;
1253 | 		while ( len-- > 0 ) collections[ len ] = arguments[ len + 1 ];
1254 | 
1255 | 		var id = null;
1256 | 		each(collections, function (collection) {
1257 | 			each(collection, function (container) {
1258 | 				if (id === null && container.node === node) {
1259 | 					id = container.id;
1260 | 				}
1261 | 			});
1262 | 		});
1263 | 		return id
1264 | 	}
1265 | 
1266 | 	/**
1267 | 	 * Re-runs the reveal method for each record stored in history,
1268 | 	 * for capturing new content asynchronously loaded into the DOM.
1269 | 	 */
1270 | 	function sync() {
1271 | 		var this$1 = this;
1272 | 
1273 | 		each(this.store.history, function (record) {
1274 | 			reveal.call(this$1, record.target, record.options, true);
1275 | 		});
1276 | 
1277 | 		initialize.call(this);
1278 | 	}
1279 | 
1280 | 	var polyfill = function (x) { return (x > 0) - (x < 0) || +x; };
1281 | 	var mathSign = Math.sign || polyfill;
1282 | 
1283 | 	/*! @license miniraf v1.0.1
1284 | 
1285 | 		Copyright 2018 Fisssion LLC.
1286 | 
1287 | 		Permission is hereby granted, free of charge, to any person obtaining a copy
1288 | 		of this software and associated documentation files (the "Software"), to deal
1289 | 		in the Software without restriction, including without limitation the rights
1290 | 		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1291 | 		copies of the Software, and to permit persons to whom the Software is
1292 | 		furnished to do so, subject to the following conditions:
1293 | 
1294 | 		The above copyright notice and this permission notice shall be included in all
1295 | 		copies or substantial portions of the Software.
1296 | 
1297 | 		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1298 | 		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1299 | 		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1300 | 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1301 | 		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1302 | 		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1303 | 		SOFTWARE.
1304 | 
1305 | 	*/
1306 | 	var polyfill$1 = (function () {
1307 | 		var clock = Date.now();
1308 | 
1309 | 		return function (callback) {
1310 | 			var currentTime = Date.now();
1311 | 			if (currentTime - clock > 16) {
1312 | 				clock = currentTime;
1313 | 				callback(currentTime);
1314 | 			} else {
1315 | 				setTimeout(function () { return polyfill$1(callback); }, 0);
1316 | 			}
1317 | 		}
1318 | 	})();
1319 | 
1320 | 	var miniraf = window.requestAnimationFrame ||
1321 | 		window.webkitRequestAnimationFrame ||
1322 | 		window.mozRequestAnimationFrame ||
1323 | 		polyfill$1;
1324 | 
1325 | 	function getGeometry(target, isContainer) {
1326 | 		/**
1327 | 		 * We want to ignore padding and scrollbars for container elements.
1328 | 		 * More information here: https://goo.gl/vOZpbz
1329 | 		 */
1330 | 		var height = isContainer ? target.node.clientHeight : target.node.offsetHeight;
1331 | 		var width = isContainer ? target.node.clientWidth : target.node.offsetWidth;
1332 | 
1333 | 		var offsetTop = 0;
1334 | 		var offsetLeft = 0;
1335 | 		var node = target.node;
1336 | 
1337 | 		do {
1338 | 			if (!isNaN(node.offsetTop)) {
1339 | 				offsetTop += node.offsetTop;
1340 | 			}
1341 | 			if (!isNaN(node.offsetLeft)) {
1342 | 				offsetLeft += node.offsetLeft;
1343 | 			}
1344 | 			node = node.offsetParent;
1345 | 		} while (node)
1346 | 
1347 | 		return {
1348 | 			bounds: {
1349 | 				top: offsetTop,
1350 | 				right: offsetLeft + width,
1351 | 				bottom: offsetTop + height,
1352 | 				left: offsetLeft
1353 | 			},
1354 | 			height: height,
1355 | 			width: width
1356 | 		}
1357 | 	}
1358 | 
1359 | 	function getScrolled(container) {
1360 | 		var top, left;
1361 | 		if (container.node === document.documentElement) {
1362 | 			top = window.pageYOffset;
1363 | 			left = window.pageXOffset;
1364 | 		} else {
1365 | 			top = container.node.scrollTop;
1366 | 			left = container.node.scrollLeft;
1367 | 		}
1368 | 		return { top: top, left: left }
1369 | 	}
1370 | 
1371 | 	function isElementVisible(element) {
1372 | 		if ( element === void 0 ) element = {};
1373 | 
1374 | 		var container = this.store.containers[element.containerId];
1375 | 		if (!container) { return }
1376 | 
1377 | 		var viewFactor = Math.max(0, Math.min(1, element.config.viewFactor));
1378 | 		var viewOffset = element.config.viewOffset;
1379 | 
1380 | 		var elementBounds = {
1381 | 			top: element.geometry.bounds.top + element.geometry.height * viewFactor,
1382 | 			right: element.geometry.bounds.right - element.geometry.width * viewFactor,
1383 | 			bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor,
1384 | 			left: element.geometry.bounds.left + element.geometry.width * viewFactor
1385 | 		};
1386 | 
1387 | 		var containerBounds = {
1388 | 			top: container.geometry.bounds.top + container.scroll.top + viewOffset.top,
1389 | 			right: container.geometry.bounds.right + container.scroll.left - viewOffset.right,
1390 | 			bottom:
1391 | 				container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom,
1392 | 			left: container.geometry.bounds.left + container.scroll.left + viewOffset.left
1393 | 		};
1394 | 
1395 | 		return (
1396 | 			(elementBounds.top < containerBounds.bottom &&
1397 | 				elementBounds.right > containerBounds.left &&
1398 | 				elementBounds.bottom > containerBounds.top &&
1399 | 				elementBounds.left < containerBounds.right) ||
1400 | 			element.styles.position === 'fixed'
1401 | 		)
1402 | 	}
1403 | 
1404 | 	function delegate(
1405 | 		event,
1406 | 		elements
1407 | 	) {
1408 | 		var this$1 = this;
1409 | 		if ( event === void 0 ) event = { type: 'init' };
1410 | 		if ( elements === void 0 ) elements = this.store.elements;
1411 | 
1412 | 		miniraf(function () {
1413 | 			var stale = event.type === 'init' || event.type === 'resize';
1414 | 
1415 | 			each(this$1.store.containers, function (container) {
1416 | 				if (stale) {
1417 | 					container.geometry = getGeometry.call(this$1, container, true);
1418 | 				}
1419 | 				var scroll = getScrolled.call(this$1, container);
1420 | 				if (container.scroll) {
1421 | 					container.direction = {
1422 | 						x: mathSign(scroll.left - container.scroll.left),
1423 | 						y: mathSign(scroll.top - container.scroll.top)
1424 | 					};
1425 | 				}
1426 | 				container.scroll = scroll;
1427 | 			});
1428 | 
1429 | 			/**
1430 | 			 * Due to how the sequencer is implemented, it’s
1431 | 			 * important that we update the state of all
1432 | 			 * elements, before any animation logic is
1433 | 			 * evaluated (in the second loop below).
1434 | 			 */
1435 | 			each(elements, function (element) {
1436 | 				if (stale || element.geometry === undefined) {
1437 | 					element.geometry = getGeometry.call(this$1, element);
1438 | 				}
1439 | 				element.visible = isElementVisible.call(this$1, element);
1440 | 			});
1441 | 
1442 | 			each(elements, function (element) {
1443 | 				if (element.sequence) {
1444 | 					sequence.call(this$1, element);
1445 | 				} else {
1446 | 					animate.call(this$1, element);
1447 | 				}
1448 | 			});
1449 | 
1450 | 			this$1.pristine = false;
1451 | 		});
1452 | 	}
1453 | 
1454 | 	function isTransformSupported() {
1455 | 		var style = document.documentElement.style;
1456 | 		return 'transform' in style || 'WebkitTransform' in style
1457 | 	}
1458 | 
1459 | 	function isTransitionSupported() {
1460 | 		var style = document.documentElement.style;
1461 | 		return 'transition' in style || 'WebkitTransition' in style
1462 | 	}
1463 | 
1464 | 	var version = "4.0.9";
1465 | 
1466 | 	var boundDelegate;
1467 | 	var boundDestroy;
1468 | 	var boundReveal;
1469 | 	var boundClean;
1470 | 	var boundSync;
1471 | 	var config;
1472 | 	var debug;
1473 | 	var instance;
1474 | 
1475 | 	function ScrollReveal(options) {
1476 | 		if ( options === void 0 ) options = {};
1477 | 
1478 | 		var invokedWithoutNew =
1479 | 			typeof this === 'undefined' ||
1480 | 			Object.getPrototypeOf(this) !== ScrollReveal.prototype;
1481 | 
1482 | 		if (invokedWithoutNew) {
1483 | 			return new ScrollReveal(options)
1484 | 		}
1485 | 
1486 | 		if (!ScrollReveal.isSupported()) {
1487 | 			logger.call(this, 'Instantiation failed.', 'This browser is not supported.');
1488 | 			return mount.failure()
1489 | 		}
1490 | 
1491 | 		var buffer;
1492 | 		try {
1493 | 			buffer = config
1494 | 				? deepAssign({}, config, options)
1495 | 				: deepAssign({}, defaults, options);
1496 | 		} catch (e) {
1497 | 			logger.call(this, 'Invalid configuration.', e.message);
1498 | 			return mount.failure()
1499 | 		}
1500 | 
1501 | 		try {
1502 | 			var container = tealight(buffer.container)[0];
1503 | 			if (!container) {
1504 | 				throw new Error('Invalid container.')
1505 | 			}
1506 | 		} catch (e) {
1507 | 			logger.call(this, e.message);
1508 | 			return mount.failure()
1509 | 		}
1510 | 
1511 | 		config = buffer;
1512 | 
1513 | 		if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
1514 | 			logger.call(
1515 | 				this,
1516 | 				'This device is disabled.',
1517 | 				("desktop: " + (config.desktop)),
1518 | 				("mobile: " + (config.mobile))
1519 | 			);
1520 | 			return mount.failure()
1521 | 		}
1522 | 
1523 | 		mount.success();
1524 | 
1525 | 		this.store = {
1526 | 			containers: {},
1527 | 			elements: {},
1528 | 			history: [],
1529 | 			sequences: {}
1530 | 		};
1531 | 
1532 | 		this.pristine = true;
1533 | 
1534 | 		boundDelegate = boundDelegate || delegate.bind(this);
1535 | 		boundDestroy = boundDestroy || destroy.bind(this);
1536 | 		boundReveal = boundReveal || reveal.bind(this);
1537 | 		boundClean = boundClean || clean.bind(this);
1538 | 		boundSync = boundSync || sync.bind(this);
1539 | 
1540 | 		Object.defineProperty(this, 'delegate', { get: function () { return boundDelegate; } });
1541 | 		Object.defineProperty(this, 'destroy', { get: function () { return boundDestroy; } });
1542 | 		Object.defineProperty(this, 'reveal', { get: function () { return boundReveal; } });
1543 | 		Object.defineProperty(this, 'clean', { get: function () { return boundClean; } });
1544 | 		Object.defineProperty(this, 'sync', { get: function () { return boundSync; } });
1545 | 
1546 | 		Object.defineProperty(this, 'defaults', { get: function () { return config; } });
1547 | 		Object.defineProperty(this, 'version', { get: function () { return version; } });
1548 | 		Object.defineProperty(this, 'noop', { get: function () { return false; } });
1549 | 
1550 | 		return instance ? instance : (instance = this)
1551 | 	}
1552 | 
1553 | 	ScrollReveal.isSupported = function () { return isTransformSupported() && isTransitionSupported(); };
1554 | 
1555 | 	Object.defineProperty(ScrollReveal, 'debug', {
1556 | 		get: function () { return debug || false; },
1557 | 		set: function (value) { return (debug = typeof value === 'boolean' ? value : debug); }
1558 | 	});
1559 | 
1560 | 	ScrollReveal();
1561 | 
1562 | 	return ScrollReveal;
1563 | 
1564 | }));
1565 | 


--------------------------------------------------------------------------------
/dist/scrollreveal.min.js:
--------------------------------------------------------------------------------
 1 | /*! @license ScrollReveal v4.0.9
 2 | 
 3 | 	Copyright 2021 Fisssion LLC.
 4 | 
 5 | 	Licensed under the GNU General Public License 3.0 for
 6 | 	compatible open source projects and non-commercial use.
 7 | 
 8 | 	For commercial sites, themes, projects, and applications,
 9 | 	keep your source code private/proprietary by purchasing
10 | 	a commercial license from https://scrollrevealjs.org/
11 | */
12 | var ScrollReveal=function(){"use strict";var r={delay:0,distance:"0",duration:600,easing:"cubic-bezier(0.5, 0, 0, 1)",interval:0,opacity:0,origin:"bottom",rotate:{x:0,y:0,z:0},scale:1,cleanup:!1,container:document.documentElement,desktop:!0,mobile:!0,reset:!1,useDelay:"always",viewFactor:0,viewOffset:{top:0,right:0,bottom:0,left:0},afterReset:function(){},afterReveal:function(){},beforeReset:function(){},beforeReveal:function(){}};var n={success:function(){document.documentElement.classList.add("sr"),document.body?document.body.style.height="100%":document.addEventListener("DOMContentLoaded",function(){document.body.style.height="100%"})},failure:function(){return document.documentElement.classList.remove("sr"),{clean:function(){},destroy:function(){},reveal:function(){},sync:function(){},get noop(){return!0}}}};function o(e){return"object"==typeof window.Node?e instanceof window.Node:null!==e&&"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName}function u(e,t){if(void 0===t&&(t=document),e instanceof Array)return e.filter(o);if(o(e))return[e];if(n=e,i=Object.prototype.toString.call(n),"object"==typeof window.NodeList?n instanceof window.NodeList:null!==n&&"object"==typeof n&&"number"==typeof n.length&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(i)&&(0===n.length||o(n[0])))return Array.prototype.slice.call(e);var n,i;if("string"==typeof e)try{var r=t.querySelectorAll(e);return Array.prototype.slice.call(r)}catch(e){return[]}return[]}function s(e){return null!==e&&e instanceof Object&&(e.constructor===Object||"[object Object]"===Object.prototype.toString.call(e))}function f(n,i){if(s(n))return Object.keys(n).forEach(function(e){return i(n[e],e,n)});if(n instanceof Array)return n.forEach(function(e,t){return i(e,t,n)});throw new TypeError("Expected either an array or object literal.")}function h(e){for(var t=[],n=arguments.length-1;0<n--;)t[n]=arguments[n+1];if(this.constructor.debug&&console){var i="%cScrollReveal: "+e;t.forEach(function(e){return i+="\n — "+e}),console.log(i,"color: #ea654b;")}}function t(){var n=this,i={active:[],stale:[]},t={active:[],stale:[]},r={active:[],stale:[]};try{f(u("[data-sr-id]"),function(e){var t=parseInt(e.getAttribute("data-sr-id"));i.active.push(t)})}catch(e){throw e}f(this.store.elements,function(e){-1===i.active.indexOf(e.id)&&i.stale.push(e.id)}),f(i.stale,function(e){return delete n.store.elements[e]}),f(this.store.elements,function(e){-1===r.active.indexOf(e.containerId)&&r.active.push(e.containerId),e.hasOwnProperty("sequence")&&-1===t.active.indexOf(e.sequence.id)&&t.active.push(e.sequence.id)}),f(this.store.containers,function(e){-1===r.active.indexOf(e.id)&&r.stale.push(e.id)}),f(r.stale,function(e){var t=n.store.containers[e].node;t.removeEventListener("scroll",n.delegate),t.removeEventListener("resize",n.delegate),delete n.store.containers[e]}),f(this.store.sequences,function(e){-1===t.active.indexOf(e.id)&&t.stale.push(e.id)}),f(t.stale,function(e){return delete n.store.sequences[e]})}function N(e){if(e.constructor!==Array)throw new TypeError("Expected array.");if(16===e.length)return e;if(6!==e.length)throw new RangeError("Expected array with either 6 or 16 values.");var t=z();return t[0]=e[0],t[1]=e[1],t[4]=e[2],t[5]=e[3],t[12]=e[4],t[13]=e[5],t}function z(){for(var e=[],t=0;t<16;t++)t%5==0?e.push(1):e.push(0);return e}function F(e,t){for(var n=N(e),i=N(t),r=[],o=0;o<4;o++)for(var s=[n[o],n[o+4],n[o+8],n[o+12]],a=0;a<4;a++){var c=4*a,l=[i[c],i[c+1],i[c+2],i[c+3]],d=s[0]*l[0]+s[1]*l[1]+s[2]*l[2]+s[3]*l[3];r[o+c]=d}return r}function D(e,t){var n=z();return n[0]=e,n[5]="number"==typeof t?t:e,n}var S=function(){var n={},i=document.documentElement.style;function e(e,t){if(void 0===t&&(t=i),e&&"string"==typeof e){if(n[e])return n[e];if("string"==typeof t[e])return n[e]=e;if("string"==typeof t["-webkit-"+e])return n[e]="-webkit-"+e;throw new RangeError('Unable to find "'+e+'" style property.')}throw new TypeError("Expected a string.")}return e.clearCache=function(){return n={}},e}();function p(e){var t=window.getComputedStyle(e.node),n=t.position,i=e.config,r={},o=(e.node.getAttribute("style")||"").match(/[\w-]+\s*:\s*[^;]+\s*/gi)||[];r.computed=o?o.map(function(e){return e.trim()}).join("; ")+";":"",r.generated=o.some(function(e){return e.match(/visibility\s?:\s?visible/i)})?r.computed:o.concat(["visibility: visible"]).map(function(e){return e.trim()}).join("; ")+";";var s,a,c,l,d,u,f,h,p,m,y,v,g,b=parseFloat(t.opacity),w=isNaN(parseFloat(i.opacity))?parseFloat(t.opacity):parseFloat(i.opacity),E={computed:b!==w?"opacity: "+b+";":"",generated:b!==w?"opacity: "+w+";":""},j=[];if(parseFloat(i.distance)){var T="top"===i.origin||"bottom"===i.origin?"Y":"X",k=i.distance;"top"!==i.origin&&"left"!==i.origin||(k=/^-/.test(k)?k.substr(1):"-"+k);var O=k.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g),x=O[0];switch(O[1]){case"em":k=parseInt(t.fontSize)*x;break;case"px":k=x;break;case"%":k="Y"===T?e.node.getBoundingClientRect().height*x/100:e.node.getBoundingClientRect().width*x/100;break;default:throw new RangeError("Unrecognized or missing distance unit.")}"Y"===T?j.push((c=k,(l=z())[13]=c,l)):j.push((s=k,(a=z())[12]=s,a))}i.rotate.x&&j.push((d=i.rotate.x,u=Math.PI/180*d,(f=z())[5]=f[10]=Math.cos(u),f[6]=f[9]=Math.sin(u),f[9]*=-1,f)),i.rotate.y&&j.push((h=i.rotate.y,p=Math.PI/180*h,(m=z())[0]=m[10]=Math.cos(p),m[2]=m[8]=Math.sin(p),m[2]*=-1,m)),i.rotate.z&&j.push((y=i.rotate.z,v=Math.PI/180*y,(g=z())[0]=g[5]=Math.cos(v),g[1]=g[4]=Math.sin(v),g[4]*=-1,g)),1!==i.scale&&(0===i.scale?j.push(D(2e-4)):j.push(D(i.scale)));var R={};if(j.length){R.property=S("transform"),R.computed={raw:t[R.property],matrix:function(e){if("string"==typeof e){var t=e.match(/matrix(3d)?\(([^)]+)\)/);if(t)return N(t[2].split(", ").map(parseFloat))}return z()}(t[R.property])},j.unshift(R.computed.matrix);var q=j.reduce(F);R.generated={initial:R.property+": matrix3d("+q.join(", ")+");",final:R.property+": matrix3d("+R.computed.matrix.join(", ")+");"}}else R.generated={initial:"",final:""};var A={};if(E.generated||R.generated.initial){A.property=S("transition"),A.computed=t[A.property],A.fragments=[];var P=i.delay,L=i.duration,M=i.easing;E.generated&&A.fragments.push({delayed:"opacity "+L/1e3+"s "+M+" "+P/1e3+"s",instant:"opacity "+L/1e3+"s "+M+" 0s"}),R.generated.initial&&A.fragments.push({delayed:R.property+" "+L/1e3+"s "+M+" "+P/1e3+"s",instant:R.property+" "+L/1e3+"s "+M+" 0s"}),A.computed&&!A.computed.match(/all 0s|none 0s/)&&A.fragments.unshift({delayed:A.computed,instant:A.computed});var I=A.fragments.reduce(function(e,t,n){return e.delayed+=0===n?t.delayed:", "+t.delayed,e.instant+=0===n?t.instant:", "+t.instant,e},{delayed:"",instant:""});A.generated={delayed:A.property+": "+I.delayed+";",instant:A.property+": "+I.instant+";"}}else A.generated={delayed:"",instant:""};return{inline:r,opacity:E,position:n,transform:R,transition:A}}function m(r,e){e.split(";").forEach(function(e){var t=e.split(":"),n=t[0],i=t.slice(1);n&&i&&(r.style[n.trim()]=i.join(":"))})}function y(e){var i,r=this;try{f(u(e),function(e){var t=e.getAttribute("data-sr-id");if(null!==t){i=!0;var n=r.store.elements[t];n.callbackTimer&&window.clearTimeout(n.callbackTimer.clock),m(n.node,n.styles.inline.generated),e.removeAttribute("data-sr-id"),delete r.store.elements[t]}})}catch(e){return h.call(this,"Clean failed.",e.message)}if(i)try{t.call(this)}catch(e){return h.call(this,"Clean failed.",e.message)}}function v(n){for(var e=[],t=arguments.length-1;0<t--;)e[t]=arguments[t+1];if(s(n))return f(e,function(e){f(e,function(e,t){s(e)?(n[t]&&s(n[t])||(n[t]={}),v(n[t],e)):n[t]=e})}),n;throw new TypeError("Target must be an object literal.")}function g(e){return void 0===e&&(e=navigator.userAgent),/Android|iPhone|iPad|iPod/i.test(e)}var e,b=(e=0,function(){return e++});function w(){var n=this;t.call(this),f(this.store.elements,function(e){var t=[e.styles.inline.generated];e.visible?(t.push(e.styles.opacity.computed),t.push(e.styles.transform.generated.final),e.revealed=!0):(t.push(e.styles.opacity.generated),t.push(e.styles.transform.generated.initial),e.revealed=!1),m(e.node,t.filter(function(e){return""!==e}).join(" "))}),f(this.store.containers,function(e){var t=e.node===document.documentElement?window:e.node;t.addEventListener("scroll",n.delegate),t.addEventListener("resize",n.delegate)}),this.delegate(),this.initTimeout=null}function c(e,t){void 0===t&&(t={});var n=t.pristine||this.pristine,i="always"===e.config.useDelay||"onload"===e.config.useDelay&&n||"once"===e.config.useDelay&&!e.seen,r=e.visible&&!e.revealed,o=!e.visible&&e.revealed&&e.config.reset;return t.reveal||r?function(e,t){var n=[e.styles.inline.generated,e.styles.opacity.computed,e.styles.transform.generated.final];t?n.push(e.styles.transition.generated.delayed):n.push(e.styles.transition.generated.instant);e.revealed=e.seen=!0,m(e.node,n.filter(function(e){return""!==e}).join(" ")),a.call(this,e,t)}.call(this,e,i):t.reset||o?function(e){var t=[e.styles.inline.generated,e.styles.opacity.generated,e.styles.transform.generated.initial,e.styles.transition.generated.instant];e.revealed=!1,m(e.node,t.filter(function(e){return""!==e}).join(" ")),a.call(this,e)}.call(this,e):void 0}function a(e,t){var n=this,i=t?e.config.duration+e.config.delay:e.config.duration,r=e.revealed?e.config.beforeReveal:e.config.beforeReset,o=e.revealed?e.config.afterReveal:e.config.afterReset,s=0;e.callbackTimer&&(s=Date.now()-e.callbackTimer.start,window.clearTimeout(e.callbackTimer.clock)),r(e.node),e.callbackTimer={start:Date.now(),clock:window.setTimeout(function(){o(e.node),e.callbackTimer=null,e.revealed&&!e.config.reset&&e.config.cleanup&&y.call(n,e.node)},i-s)}}function l(e,t){if(void 0===t&&(t=this.pristine),!e.visible&&e.revealed&&e.config.reset)return c.call(this,e,{reset:!0});var n=this.store.sequences[e.sequence.id],i=e.sequence.index;if(n){var r=new d(n,"visible",this.store),o=new d(n,"revealed",this.store);if(n.models={visible:r,revealed:o},!o.body.length){var s=n.members[r.body[0]],a=this.store.elements[s];if(a)return j.call(this,n,r.body[0],-1,t),j.call(this,n,r.body[0],1,t),c.call(this,a,{reveal:!0,pristine:t})}if(!n.blocked.head&&i===[].concat(o.head).pop()&&i>=[].concat(r.body).shift())return j.call(this,n,i,-1,t),c.call(this,e,{reveal:!0,pristine:t});if(!n.blocked.foot&&i===[].concat(o.foot).shift()&&i<=[].concat(r.body).pop())return j.call(this,n,i,1,t),c.call(this,e,{reveal:!0,pristine:t})}}function E(e){var t=Math.abs(e);if(isNaN(t))throw new RangeError("Invalid sequence interval.");this.id=b(),this.interval=Math.max(t,16),this.members=[],this.models={},this.blocked={head:!1,foot:!1}}function d(e,i,r){var o=this;this.head=[],this.body=[],this.foot=[],f(e.members,function(e,t){var n=r.elements[e];n&&n[i]&&o.body.push(t)}),this.body.length&&f(e.members,function(e,t){var n=r.elements[e];n&&!n[i]&&(t<o.body[0]?o.head.push(t):o.foot.push(t))})}function j(e,t,n,i){var r=this,o=["head",null,"foot"][1+n],s=e.members[t+n],a=this.store.elements[s];e.blocked[o]=!0,setTimeout(function(){e.blocked[o]=!1,a&&l.call(r,a,i)},e.interval)}function i(e,a,t){var c=this;void 0===a&&(a={}),void 0===t&&(t=!1);var l,d=[],n=a.interval||r.interval;try{n&&(l=new E(n));var i=u(e);if(!i.length)throw new Error("Invalid reveal target.");f(i.reduce(function(e,t){var n={},i=t.getAttribute("data-sr-id");i?(v(n,c.store.elements[i]),m(n.node,n.styles.inline.computed)):(n.id=b(),n.node=t,n.seen=!1,n.revealed=!1,n.visible=!1);var r=v({},n.config||c.defaults,a);if(!r.mobile&&g()||!r.desktop&&!g())return i&&y.call(c,n),e;var o,s=u(r.container)[0];if(!s)throw new Error("Invalid container.");return s.contains(t)&&(null===(o=function(t){var e=[],n=arguments.length-1;for(;0<n--;)e[n]=arguments[n+1];var i=null;return f(e,function(e){f(e,function(e){null===i&&e.node===t&&(i=e.id)})}),i}(s,d,c.store.containers))&&(o=b(),d.push({id:o,node:s})),n.config=r,n.containerId=o,n.styles=p(n),l&&(n.sequence={id:l.id,index:l.members.length},l.members.push(n.id)),e.push(n)),e},[]),function(e){(c.store.elements[e.id]=e).node.setAttribute("data-sr-id",e.id)})}catch(e){return h.call(this,"Reveal failed.",e.message)}f(d,function(e){c.store.containers[e.id]={id:e.id,node:e.node}}),l&&(this.store.sequences[l.id]=l),!0!==t&&(this.store.history.push({target:e,options:a}),this.initTimeout&&window.clearTimeout(this.initTimeout),this.initTimeout=window.setTimeout(w.bind(this),0))}var T,k=Math.sign||function(e){return(0<e)-(e<0)||+e},O=(T=Date.now(),function(e){var t=Date.now();16<t-T?e(T=t):setTimeout(function(){return O(e)},0)}),x=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||O;function R(e,t){for(var n=t?e.node.clientHeight:e.node.offsetHeight,i=t?e.node.clientWidth:e.node.offsetWidth,r=0,o=0,s=e.node;isNaN(s.offsetTop)||(r+=s.offsetTop),isNaN(s.offsetLeft)||(o+=s.offsetLeft),s=s.offsetParent;);return{bounds:{top:r,right:o+i,bottom:r+n,left:o},height:n,width:i}}function q(e,t){var i=this;void 0===e&&(e={type:"init"}),void 0===t&&(t=this.store.elements),x(function(){var n="init"===e.type||"resize"===e.type;f(i.store.containers,function(e){n&&(e.geometry=R.call(i,e,!0));var t=function(e){var t,n;return n=e.node===document.documentElement?(t=window.pageYOffset,window.pageXOffset):(t=e.node.scrollTop,e.node.scrollLeft),{top:t,left:n}}.call(i,e);e.scroll&&(e.direction={x:k(t.left-e.scroll.left),y:k(t.top-e.scroll.top)}),e.scroll=t}),f(t,function(e){(n||void 0===e.geometry)&&(e.geometry=R.call(i,e)),e.visible=function(e){void 0===e&&(e={});var t=this.store.containers[e.containerId];if(t){var n=Math.max(0,Math.min(1,e.config.viewFactor)),i=e.config.viewOffset,r=e.geometry.bounds.top+e.geometry.height*n,o=e.geometry.bounds.right-e.geometry.width*n,s=e.geometry.bounds.bottom-e.geometry.height*n,a=e.geometry.bounds.left+e.geometry.width*n,c=t.geometry.bounds.top+t.scroll.top+i.top,l=t.geometry.bounds.right+t.scroll.left-i.right,d=t.geometry.bounds.bottom+t.scroll.top-i.bottom,u=t.geometry.bounds.left+t.scroll.left+i.left;return r<d&&u<o&&c<s&&a<l||"fixed"===e.styles.position}}.call(i,e)}),f(t,function(e){e.sequence?l.call(i,e):c.call(i,e)}),i.pristine=!1})}var A,P,L,M,I,C,W,Y,$="4.0.9";function H(e){var t;if(void 0===e&&(e={}),void 0===this||Object.getPrototypeOf(this)!==H.prototype)return new H(e);if(!H.isSupported())return h.call(this,"Instantiation failed.","This browser is not supported."),n.failure();try{t=v({},C||r,e)}catch(e){return h.call(this,"Invalid configuration.",e.message),n.failure()}try{if(!u(t.container)[0])throw new Error("Invalid container.")}catch(e){return h.call(this,e.message),n.failure()}return!(C=t).mobile&&g()||!C.desktop&&!g()?(h.call(this,"This device is disabled.","desktop: "+C.desktop,"mobile: "+C.mobile),n.failure()):(n.success(),this.store={containers:{},elements:{},history:[],sequences:{}},this.pristine=!0,A=A||q.bind(this),P=P||function(){var n=this;f(this.store.elements,function(e){m(e.node,e.styles.inline.generated),e.node.removeAttribute("data-sr-id")}),f(this.store.containers,function(e){var t=e.node===document.documentElement?window:e.node;t.removeEventListener("scroll",n.delegate),t.removeEventListener("resize",n.delegate)}),this.store={containers:{},elements:{},history:[],sequences:{}}}.bind(this),L=L||i.bind(this),M=M||y.bind(this),I=I||function(){var t=this;f(this.store.history,function(e){i.call(t,e.target,e.options,!0)}),w.call(this)}.bind(this),Object.defineProperty(this,"delegate",{get:function(){return A}}),Object.defineProperty(this,"destroy",{get:function(){return P}}),Object.defineProperty(this,"reveal",{get:function(){return L}}),Object.defineProperty(this,"clean",{get:function(){return M}}),Object.defineProperty(this,"sync",{get:function(){return I}}),Object.defineProperty(this,"defaults",{get:function(){return C}}),Object.defineProperty(this,"version",{get:function(){return $}}),Object.defineProperty(this,"noop",{get:function(){return!1}}),Y||(Y=this))}return H.isSupported=function(){return("transform"in(t=document.documentElement.style)||"WebkitTransform"in t)&&("transition"in(e=document.documentElement.style)||"WebkitTransition"in e);var e,t},Object.defineProperty(H,"debug",{get:function(){return W||!1},set:function(e){return W="boolean"==typeof e?e:W}}),H(),H}();
13 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "scrollreveal",
 3 | 	"version": "4.0.9",
 4 | 	"description": "Animate elements as they scroll into view",
 5 | 	"homepage": "https://scrollrevealjs.org",
 6 | 	"main": "dist/scrollreveal.js",
 7 | 	"module": "dist/scrollreveal.es.js",
 8 | 	"jsnext:main": "dist/scrollreveal.es.js",
 9 | 	"files": [
10 | 		"dist"
11 | 	],
12 | 	"scripts": {
13 | 		"prebuild": "rm -rf dist/*",
14 | 		"build": "npm run bundle && npm run bundle:min",
15 | 		"bundle": "./node_modules/rollup/bin/rollup -c ./build/rollup.conf.js",
16 | 		"bundle:min": "./node_modules/rollup/bin/rollup -c ./build/rollup.conf.min.js",
17 | 		"lint": "./node_modules/eslint/bin/eslint.js src test",
18 | 		"pretest": "rm -rf .ignore/coverage/**/ && npm run lint",
19 | 		"test": "./node_modules/karma/bin/karma start ./test/karma.conf.js",
20 | 		"testing": "cross-env COVERAGE=true npm test -- --no-single-run",
21 | 		"coverage": "cross-env COVERAGE=true npm test",
22 | 		"sandbox:bundle": "./node_modules/rollup/bin/rollup -w -c ./.ignore/sandbox/rollup.conf.sandbox.js",
23 | 		"sandbox:server": "node ./.ignore/sandbox/server.sandbox.js",
24 | 		"coverage:server": "node ./.ignore/coverage/server.coverage.js",
25 | 		"postinstall": "node -e \"console.log('\\u001b[35m\\u001b[1mLove ScrollReveal? 🔑  Buy a license!\\u001b[22m\\u001b[35m\\n >> \\u001b[33mhttps://scrollrevealjs.org/pricing/\\u001b[0m\\n')\""
26 | 	},
27 | 	"keywords": [
28 | 		"scroll",
29 | 		"animation",
30 | 		"reveal",
31 | 		"css",
32 | 		"transform",
33 | 		"transition"
34 | 	],
35 | 	"repository": {
36 | 		"type": "git",
37 | 		"url": "https://github.com/jlmakes/scrollreveal.git"
38 | 	},
39 | 	"bugs": {
40 | 		"url": "https://github.com/jlmakes/scrollreveal/issues"
41 | 	},
42 | 	"dependencies": {
43 | 		"miniraf": "1.0.0",
44 | 		"rematrix": "0.3.0",
45 | 		"tealight": "0.3.6"
46 | 	},
47 | 	"devDependencies": {
48 | 		"chai": "^4.1.2",
49 | 		"cross-env": "^5.1.3",
50 | 		"eslint": "^4.16.0",
51 | 		"karma": "^2.0.0",
52 | 		"karma-chrome-launcher": "^2.0.0",
53 | 		"karma-coverage": "^1.1.1",
54 | 		"karma-coveralls": "^1.1.2",
55 | 		"karma-mocha": "^1.3.0",
56 | 		"karma-mocha-reporter": "^2.2.5",
57 | 		"karma-rollup-preprocessor": "^5.1.1",
58 | 		"karma-sauce-launcher": "^1.1.0",
59 | 		"karma-sinon-chai": "^1.3.3",
60 | 		"live-server": "jlmakes/live-server",
61 | 		"mocha": "^4.0.1",
62 | 		"rollup": "^0.55.0",
63 | 		"rollup-plugin-buble": "^0.x",
64 | 		"rollup-plugin-istanbul": "^1.1.0",
65 | 		"rollup-plugin-json": "^2.1.0",
66 | 		"rollup-plugin-node-resolve": "^3.0.0",
67 | 		"rollup-plugin-strip": "^1.1.1",
68 | 		"rollup-plugin-uglify": "^2.0.1",
69 | 		"rollup-watch": "^4.3.1",
70 | 		"sinon": "^4.2.0",
71 | 		"sinon-chai": "^2.8.0"
72 | 	},
73 | 	"author": "Julian Lloyd",
74 | 	"license": "GPL-3.0"
75 | }
76 | 


--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Constructor from './instance/constructor'
2 | 
3 | Constructor()
4 | 
5 | export default Constructor
6 | 


--------------------------------------------------------------------------------
/src/instance/constructor.js:
--------------------------------------------------------------------------------
  1 | import defaults from './defaults'
  2 | import mount from './mount'
  3 | 
  4 | import clean from './methods/clean'
  5 | import destroy from './methods/destroy'
  6 | import reveal from './methods/reveal'
  7 | import sync from './methods/sync'
  8 | 
  9 | import delegate from './functions/delegate'
 10 | 
 11 | import isMobile from '../utils/is-mobile'
 12 | import isTransformSupported from '../utils/is-transform-supported'
 13 | import isTransitionSupported from '../utils/is-transition-supported'
 14 | 
 15 | import deepAssign from '../utils/deep-assign'
 16 | import logger from '../utils/logger'
 17 | import $ from 'tealight'
 18 | 
 19 | import { version } from '../../package.json'
 20 | 
 21 | let boundDelegate
 22 | let boundDestroy
 23 | let boundReveal
 24 | let boundClean
 25 | let boundSync
 26 | let config
 27 | let debug
 28 | let instance
 29 | 
 30 | export default function ScrollReveal(options = {}) {
 31 | 	const invokedWithoutNew =
 32 | 		typeof this === 'undefined' ||
 33 | 		Object.getPrototypeOf(this) !== ScrollReveal.prototype
 34 | 
 35 | 	if (invokedWithoutNew) {
 36 | 		return new ScrollReveal(options)
 37 | 	}
 38 | 
 39 | 	if (!ScrollReveal.isSupported()) {
 40 | 		logger.call(this, 'Instantiation failed.', 'This browser is not supported.')
 41 | 		return mount.failure()
 42 | 	}
 43 | 
 44 | 	let buffer
 45 | 	try {
 46 | 		buffer = config
 47 | 			? deepAssign({}, config, options)
 48 | 			: deepAssign({}, defaults, options)
 49 | 	} catch (e) {
 50 | 		logger.call(this, 'Invalid configuration.', e.message)
 51 | 		return mount.failure()
 52 | 	}
 53 | 
 54 | 	try {
 55 | 		const container = $(buffer.container)[0]
 56 | 		if (!container) {
 57 | 			throw new Error('Invalid container.')
 58 | 		}
 59 | 	} catch (e) {
 60 | 		logger.call(this, e.message)
 61 | 		return mount.failure()
 62 | 	}
 63 | 
 64 | 	config = buffer
 65 | 
 66 | 	if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
 67 | 		logger.call(
 68 | 			this,
 69 | 			'This device is disabled.',
 70 | 			`desktop: ${config.desktop}`,
 71 | 			`mobile: ${config.mobile}`
 72 | 		)
 73 | 		return mount.failure()
 74 | 	}
 75 | 
 76 | 	mount.success()
 77 | 
 78 | 	this.store = {
 79 | 		containers: {},
 80 | 		elements: {},
 81 | 		history: [],
 82 | 		sequences: {}
 83 | 	}
 84 | 
 85 | 	this.pristine = true
 86 | 
 87 | 	boundDelegate = boundDelegate || delegate.bind(this)
 88 | 	boundDestroy = boundDestroy || destroy.bind(this)
 89 | 	boundReveal = boundReveal || reveal.bind(this)
 90 | 	boundClean = boundClean || clean.bind(this)
 91 | 	boundSync = boundSync || sync.bind(this)
 92 | 
 93 | 	Object.defineProperty(this, 'delegate', { get: () => boundDelegate })
 94 | 	Object.defineProperty(this, 'destroy', { get: () => boundDestroy })
 95 | 	Object.defineProperty(this, 'reveal', { get: () => boundReveal })
 96 | 	Object.defineProperty(this, 'clean', { get: () => boundClean })
 97 | 	Object.defineProperty(this, 'sync', { get: () => boundSync })
 98 | 
 99 | 	Object.defineProperty(this, 'defaults', { get: () => config })
100 | 	Object.defineProperty(this, 'version', { get: () => version })
101 | 	Object.defineProperty(this, 'noop', { get: () => false })
102 | 
103 | 	return instance ? instance : (instance = this)
104 | }
105 | 
106 | ScrollReveal.isSupported = () =>
107 | 	isTransformSupported() && isTransitionSupported()
108 | 
109 | Object.defineProperty(ScrollReveal, 'debug', {
110 | 	get: () => debug || false,
111 | 	set: value => (debug = typeof value === 'boolean' ? value : debug)
112 | })
113 | 


--------------------------------------------------------------------------------
/src/instance/defaults.js:
--------------------------------------------------------------------------------
 1 | export default {
 2 | 	delay: 0,
 3 | 	distance: '0',
 4 | 	duration: 600,
 5 | 	easing: 'cubic-bezier(0.5, 0, 0, 1)',
 6 | 	interval: 0,
 7 | 	opacity: 0,
 8 | 	origin: 'bottom',
 9 | 	rotate: {
10 | 		x: 0,
11 | 		y: 0,
12 | 		z: 0
13 | 	},
14 | 	scale: 1,
15 | 	cleanup: false,
16 | 	container: document.documentElement,
17 | 	desktop: true,
18 | 	mobile: true,
19 | 	reset: false,
20 | 	useDelay: 'always',
21 | 	viewFactor: 0.0,
22 | 	viewOffset: {
23 | 		top: 0,
24 | 		right: 0,
25 | 		bottom: 0,
26 | 		left: 0
27 | 	},
28 | 	afterReset() {},
29 | 	afterReveal() {},
30 | 	beforeReset() {},
31 | 	beforeReveal() {}
32 | }
33 | 


--------------------------------------------------------------------------------
/src/instance/functions/animate.js:
--------------------------------------------------------------------------------
 1 | import { applyStyle } from '../functions/style'
 2 | import clean from '../methods/clean'
 3 | 
 4 | export default function animate(element, force = {}) {
 5 | 	const pristine = force.pristine || this.pristine
 6 | 	const delayed =
 7 | 		element.config.useDelay === 'always' ||
 8 | 		(element.config.useDelay === 'onload' && pristine) ||
 9 | 		(element.config.useDelay === 'once' && !element.seen)
10 | 
11 | 	const shouldReveal = element.visible && !element.revealed
12 | 	const shouldReset = !element.visible && element.revealed && element.config.reset
13 | 
14 | 	if (force.reveal || shouldReveal) {
15 | 		return triggerReveal.call(this, element, delayed)
16 | 	}
17 | 
18 | 	if (force.reset || shouldReset) {
19 | 		return triggerReset.call(this, element)
20 | 	}
21 | }
22 | 
23 | function triggerReveal(element, delayed) {
24 | 	const styles = [
25 | 		element.styles.inline.generated,
26 | 		element.styles.opacity.computed,
27 | 		element.styles.transform.generated.final
28 | 	]
29 | 	if (delayed) {
30 | 		styles.push(element.styles.transition.generated.delayed)
31 | 	} else {
32 | 		styles.push(element.styles.transition.generated.instant)
33 | 	}
34 | 	element.revealed = element.seen = true
35 | 	applyStyle(element.node, styles.filter((s) => s !== '').join(' '))
36 | 	registerCallbacks.call(this, element, delayed)
37 | }
38 | 
39 | function triggerReset(element) {
40 | 	const styles = [
41 | 		element.styles.inline.generated,
42 | 		element.styles.opacity.generated,
43 | 		element.styles.transform.generated.initial,
44 | 		element.styles.transition.generated.instant
45 | 	]
46 | 	element.revealed = false
47 | 	applyStyle(element.node, styles.filter((s) => s !== '').join(' '))
48 | 	registerCallbacks.call(this, element)
49 | }
50 | 
51 | function registerCallbacks(element, isDelayed) {
52 | 	const duration = isDelayed
53 | 		? element.config.duration + element.config.delay
54 | 		: element.config.duration
55 | 
56 | 	const beforeCallback = element.revealed
57 | 		? element.config.beforeReveal
58 | 		: element.config.beforeReset
59 | 
60 | 	const afterCallback = element.revealed
61 | 		? element.config.afterReveal
62 | 		: element.config.afterReset
63 | 
64 | 	let elapsed = 0
65 | 	if (element.callbackTimer) {
66 | 		elapsed = Date.now() - element.callbackTimer.start
67 | 		window.clearTimeout(element.callbackTimer.clock)
68 | 	}
69 | 
70 | 	beforeCallback(element.node)
71 | 
72 | 	element.callbackTimer = {
73 | 		start: Date.now(),
74 | 		clock: window.setTimeout(() => {
75 | 			afterCallback(element.node)
76 | 			element.callbackTimer = null
77 | 			if (element.revealed && !element.config.reset && element.config.cleanup) {
78 | 				clean.call(this, element.node)
79 | 			}
80 | 		}, duration - elapsed)
81 | 	}
82 | }
83 | 


--------------------------------------------------------------------------------
/src/instance/functions/delegate.js:
--------------------------------------------------------------------------------
 1 | import animate from './animate'
 2 | import sequence from './sequence'
 3 | import mathSign from '../../polyfills/math-sign'
 4 | import raf from 'miniraf'
 5 | import each from '../../utils/each'
 6 | import getGeometry from '../../utils/get-geometry'
 7 | import getScrolled from '../../utils/get-scrolled'
 8 | import isElementVisible from '../../utils/is-element-visible'
 9 | 
10 | export default function delegate(
11 | 	event = { type: 'init' },
12 | 	elements = this.store.elements
13 | ) {
14 | 	raf(() => {
15 | 		const stale = event.type === 'init' || event.type === 'resize'
16 | 
17 | 		each(this.store.containers, container => {
18 | 			if (stale) {
19 | 				container.geometry = getGeometry.call(this, container, true)
20 | 			}
21 | 			const scroll = getScrolled.call(this, container)
22 | 			if (container.scroll) {
23 | 				container.direction = {
24 | 					x: mathSign(scroll.left - container.scroll.left),
25 | 					y: mathSign(scroll.top - container.scroll.top)
26 | 				}
27 | 			}
28 | 			container.scroll = scroll
29 | 		})
30 | 
31 | 		/**
32 | 		 * Due to how the sequencer is implemented, it’s
33 | 		 * important that we update the state of all
34 | 		 * elements, before any animation logic is
35 | 		 * evaluated (in the second loop below).
36 | 		 */
37 | 		each(elements, element => {
38 | 			if (stale || element.geometry === undefined) {
39 | 				element.geometry = getGeometry.call(this, element)
40 | 			}
41 | 			element.visible = isElementVisible.call(this, element)
42 | 		})
43 | 
44 | 		each(elements, element => {
45 | 			if (element.sequence) {
46 | 				sequence.call(this, element)
47 | 			} else {
48 | 				animate.call(this, element)
49 | 			}
50 | 		})
51 | 
52 | 		this.pristine = false
53 | 	})
54 | }
55 | 


--------------------------------------------------------------------------------
/src/instance/functions/initialize.js:
--------------------------------------------------------------------------------
 1 | import each from '../../utils/each'
 2 | import { applyStyle } from '../functions/style'
 3 | import rinse from './rinse'
 4 | 
 5 | export default function initialize() {
 6 | 	rinse.call(this)
 7 | 
 8 | 	each(this.store.elements, element => {
 9 | 		let styles = [element.styles.inline.generated]
10 | 
11 | 		if (element.visible) {
12 | 			styles.push(element.styles.opacity.computed)
13 | 			styles.push(element.styles.transform.generated.final)
14 | 			element.revealed = true
15 | 		} else {
16 | 			styles.push(element.styles.opacity.generated)
17 | 			styles.push(element.styles.transform.generated.initial)
18 | 			element.revealed = false
19 | 		}
20 | 
21 | 		applyStyle(element.node, styles.filter((s) => s !== '').join(' '))
22 | 	})
23 | 
24 | 	each(this.store.containers, container => {
25 | 		const target =
26 | 			container.node === document.documentElement ? window : container.node
27 | 		target.addEventListener('scroll', this.delegate)
28 | 		target.addEventListener('resize', this.delegate)
29 | 	})
30 | 
31 | 	/**
32 | 	 * Manually invoke delegate once to capture
33 | 	 * element and container dimensions, container
34 | 	 * scroll position, and trigger any valid reveals
35 | 	 */
36 | 	this.delegate()
37 | 
38 | 	/**
39 | 	 * Wipe any existing `setTimeout` now
40 | 	 * that initialization has completed.
41 | 	 */
42 | 	this.initTimeout = null
43 | }
44 | 


--------------------------------------------------------------------------------
/src/instance/functions/rinse.js:
--------------------------------------------------------------------------------
 1 | import $ from 'tealight'
 2 | import each from '../../utils/each'
 3 | 
 4 | export default function rinse() {
 5 | 	const struct = () => ({
 6 | 		active: [],
 7 | 		stale: []
 8 | 	})
 9 | 
10 | 	const elementIds = struct()
11 | 	const sequenceIds = struct()
12 | 	const containerIds = struct()
13 | 
14 | 	/**
15 | 	 * Take stock of active element IDs.
16 | 	 */
17 | 	try {
18 | 		each($('[data-sr-id]'), node => {
19 | 			const id = parseInt(node.getAttribute('data-sr-id'))
20 | 			elementIds.active.push(id)
21 | 		})
22 | 	} catch (e) {
23 | 		throw e
24 | 	}
25 | 	/**
26 | 	 * Destroy stale elements.
27 | 	 */
28 | 	each(this.store.elements, element => {
29 | 		if (elementIds.active.indexOf(element.id) === -1) {
30 | 			elementIds.stale.push(element.id)
31 | 		}
32 | 	})
33 | 
34 | 	each(elementIds.stale, staleId => delete this.store.elements[staleId])
35 | 
36 | 	/**
37 | 	 * Take stock of active container and sequence IDs.
38 | 	 */
39 | 	each(this.store.elements, element => {
40 | 		if (containerIds.active.indexOf(element.containerId) === -1) {
41 | 			containerIds.active.push(element.containerId)
42 | 		}
43 | 		if (element.hasOwnProperty('sequence')) {
44 | 			if (sequenceIds.active.indexOf(element.sequence.id) === -1) {
45 | 				sequenceIds.active.push(element.sequence.id)
46 | 			}
47 | 		}
48 | 	})
49 | 
50 | 	/**
51 | 	 * Destroy stale containers.
52 | 	 */
53 | 	each(this.store.containers, container => {
54 | 		if (containerIds.active.indexOf(container.id) === -1) {
55 | 			containerIds.stale.push(container.id)
56 | 		}
57 | 	})
58 | 
59 | 	each(containerIds.stale, staleId => {
60 | 		const stale = this.store.containers[staleId].node
61 | 		stale.removeEventListener('scroll', this.delegate)
62 | 		stale.removeEventListener('resize', this.delegate)
63 | 		delete this.store.containers[staleId]
64 | 	})
65 | 
66 | 	/**
67 | 	 * Destroy stale sequences.
68 | 	 */
69 | 	each(this.store.sequences, sequence => {
70 | 		if (sequenceIds.active.indexOf(sequence.id) === -1) {
71 | 			sequenceIds.stale.push(sequence.id)
72 | 		}
73 | 	})
74 | 
75 | 	each(sequenceIds.stale, staleId => delete this.store.sequences[staleId])
76 | }
77 | 


--------------------------------------------------------------------------------
/src/instance/functions/sequence.js:
--------------------------------------------------------------------------------
  1 | import animate from './animate'
  2 | import each from '../../utils/each'
  3 | import nextUniqueId from '../../utils/next-unique-id'
  4 | 
  5 | export default function sequence(element, pristine = this.pristine) {
  6 | 	/**
  7 | 	 * We first check if the element should reset.
  8 | 	 */
  9 | 	if (!element.visible && element.revealed && element.config.reset) {
 10 | 		return animate.call(this, element, { reset: true })
 11 | 	}
 12 | 
 13 | 	const seq = this.store.sequences[element.sequence.id]
 14 | 	const i = element.sequence.index
 15 | 
 16 | 	if (seq) {
 17 | 		const visible = new SequenceModel(seq, 'visible', this.store)
 18 | 		const revealed = new SequenceModel(seq, 'revealed', this.store)
 19 | 
 20 | 		seq.models = { visible, revealed }
 21 | 
 22 | 		/**
 23 | 		 * If the sequence has no revealed members,
 24 | 		 * then we reveal the first visible element
 25 | 		 * within that sequence.
 26 | 		 *
 27 | 		 * The sequence then cues a recursive call
 28 | 		 * in both directions.
 29 | 		 */
 30 | 		if (!revealed.body.length) {
 31 | 			const nextId = seq.members[visible.body[0]]
 32 | 			const nextElement = this.store.elements[nextId]
 33 | 
 34 | 			if (nextElement) {
 35 | 				cue.call(this, seq, visible.body[0], -1, pristine)
 36 | 				cue.call(this, seq, visible.body[0], +1, pristine)
 37 | 				return animate.call(this, nextElement, { reveal: true, pristine })
 38 | 			}
 39 | 		}
 40 | 
 41 | 		/**
 42 | 		 * If our element isn’t resetting, we check the
 43 | 		 * element sequence index against the head, and
 44 | 		 * then the foot of the sequence.
 45 | 		 */
 46 | 		if (
 47 | 			!seq.blocked.head &&
 48 | 			i === [...revealed.head].pop() &&
 49 | 			i >= [...visible.body].shift()
 50 | 		) {
 51 | 			cue.call(this, seq, i, -1, pristine)
 52 | 			return animate.call(this, element, { reveal: true, pristine })
 53 | 		}
 54 | 
 55 | 		if (
 56 | 			!seq.blocked.foot &&
 57 | 			i === [...revealed.foot].shift() &&
 58 | 			i <= [...visible.body].pop()
 59 | 		) {
 60 | 			cue.call(this, seq, i, +1, pristine)
 61 | 			return animate.call(this, element, { reveal: true, pristine })
 62 | 		}
 63 | 	}
 64 | }
 65 | 
 66 | export function Sequence(interval) {
 67 | 	const i = Math.abs(interval)
 68 | 	if (!isNaN(i)) {
 69 | 		this.id = nextUniqueId()
 70 | 		this.interval = Math.max(i, 16)
 71 | 		this.members = []
 72 | 		this.models = {}
 73 | 		this.blocked = {
 74 | 			head: false,
 75 | 			foot: false
 76 | 		}
 77 | 	} else {
 78 | 		throw new RangeError('Invalid sequence interval.')
 79 | 	}
 80 | }
 81 | 
 82 | function SequenceModel(seq, prop, store) {
 83 | 	this.head = []
 84 | 	this.body = []
 85 | 	this.foot = []
 86 | 
 87 | 	each(seq.members, (id, index) => {
 88 | 		const element = store.elements[id]
 89 | 		if (element && element[prop]) {
 90 | 			this.body.push(index)
 91 | 		}
 92 | 	})
 93 | 
 94 | 	if (this.body.length) {
 95 | 		each(seq.members, (id, index) => {
 96 | 			const element = store.elements[id]
 97 | 			if (element && !element[prop]) {
 98 | 				if (index < this.body[0]) {
 99 | 					this.head.push(index)
100 | 				} else {
101 | 					this.foot.push(index)
102 | 				}
103 | 			}
104 | 		})
105 | 	}
106 | }
107 | 
108 | function cue(seq, i, direction, pristine) {
109 | 	const blocked = ['head', null, 'foot'][1 + direction]
110 | 	const nextId = seq.members[i + direction]
111 | 	const nextElement = this.store.elements[nextId]
112 | 
113 | 	seq.blocked[blocked] = true
114 | 
115 | 	setTimeout(() => {
116 | 		seq.blocked[blocked] = false
117 | 		if (nextElement) {
118 | 			sequence.call(this, nextElement, pristine)
119 | 		}
120 | 	}, seq.interval)
121 | }
122 | 


--------------------------------------------------------------------------------
/src/instance/functions/style.js:
--------------------------------------------------------------------------------
  1 | import {
  2 | 	multiply,
  3 | 	parse,
  4 | 	rotateX,
  5 | 	rotateY,
  6 | 	rotateZ,
  7 | 	scale,
  8 | 	translateX,
  9 | 	translateY
 10 | } from 'rematrix'
 11 | import getPrefixedCssProp from '../../utils/get-prefixed-css-prop'
 12 | 
 13 | export default function style(element) {
 14 | 	const computed = window.getComputedStyle(element.node)
 15 | 	const position = computed.position
 16 | 	const config = element.config
 17 | 
 18 | 	/**
 19 | 	 * Generate inline styles
 20 | 	 */
 21 | 	const inline = {}
 22 | 	const inlineStyle = element.node.getAttribute('style') || ''
 23 | 	const inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || []
 24 | 
 25 | 	inline.computed = inlineMatch ? inlineMatch.map(m => m.trim()).join('; ') + ';' : ''
 26 | 
 27 | 	inline.generated = inlineMatch.some(m => m.match(/visibility\s?:\s?visible/i))
 28 | 		? inline.computed
 29 | 		: [...inlineMatch, 'visibility: visible'].map(m => m.trim()).join('; ') + ';'
 30 | 
 31 | 	/**
 32 | 	 * Generate opacity styles
 33 | 	 */
 34 | 	const computedOpacity = parseFloat(computed.opacity)
 35 | 	const configOpacity = !isNaN(parseFloat(config.opacity))
 36 | 		? parseFloat(config.opacity)
 37 | 		: parseFloat(computed.opacity)
 38 | 
 39 | 	const opacity = {
 40 | 		computed: computedOpacity !== configOpacity ? `opacity: ${computedOpacity};` : '',
 41 | 		generated: computedOpacity !== configOpacity ? `opacity: ${configOpacity};` : ''
 42 | 	}
 43 | 
 44 | 	/**
 45 | 	 * Generate transformation styles
 46 | 	 */
 47 | 	const transformations = []
 48 | 
 49 | 	if (parseFloat(config.distance)) {
 50 | 		const axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X'
 51 | 
 52 | 		/**
 53 | 		 * Let’s make sure our our pixel distances are negative for top and left.
 54 | 		 * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS.
 55 | 		 */
 56 | 		let distance = config.distance
 57 | 		if (config.origin === 'top' || config.origin === 'left') {
 58 | 			distance = /^-/.test(distance) ? distance.substr(1) : `-${distance}`
 59 | 		}
 60 | 
 61 | 		const [value, unit] = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g)
 62 | 
 63 | 		switch (unit) {
 64 | 			case 'em':
 65 | 				distance = parseInt(computed.fontSize) * value
 66 | 				break
 67 | 			case 'px':
 68 | 				distance = value
 69 | 				break
 70 | 			case '%':
 71 | 				/**
 72 | 				 * Here we use `getBoundingClientRect` instead of
 73 | 				 * the existing data attached to `element.geometry`
 74 | 				 * because only the former includes any transformations
 75 | 				 * current applied to the element.
 76 | 				 *
 77 | 				 * If that behavior ends up being unintuitive, this
 78 | 				 * logic could instead utilize `element.geometry.height`
 79 | 				 * and `element.geoemetry.width` for the distance calculation
 80 | 				 */
 81 | 				distance =
 82 | 					axis === 'Y'
 83 | 						? (element.node.getBoundingClientRect().height * value) / 100
 84 | 						: (element.node.getBoundingClientRect().width * value) / 100
 85 | 				break
 86 | 			default:
 87 | 				throw new RangeError('Unrecognized or missing distance unit.')
 88 | 		}
 89 | 
 90 | 		if (axis === 'Y') {
 91 | 			transformations.push(translateY(distance))
 92 | 		} else {
 93 | 			transformations.push(translateX(distance))
 94 | 		}
 95 | 	}
 96 | 
 97 | 	if (config.rotate.x) transformations.push(rotateX(config.rotate.x))
 98 | 	if (config.rotate.y) transformations.push(rotateY(config.rotate.y))
 99 | 	if (config.rotate.z) transformations.push(rotateZ(config.rotate.z))
100 | 	if (config.scale !== 1) {
101 | 		if (config.scale === 0) {
102 | 			/**
103 | 			 * The CSS Transforms matrix interpolation specification
104 | 			 * basically disallows transitions of non-invertible
105 | 			 * matrixes, which means browsers won't transition
106 | 			 * elements with zero scale.
107 | 			 *
108 | 			 * That’s inconvenient for the API and developer
109 | 			 * experience, so we simply nudge their value
110 | 			 * slightly above zero; this allows browsers
111 | 			 * to transition our element as expected.
112 | 			 *
113 | 			 * `0.0002` was the smallest number
114 | 			 * that performed across browsers.
115 | 			 */
116 | 			transformations.push(scale(0.0002))
117 | 		} else {
118 | 			transformations.push(scale(config.scale))
119 | 		}
120 | 	}
121 | 
122 | 	const transform = {}
123 | 	if (transformations.length) {
124 | 		transform.property = getPrefixedCssProp('transform')
125 | 		/**
126 | 		 * The default computed transform value should be one of:
127 | 		 * undefined || 'none' || 'matrix()' || 'matrix3d()'
128 | 		 */
129 | 		transform.computed = {
130 | 			raw: computed[transform.property],
131 | 			matrix: parse(computed[transform.property])
132 | 		}
133 | 
134 | 		transformations.unshift(transform.computed.matrix)
135 | 		const product = transformations.reduce(multiply)
136 | 
137 | 		transform.generated = {
138 | 			initial: `${transform.property}: matrix3d(${product.join(', ')});`,
139 | 			final: `${transform.property}: matrix3d(${transform.computed.matrix.join(', ')});`
140 | 		}
141 | 	} else {
142 | 		transform.generated = {
143 | 			initial: '',
144 | 			final: ''
145 | 		}
146 | 	}
147 | 
148 | 	/**
149 | 	 * Generate transition styles
150 | 	 */
151 | 	let transition = {}
152 | 	if (opacity.generated || transform.generated.initial) {
153 | 		transition.property = getPrefixedCssProp('transition')
154 | 		transition.computed = computed[transition.property]
155 | 		transition.fragments = []
156 | 
157 | 		const { delay, duration, easing } = config
158 | 
159 | 		if (opacity.generated) {
160 | 			transition.fragments.push({
161 | 				delayed: `opacity ${duration / 1000}s ${easing} ${delay / 1000}s`,
162 | 				instant: `opacity ${duration / 1000}s ${easing} 0s`
163 | 			})
164 | 		}
165 | 
166 | 		if (transform.generated.initial) {
167 | 			transition.fragments.push({
168 | 				delayed: `${transform.property} ${duration / 1000}s ${easing} ${delay / 1000}s`,
169 | 				instant: `${transform.property} ${duration / 1000}s ${easing} 0s`
170 | 			})
171 | 		}
172 | 
173 | 		/**
174 | 		 * The default computed transition property should be undefined, or one of:
175 | 		 * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()'
176 | 		 */
177 | 		let hasCustomTransition =
178 | 			transition.computed && !transition.computed.match(/all 0s|none 0s/)
179 | 
180 | 		if (hasCustomTransition) {
181 | 			transition.fragments.unshift({
182 | 				delayed: transition.computed,
183 | 				instant: transition.computed
184 | 			})
185 | 		}
186 | 
187 | 		const composed = transition.fragments.reduce(
188 | 			(composition, fragment, i) => {
189 | 				composition.delayed += i === 0 ? fragment.delayed : `, ${fragment.delayed}`
190 | 				composition.instant += i === 0 ? fragment.instant : `, ${fragment.instant}`
191 | 				return composition
192 | 			},
193 | 			{
194 | 				delayed: '',
195 | 				instant: ''
196 | 			}
197 | 		)
198 | 
199 | 		transition.generated = {
200 | 			delayed: `${transition.property}: ${composed.delayed};`,
201 | 			instant: `${transition.property}: ${composed.instant};`
202 | 		}
203 | 	} else {
204 | 		transition.generated = {
205 | 			delayed: '',
206 | 			instant: ''
207 | 		}
208 | 	}
209 | 
210 | 	return {
211 | 		inline,
212 | 		opacity,
213 | 		position,
214 | 		transform,
215 | 		transition
216 | 	}
217 | }
218 | 
219 | /**
220 |  * apply a CSS string to an element using the CSSOM (element.style) rather
221 |  * than setAttribute, which may violate the content security policy.
222 |  *
223 |  * @param {Node}   [el]  Element to receive styles.
224 |  * @param {string} [declaration] Styles to apply.
225 |  */
226 | export function applyStyle (el, declaration) {
227 | 	declaration.split(';').forEach(pair => {
228 | 		const [property, ...value] = pair.split(':')
229 | 		if (property && value) {
230 | 			el.style[property.trim()] = value.join(':')
231 | 		}
232 | 	})
233 | }
234 | 
235 | 


--------------------------------------------------------------------------------
/src/instance/methods/clean.js:
--------------------------------------------------------------------------------
 1 | import $ from 'tealight'
 2 | import each from '../../utils/each'
 3 | import logger from '../../utils/logger'
 4 | import rinse from '../functions/rinse'
 5 | import { applyStyle } from '../functions/style'
 6 | 
 7 | export default function clean(target) {
 8 | 	let dirty
 9 | 	try {
10 | 		each($(target), node => {
11 | 			const id = node.getAttribute('data-sr-id')
12 | 			if (id !== null) {
13 | 				dirty = true
14 | 				const element = this.store.elements[id]
15 | 				if (element.callbackTimer) {
16 | 					window.clearTimeout(element.callbackTimer.clock)
17 | 				}
18 | 				applyStyle(element.node, element.styles.inline.generated)
19 | 				node.removeAttribute('data-sr-id')
20 | 				delete this.store.elements[id]
21 | 			}
22 | 		})
23 | 	} catch (e) {
24 | 		return logger.call(this, 'Clean failed.', e.message)
25 | 	}
26 | 
27 | 	if (dirty) {
28 | 		try {
29 | 			rinse.call(this)
30 | 		} catch (e) {
31 | 			return logger.call(this, 'Clean failed.', e.message)
32 | 		}
33 | 	}
34 | }
35 | 


--------------------------------------------------------------------------------
/src/instance/methods/destroy.js:
--------------------------------------------------------------------------------
 1 | import each from '../../utils/each'
 2 | import { applyStyle } from '../functions/style'
 3 | 
 4 | export default function destroy() {
 5 | 	/**
 6 | 	 * Remove all generated styles and element ids
 7 | 	 */
 8 | 	each(this.store.elements, element => {
 9 | 		applyStyle(element.node, element.styles.inline.generated)
10 | 		element.node.removeAttribute('data-sr-id')
11 | 	})
12 | 
13 | 	/**
14 | 	 * Remove all event listeners.
15 | 	 */
16 | 	each(this.store.containers, container => {
17 | 		const target =
18 | 			container.node === document.documentElement ? window : container.node
19 | 		target.removeEventListener('scroll', this.delegate)
20 | 		target.removeEventListener('resize', this.delegate)
21 | 	})
22 | 
23 | 	/**
24 | 	 * Clear all data from the store
25 | 	 */
26 | 	this.store = {
27 | 		containers: {},
28 | 		elements: {},
29 | 		history: [],
30 | 		sequences: {}
31 | 	}
32 | }
33 | 


--------------------------------------------------------------------------------
/src/instance/methods/reveal.js:
--------------------------------------------------------------------------------
  1 | import tealight from 'tealight'
  2 | import deepAssign from '../../utils/deep-assign'
  3 | import each from '../../utils/each'
  4 | import isMobile from '../../utils/is-mobile'
  5 | import logger from '../../utils/logger'
  6 | import nextUniqueId from '../../utils/next-unique-id'
  7 | import defaults from '../defaults'
  8 | import initialize from '../functions/initialize'
  9 | import { Sequence } from '../functions/sequence'
 10 | import style, { applyStyle } from '../functions/style'
 11 | import clean from '../methods/clean'
 12 | 
 13 | export default function reveal(target, options = {}, syncing = false) {
 14 | 	const containerBuffer = []
 15 | 	let sequence
 16 | 	let interval = options.interval || defaults.interval
 17 | 
 18 | 	try {
 19 | 		if (interval) {
 20 | 			sequence = new Sequence(interval)
 21 | 		}
 22 | 
 23 | 		const nodes = tealight(target)
 24 | 		if (!nodes.length) {
 25 | 			throw new Error('Invalid reveal target.')
 26 | 		}
 27 | 
 28 | 		const elements = nodes.reduce((elementBuffer, elementNode) => {
 29 | 			const element = {}
 30 | 			const existingId = elementNode.getAttribute('data-sr-id')
 31 | 
 32 | 			if (existingId) {
 33 | 				deepAssign(element, this.store.elements[existingId])
 34 | 
 35 | 				/**
 36 | 				 * In order to prevent previously generated styles
 37 | 				 * from throwing off the new styles, the style tag
 38 | 				 * has to be reverted to its pre-reveal state.
 39 | 				 */
 40 | 				applyStyle(element.node, element.styles.inline.computed)
 41 | 			} else {
 42 | 				element.id = nextUniqueId()
 43 | 				element.node = elementNode
 44 | 				element.seen = false
 45 | 				element.revealed = false
 46 | 				element.visible = false
 47 | 			}
 48 | 
 49 | 			const config = deepAssign({}, element.config || this.defaults, options)
 50 | 
 51 | 			if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
 52 | 				if (existingId) {
 53 | 					clean.call(this, element)
 54 | 				}
 55 | 				return elementBuffer // skip elements that are disabled
 56 | 			}
 57 | 
 58 | 			const containerNode = tealight(config.container)[0]
 59 | 			if (!containerNode) {
 60 | 				throw new Error('Invalid container.')
 61 | 			}
 62 | 			if (!containerNode.contains(elementNode)) {
 63 | 				return elementBuffer // skip elements found outside the container
 64 | 			}
 65 | 
 66 | 			let containerId
 67 | 			{
 68 | 				containerId = getContainerId(
 69 | 					containerNode,
 70 | 					containerBuffer,
 71 | 					this.store.containers
 72 | 				)
 73 | 				if (containerId === null) {
 74 | 					containerId = nextUniqueId()
 75 | 					containerBuffer.push({ id: containerId, node: containerNode })
 76 | 				}
 77 | 			}
 78 | 
 79 | 			element.config = config
 80 | 			element.containerId = containerId
 81 | 			element.styles = style(element)
 82 | 
 83 | 			if (sequence) {
 84 | 				element.sequence = {
 85 | 					id: sequence.id,
 86 | 					index: sequence.members.length
 87 | 				}
 88 | 				sequence.members.push(element.id)
 89 | 			}
 90 | 
 91 | 			elementBuffer.push(element)
 92 | 			return elementBuffer
 93 | 		}, [])
 94 | 
 95 | 		/**
 96 | 		 * Modifying the DOM via setAttribute needs to be handled
 97 | 		 * separately from reading computed styles in the map above
 98 | 		 * for the browser to batch DOM changes (limiting reflows)
 99 | 		 */
100 | 		each(elements, element => {
101 | 			this.store.elements[element.id] = element
102 | 			element.node.setAttribute('data-sr-id', element.id)
103 | 		})
104 | 	} catch (e) {
105 | 		return logger.call(this, 'Reveal failed.', e.message)
106 | 	}
107 | 
108 | 	/**
109 | 	 * Now that element set-up is complete...
110 | 	 * Let’s commit any container and sequence data we have to the store.
111 | 	 */
112 | 	each(containerBuffer, container => {
113 | 		this.store.containers[container.id] = {
114 | 			id: container.id,
115 | 			node: container.node
116 | 		}
117 | 	})
118 | 	if (sequence) {
119 | 		this.store.sequences[sequence.id] = sequence
120 | 	}
121 | 
122 | 	/**
123 | 	 * If reveal wasn't invoked by sync, we want to
124 | 	 * make sure to add this call to the history.
125 | 	 */
126 | 	if (syncing !== true) {
127 | 		this.store.history.push({ target, options })
128 | 
129 | 		/**
130 | 		 * Push initialization to the event queue, giving
131 | 		 * multiple reveal calls time to be interpreted.
132 | 		 */
133 | 		if (this.initTimeout) {
134 | 			window.clearTimeout(this.initTimeout)
135 | 		}
136 | 		this.initTimeout = window.setTimeout(initialize.bind(this), 0)
137 | 	}
138 | }
139 | 
140 | function getContainerId(node, ...collections) {
141 | 	let id = null
142 | 	each(collections, collection => {
143 | 		each(collection, container => {
144 | 			if (id === null && container.node === node) {
145 | 				id = container.id
146 | 			}
147 | 		})
148 | 	})
149 | 	return id
150 | }
151 | 


--------------------------------------------------------------------------------
/src/instance/methods/sync.js:
--------------------------------------------------------------------------------
 1 | import initialize from '../functions/initialize'
 2 | import each from '../../utils/each'
 3 | import reveal from './reveal'
 4 | 
 5 | /**
 6 |  * Re-runs the reveal method for each record stored in history,
 7 |  * for capturing new content asynchronously loaded into the DOM.
 8 |  */
 9 | export default function sync() {
10 | 	each(this.store.history, record => {
11 | 		reveal.call(this, record.target, record.options, true)
12 | 	})
13 | 
14 | 	initialize.call(this)
15 | }
16 | 


--------------------------------------------------------------------------------
/src/instance/mount.js:
--------------------------------------------------------------------------------
 1 | function failure() {
 2 | 	document.documentElement.classList.remove('sr')
 3 | 
 4 | 	return {
 5 | 		clean() {},
 6 | 		destroy() {},
 7 | 		reveal() {},
 8 | 		sync() {},
 9 | 		get noop() {
10 | 			return true
11 | 		}
12 | 	}
13 | }
14 | 
15 | function success() {
16 | 	document.documentElement.classList.add('sr')
17 | 
18 | 	if (document.body) {
19 | 		document.body.style.height = '100%'
20 | 	} else {
21 | 		document.addEventListener('DOMContentLoaded', () => {
22 | 			document.body.style.height = '100%'
23 | 		})
24 | 	}
25 | }
26 | 
27 | export default { success, failure }
28 | 


--------------------------------------------------------------------------------
/src/polyfills/math-sign.js:
--------------------------------------------------------------------------------
1 | export const polyfill = x => (x > 0) - (x < 0) || +x
2 | export default Math.sign || polyfill
3 | 


--------------------------------------------------------------------------------
/src/utils/deep-assign.js:
--------------------------------------------------------------------------------
 1 | import isObject from './is-object'
 2 | import each from './each'
 3 | 
 4 | export default function deepAssign(target, ...sources) {
 5 | 	if (isObject(target)) {
 6 | 		each(sources, source => {
 7 | 			each(source, (data, key) => {
 8 | 				if (isObject(data)) {
 9 | 					if (!target[key] || !isObject(target[key])) {
10 | 						target[key] = {}
11 | 					}
12 | 					deepAssign(target[key], data)
13 | 				} else {
14 | 					target[key] = data
15 | 				}
16 | 			})
17 | 		})
18 | 		return target
19 | 	} else {
20 | 		throw new TypeError('Target must be an object literal.')
21 | 	}
22 | }
23 | 


--------------------------------------------------------------------------------
/src/utils/each.js:
--------------------------------------------------------------------------------
 1 | import isObject from './is-object'
 2 | 
 3 | export default function each(collection, callback) {
 4 | 	if (isObject(collection)) {
 5 | 		const keys = Object.keys(collection)
 6 | 		return keys.forEach(key => callback(collection[key], key, collection))
 7 | 	}
 8 | 	if (collection instanceof Array) {
 9 | 		return collection.forEach((item, i) => callback(item, i, collection))
10 | 	}
11 | 	throw new TypeError('Expected either an array or object literal.')
12 | }
13 | 


--------------------------------------------------------------------------------
/src/utils/get-geometry.js:
--------------------------------------------------------------------------------
 1 | export default function getGeometry(target, isContainer) {
 2 | 	/**
 3 | 	 * We want to ignore padding and scrollbars for container elements.
 4 | 	 * More information here: https://goo.gl/vOZpbz
 5 | 	 */
 6 | 	const height = isContainer ? target.node.clientHeight : target.node.offsetHeight
 7 | 	const width = isContainer ? target.node.clientWidth : target.node.offsetWidth
 8 | 
 9 | 	let offsetTop = 0
10 | 	let offsetLeft = 0
11 | 	let node = target.node
12 | 
13 | 	do {
14 | 		if (!isNaN(node.offsetTop)) {
15 | 			offsetTop += node.offsetTop
16 | 		}
17 | 		if (!isNaN(node.offsetLeft)) {
18 | 			offsetLeft += node.offsetLeft
19 | 		}
20 | 		node = node.offsetParent
21 | 	} while (node)
22 | 
23 | 	return {
24 | 		bounds: {
25 | 			top: offsetTop,
26 | 			right: offsetLeft + width,
27 | 			bottom: offsetTop + height,
28 | 			left: offsetLeft
29 | 		},
30 | 		height,
31 | 		width
32 | 	}
33 | }
34 | 


--------------------------------------------------------------------------------
/src/utils/get-prefixed-css-prop.js:
--------------------------------------------------------------------------------
 1 | const getPrefixedCssProp = (() => {
 2 | 	let properties = {}
 3 | 	const style = document.documentElement.style
 4 | 
 5 | 	function getPrefixedCssProperty(name, source = style) {
 6 | 		if (name && typeof name === 'string') {
 7 | 			if (properties[name]) {
 8 | 				return properties[name]
 9 | 			}
10 | 			if (typeof source[name] === 'string') {
11 | 				return (properties[name] = name)
12 | 			}
13 | 			if (typeof source[`-webkit-${name}`] === 'string') {
14 | 				return (properties[name] = `-webkit-${name}`)
15 | 			}
16 | 			throw new RangeError(`Unable to find "${name}" style property.`)
17 | 		}
18 | 		throw new TypeError('Expected a string.')
19 | 	}
20 | 
21 | 	getPrefixedCssProperty.clearCache = () => (properties = {})
22 | 
23 | 	return getPrefixedCssProperty
24 | })()
25 | 
26 | export default getPrefixedCssProp
27 | 


--------------------------------------------------------------------------------
/src/utils/get-scrolled.js:
--------------------------------------------------------------------------------
 1 | export default function getScrolled(container) {
 2 | 	let top, left
 3 | 	if (container.node === document.documentElement) {
 4 | 		top = window.pageYOffset
 5 | 		left = window.pageXOffset
 6 | 	} else {
 7 | 		top = container.node.scrollTop
 8 | 		left = container.node.scrollLeft
 9 | 	}
10 | 	return { top, left }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/utils/is-element-visible.js:
--------------------------------------------------------------------------------
 1 | export default function isElementVisible(element = {}) {
 2 | 	const container = this.store.containers[element.containerId]
 3 | 	if (!container) return
 4 | 
 5 | 	const viewFactor = Math.max(0, Math.min(1, element.config.viewFactor))
 6 | 	const viewOffset = element.config.viewOffset
 7 | 
 8 | 	const elementBounds = {
 9 | 		top: element.geometry.bounds.top + element.geometry.height * viewFactor,
10 | 		right: element.geometry.bounds.right - element.geometry.width * viewFactor,
11 | 		bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor,
12 | 		left: element.geometry.bounds.left + element.geometry.width * viewFactor
13 | 	}
14 | 
15 | 	const containerBounds = {
16 | 		top: container.geometry.bounds.top + container.scroll.top + viewOffset.top,
17 | 		right: container.geometry.bounds.right + container.scroll.left - viewOffset.right,
18 | 		bottom:
19 | 			container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom,
20 | 		left: container.geometry.bounds.left + container.scroll.left + viewOffset.left
21 | 	}
22 | 
23 | 	return (
24 | 		(elementBounds.top < containerBounds.bottom &&
25 | 			elementBounds.right > containerBounds.left &&
26 | 			elementBounds.bottom > containerBounds.top &&
27 | 			elementBounds.left < containerBounds.right) ||
28 | 		element.styles.position === 'fixed'
29 | 	)
30 | }
31 | 


--------------------------------------------------------------------------------
/src/utils/is-mobile.js:
--------------------------------------------------------------------------------
1 | export default function isMobile(agent = navigator.userAgent) {
2 | 	return /Android|iPhone|iPad|iPod/i.test(agent)
3 | }
4 | 


--------------------------------------------------------------------------------
/src/utils/is-object.js:
--------------------------------------------------------------------------------
1 | export default function isObject(x) {
2 | 	return (
3 | 		x !== null &&
4 | 		x instanceof Object &&
5 | 		(x.constructor === Object ||
6 | 			Object.prototype.toString.call(x) === '[object Object]')
7 | 	)
8 | }
9 | 


--------------------------------------------------------------------------------
/src/utils/is-transform-supported.js:
--------------------------------------------------------------------------------
1 | export default function isTransformSupported() {
2 | 	const style = document.documentElement.style
3 | 	return 'transform' in style || 'WebkitTransform' in style
4 | }
5 | 


--------------------------------------------------------------------------------
/src/utils/is-transition-supported.js:
--------------------------------------------------------------------------------
1 | export default function isTransitionSupported() {
2 | 	const style = document.documentElement.style
3 | 	return 'transition' in style || 'WebkitTransition' in style
4 | }
5 | 


--------------------------------------------------------------------------------
/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | export default function logger(message, ...details) {
2 | 	if (this.constructor.debug && console) {
3 | 		let report = `%cScrollReveal: ${message}`
4 | 		details.forEach(detail => (report += `\n — ${detail}`))
5 | 		console.log(report, 'color: #ea654b;') // eslint-disable-line no-console
6 | 	}
7 | }
8 | 


--------------------------------------------------------------------------------
/src/utils/next-unique-id.js:
--------------------------------------------------------------------------------
1 | const nextUniqueId = (() => {
2 | 	let uid = 0
3 | 	return () => uid++
4 | })()
5 | 
6 | export default nextUniqueId
7 | 


--------------------------------------------------------------------------------
/test/instance/constructor.spec.js:
--------------------------------------------------------------------------------
  1 | import ScrollReveal from '../../src/instance/constructor'
  2 | import isMobile from '../../src/utils/is-mobile'
  3 | import { version } from '../../package.json'
  4 | 
  5 | describe('ScrollReveal', () => {
  6 | 	describe('Constructor', () => {
  7 | 		it('should return a new instance with `new` keyword', () => {
  8 | 			const sr = new ScrollReveal()
  9 | 			expect(sr).to.exist
 10 | 		})
 11 | 
 12 | 		it('should return a new instance without `new` keyword', () => {
 13 | 			const sr = ScrollReveal()
 14 | 			expect(sr).to.exist
 15 | 		})
 16 | 
 17 | 		it('should add the class `sr` to `<html>` element', () => {
 18 | 			document.documentElement.classList.remove('sr')
 19 | 			ScrollReveal()
 20 | 			const result = document.documentElement.classList.contains('sr')
 21 | 			expect(result).to.be.true
 22 | 		})
 23 | 
 24 | 		it('should add `height: 100%` to `<body>` element', () => {
 25 | 			document.body.style.height = 'auto'
 26 | 			ScrollReveal()
 27 | 			const result = document.body.style.height === '100%'
 28 | 			expect(result).to.be.true
 29 | 		})
 30 | 
 31 | 		it('should return a noop instance when not supported', () => {
 32 | 			const stubs = [
 33 | 				sinon.stub(console, 'log'),
 34 | 				sinon.stub(ScrollReveal, 'isSupported')
 35 | 			]
 36 | 			const sr = ScrollReveal()
 37 | 			stubs.forEach(stub => stub.restore())
 38 | 			expect(sr.noop).to.be.true
 39 | 		})
 40 | 
 41 | 		it('should return a noop instance when device is disabled', () => {
 42 | 			isMobile()
 43 | 				? expect(ScrollReveal({ mobile: false }).noop).to.be.true
 44 | 				: expect(ScrollReveal({ desktop: false }).noop).to.be.true
 45 | 
 46 | 			ScrollReveal({ desktop: true, mobile: true })
 47 | 		})
 48 | 
 49 | 		it('should return a noop instance when container is invalid', () => {
 50 | 			const stub = sinon.stub(console, 'log')
 51 | 			const sr = ScrollReveal({ container: null })
 52 | 			stub.restore()
 53 | 			expect(sr.noop).to.be.true
 54 | 		})
 55 | 
 56 | 		it('should return a noop instance when passed non-object options', () => {
 57 | 			const stub = sinon.stub(console, 'log')
 58 | 			let sr
 59 | 			{
 60 | 				sr = ScrollReveal(null)
 61 | 				expect(sr.noop).to.be.true
 62 | 				sr = ScrollReveal('foo')
 63 | 				expect(sr.noop).to.be.true
 64 | 			}
 65 | 			stub.restore()
 66 | 		})
 67 | 
 68 | 		it('should return a singleton', () => {
 69 | 			const A = ScrollReveal()
 70 | 			const B = ScrollReveal()
 71 | 			expect(A === B).to.be.true
 72 | 		})
 73 | 
 74 | 		it('should not update the defaults when re-invoked with invalid options', () => {
 75 | 			ScrollReveal({ duration: 1000 })
 76 | 			ScrollReveal(null)
 77 | 			expect(ScrollReveal().defaults.duration).to.equal(1000)
 78 | 		})
 79 | 
 80 | 		it('should update the defaults when re-invoked with valid options', () => {
 81 | 			ScrollReveal({ duration: 1000 })
 82 | 			ScrollReveal({ duration: 5000 })
 83 | 			expect(ScrollReveal().defaults.duration).to.equal(5000)
 84 | 		})
 85 | 
 86 | 		it('should have a static `debug` property', () => {
 87 | 			expect(ScrollReveal.debug).to.exist
 88 | 			expect(ScrollReveal.debug).to.be.a('boolean')
 89 | 		})
 90 | 
 91 | 		it('should accept boolean value for static `debug` property', () => {
 92 | 			ScrollReveal.debug = true
 93 | 			expect(ScrollReveal.debug).to.be.true
 94 | 		})
 95 | 
 96 | 		it('should ignore non-boolean values assigned to static `debug` property', () => {
 97 | 			ScrollReveal.debug = null
 98 | 			expect(ScrollReveal.debug).to.exist
 99 | 			expect(ScrollReveal.debug).to.be.a('boolean')
100 | 		})
101 | 	})
102 | 
103 | 	describe('Instance', () => {
104 | 		const sr = new ScrollReveal()
105 | 
106 | 		it('should have a `clean` method', () => {
107 | 			expect(sr.clean).to.exist
108 | 			expect(sr.clean).to.be.a('function')
109 | 		})
110 | 
111 | 		it('should have a `destroy` method', () => {
112 | 			expect(sr.destroy).to.exist
113 | 			expect(sr.destroy).to.be.a('function')
114 | 		})
115 | 
116 | 		it('should have a `reveal` method', () => {
117 | 			expect(sr.reveal).to.exist
118 | 			expect(sr.reveal).to.be.a('function')
119 | 		})
120 | 
121 | 		it('should have a `sync` method', () => {
122 | 			expect(sr.sync).to.exist
123 | 			expect(sr.sync).to.be.a('function')
124 | 		})
125 | 
126 | 		it('should have a `delegate` property', () => {
127 | 			expect(sr.delegate).to.exist
128 | 			expect(sr.delegate).to.be.a('function')
129 | 		})
130 | 
131 | 		it('should have a `version` property', () => {
132 | 			expect(sr.version).to.exist
133 | 			expect(sr.version).to.be.equal(version)
134 | 		})
135 | 
136 | 		it('should have a `noop` property set to `false`', () => {
137 | 			expect(sr.noop).to.exist
138 | 			expect(sr.noop).to.be.false
139 | 		})
140 | 	})
141 | 
142 | 	describe('Non-operational Instance', () => {
143 | 		const stubs = [
144 | 			sinon.stub(console, 'log'),
145 | 			sinon.stub(ScrollReveal, 'isSupported')
146 | 		]
147 | 		const sr = ScrollReveal()
148 | 		stubs.forEach(stub => stub.restore())
149 | 
150 | 		it('should have a `clean` method', () => {
151 | 			expect(sr.clean).to.exist
152 | 			expect(sr.clean).to.be.a('function')
153 | 		})
154 | 
155 | 		it('should have a `destroy` method', () => {
156 | 			expect(sr.destroy).to.exist
157 | 			expect(sr.destroy).to.be.a('function')
158 | 		})
159 | 
160 | 		it('should have a `reveal` method', () => {
161 | 			expect(sr.reveal).to.exist
162 | 			expect(sr.reveal).to.be.a('function')
163 | 		})
164 | 
165 | 		it('should have a `sync` method', () => {
166 | 			expect(sr.sync).to.exist
167 | 			expect(sr.sync).to.be.a('function')
168 | 		})
169 | 
170 | 		it('should have a `noop` property set to `true`', () => {
171 | 			expect(sr.noop).to.exist
172 | 			expect(sr.noop).to.be.true
173 | 		})
174 | 	})
175 | })
176 | 


--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
 1 | const rollupPlugins = [
 2 | 	require('rollup-plugin-json')(),
 3 | 	require('rollup-plugin-node-resolve')({ jsnext: true, main: true }),
 4 | 	require('rollup-plugin-buble')()
 5 | ]
 6 | 
 7 | if (process.env.COVERAGE) {
 8 | 	rollupPlugins.push(
 9 | 		require('rollup-plugin-istanbul')({
10 | 			exclude: [
11 | 				'../package.json',
12 | 				'../src/index.js',
13 | 				'./**/*.spec.js',
14 | 				'**/node_modules/**'
15 | 			],
16 | 			instrumenterConfig: {
17 | 				embedSource: true
18 | 			}
19 | 		})
20 | 	)
21 | }
22 | 
23 | module.exports = function(karma) {
24 | 	karma.set({
25 | 		frameworks: ['mocha', 'sinon-chai'],
26 | 
27 | 		preprocessors: {
28 | 			'./**/*.spec.js': ['rollup']
29 | 		},
30 | 
31 | 		files: [{ pattern: './**/*.spec.js', watched: false }],
32 | 
33 | 		rollupPreprocessor: {
34 | 			plugins: rollupPlugins,
35 | 			output: {
36 | 				format: 'iife',
37 | 				name: 'ScrollReveal',
38 | 				sourcemap: 'inline'
39 | 			}
40 | 		},
41 | 
42 | 		colors: true,
43 | 		concurrency: 10,
44 | 		logLevel: karma.LOG_ERROR,
45 | 		singleRun: true,
46 | 
47 | 		browserDisconnectTolerance: 1,
48 | 		browserDisconnectTimeout: 60 * 1000,
49 | 		browserNoActivityTimeout: 60 * 1000,
50 | 		// browserNoActivityTimeout: 60 * 1000 * 10 * 6, // dev tools debugging
51 | 		captureTimeout: 4 * 60 * 1000
52 | 	})
53 | 
54 | 	if (process.env.TRAVIS) {
55 | 		if (process.env.COVERAGE) {
56 | 			karma.set({
57 | 				autoWatch: false,
58 | 				browsers: ['ChromeHeadless'],
59 | 				coverageReporter: {
60 | 					type: 'lcovonly',
61 | 					dir: 'coverage/'
62 | 				},
63 | 				reporters: ['mocha', 'coverage', 'coveralls']
64 | 			})
65 | 		} else {
66 | 			const customLaunchers = require('./sauce.conf')
67 | 			karma.set({
68 | 				autoWatch: false,
69 | 				browsers: Object.keys(customLaunchers),
70 | 				customLaunchers,
71 | 				reporters: ['dots', 'saucelabs'],
72 | 				hostname: 'localsauce',
73 | 				sauceLabs: {
74 | 					testName: 'ScrollReveal',
75 | 					build: process.env.TRAVIS_BUILD_NUMBER || 'manual',
76 | 					tunnelIdentifier: process.env.TRAVIS_BUILD_NUMBER || 'autoGeneratedTunnelID',
77 | 					recordVideo: true,
78 | 					connectOptions: {
79 | 						tunnelDomains: 'localsauce' // because Android 8 has an SSL error?
80 | 					}
81 | 				}
82 | 			})
83 | 		}
84 | 	} else {
85 | 		karma.set({
86 | 			browsers: ['ChromeHeadless'],
87 | 			// browsers: ['Chrome'], // dev tools debugging
88 | 			coverageReporter: {
89 | 				type: 'lcov',
90 | 				dir: '../.ignore/coverage/'
91 | 			},
92 | 			reporters: ['mocha', 'coverage']
93 | 		})
94 | 	}
95 | }
96 | 


--------------------------------------------------------------------------------
/test/polyfills/math-sign.spec.js:
--------------------------------------------------------------------------------
 1 | import { polyfill } from '../../src/polyfills/math-sign'
 2 | 
 3 | describe('Polyfills', () => {
 4 | 	describe('mathSign()', () => {
 5 | 		it('should be a function', () => {
 6 | 			expect(polyfill).to.be.a('function')
 7 | 		})
 8 | 
 9 | 		it('should return -1 when passed values smaller than 0', () => {
10 | 			expect(polyfill(-500)).to.equal(-1)
11 | 		})
12 | 
13 | 		it('should return 1 when passed values larger than 0', () => {
14 | 			expect(polyfill(500)).to.equal(1)
15 | 		})
16 | 
17 | 		it('should return 1 when passed true', () => {
18 | 			expect(polyfill(true)).to.equal(1)
19 | 		})
20 | 
21 | 		it('should return -0 when passed -0', () => {
22 | 			expect(polyfill(-0)).to.equal(-0)
23 | 		})
24 | 
25 | 		it('should return 0 when passed 0', () => {
26 | 			expect(polyfill(0)).to.equal(0)
27 | 		})
28 | 
29 | 		it('should return 0 when passed falsey values', () => {
30 | 			expect(polyfill(false)).to.equal(0)
31 | 			expect(polyfill('')).to.equal(0)
32 | 			expect(polyfill([])).to.equal(0)
33 | 			expect(polyfill(null)).to.equal(0)
34 | 		})
35 | 
36 | 		it('should return NaN when passed non-falsey non-numbers', () => {
37 | 			expect(polyfill('foo')).to.be.NaN
38 | 			expect(polyfill({})).to.be.NaN
39 | 			expect(polyfill([1, 2, 3])).to.be.NaN
40 | 		})
41 | 	})
42 | })
43 | 


--------------------------------------------------------------------------------
/test/sauce.conf.js:
--------------------------------------------------------------------------------
 1 | let launchers = {}
 2 | 
 3 | let mobileLaunchers = [
 4 | 	['iOS', '10.3', 'Safari', 'iPhone 7 Simulator', '1.9.1'],
 5 | 	['iOS', '11.3', 'Safari', 'iPhone 7 Simulator', '1.9.1'],
 6 | 	['iOS', '12.2', 'Safari', 'iPhone 7 Simulator', '1.13.0'],
 7 | 	['iOS', '13.0', 'Safari', 'iPhone 7 Simulator', '1.15.0'],
 8 | 	['Android', '5.1', 'Browser', 'Android Emulator', '1.15.0'],
 9 | 	['Android', '6.0', 'Chrome', 'Android Emulator', '1.15.0'],
10 | 	['Android', '8.0', 'Chrome', 'Android Emulator', '1.15.0']
11 | ]
12 | 
13 | for (let [platform, version, browser, device, appium] of mobileLaunchers) {
14 | 	let launcher = `sl_${platform}_${version}_${browser}`
15 | 		.replace(/[^a-z0-9]/gi, '_')
16 | 		.toLowerCase()
17 | 
18 | 	launchers[launcher] = {
19 | 		name: `${browser}, ${platform} ${version}`,
20 | 		platformName: platform,
21 | 		platformVersion: version,
22 | 		browserName: browser,
23 | 		deviceName: device,
24 | 		deviceOrientation: 'portrait',
25 | 		appiumVersion: appium
26 | 	}
27 | }
28 | 
29 | let desktopLaunchers = [
30 | 	['Windows 8.1', 'Internet Explorer', '11.0'],
31 | 	['Windows 8', 'Internet Explorer', '10.0'],
32 | 	['macOS 10.12', 'Safari', '11.0'],
33 | 	['OS X 10.11', 'Safari', '10.0'],
34 | 	['OS X 10.11', 'Safari', '9.0']
35 | ]
36 | 
37 | for (let [platform, browser, version] of desktopLaunchers) {
38 | 	let launcher = `sl_${platform}_${browser}_${version}`
39 | 		.replace(/[^a-z0-9]/gi, '_')
40 | 		.toLowerCase()
41 | 
42 | 	launchers[launcher] = {
43 | 		name: `${browser} ${version}, ${platform}`,
44 | 		browserName: browser,
45 | 		version,
46 | 		platform
47 | 	}
48 | }
49 | 
50 | for (let browser of ['Chrome', 'Firefox', 'MicrosoftEdge']) {
51 | 	let pastVersions = 3
52 | 	do {
53 | 		pastVersions--
54 | 		let postfix = pastVersions > 0 ? `-${pastVersions}` : ''
55 | 		let version = 'latest' + postfix
56 | 
57 | 		let browserName = browser === 'MicrosoftEdge' ? 'Edge' : browser
58 | 		let launcher = `sl_win10_${browser}_latest${postfix}`.replace(/-/g, '_').toLowerCase()
59 | 
60 | 		launchers[launcher] = {
61 | 			name: `${browserName} ${version}, Windows 10`,
62 | 			browserName: browser,
63 | 			version,
64 | 			platform: 'Windows 10'
65 | 		}
66 | 	} while (pastVersions)
67 | }
68 | 
69 | for (let launcher in launchers) {
70 | 	launchers[launcher].base = 'SauceLabs'
71 | }
72 | 
73 | module.exports = launchers
74 | 


--------------------------------------------------------------------------------
/test/timeout.spec.js:
--------------------------------------------------------------------------------
1 | // describe('suite delay for DOM inspection', function () {
2 | // 	it('should delay by 10 minutes', function (done) {
3 | // 		document.documentElement.style = 'background-color: #eee; height: 100%'
4 | // 		const time = 1000 * 60 * 10 * 6
5 | // 		this.timeout(time)
6 | // 		setTimeout(done, time - 500)
7 | // 	})
8 | // })
9 | 


--------------------------------------------------------------------------------
/test/utils/deep-assign.spec.js:
--------------------------------------------------------------------------------
 1 | import deepAssign from '../../src/utils/deep-assign'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('deepAssign()', () => {
 5 | 		it('should assign source values to target object', () => {
 6 | 			const target = { foo: 'bar', bun: 'baz' }
 7 | 			const source = { foo: 'bonk!', bif: 'baff' }
 8 | 			const goal = { foo: 'bonk!', bun: 'baz', bif: 'baff' }
 9 | 			deepAssign(target, source)
10 | 			expect(target).to.deep.equal(goal)
11 | 		})
12 | 
13 | 		it('should assign nested source values to target object', () => {
14 | 			// each property tests a
15 | 			// different execution path
16 | 			const target = {
17 | 				foo: 'initial',
18 | 				bar: 'initial',
19 | 				kel: { pow: 'pop' },
20 | 				zad: null
21 | 			}
22 | 			const source = {
23 | 				foo: 'bonk!',
24 | 				bar: { baz: 'baff' },
25 | 				kel: { pow: 'lol' },
26 | 				zad: { min: 'max' }
27 | 			}
28 | 			const goal = {
29 | 				foo: 'bonk!',
30 | 				bar: { baz: 'baff' },
31 | 				kel: { pow: 'lol' },
32 | 				zad: { min: 'max' }
33 | 			}
34 | 			deepAssign(target, source)
35 | 			expect(target).to.deep.equal(goal)
36 | 		})
37 | 
38 | 		it('should accept multiple sources', () => {
39 | 			const target = { foo: 'bar', bun: 'baz' }
40 | 			const source1 = { foo: 'bonk!', bif: 'baff' }
41 | 			const source2 = { foo: 'pow!' }
42 | 			const goal = { foo: 'pow!', bun: 'baz', bif: 'baff' }
43 | 			deepAssign(target, source1, source2)
44 | 			expect(target).to.deep.equal(goal)
45 | 		})
46 | 
47 | 		it('should throw a type error when not passed an object literal', () => {
48 | 			let caught
49 | 			try {
50 | 				deepAssign(null, null)
51 | 			} catch (error) {
52 | 				caught = error
53 | 			}
54 | 			expect(caught).to.exist
55 | 			expect(caught).to.be.an.instanceof(TypeError)
56 | 		})
57 | 	})
58 | })
59 | 


--------------------------------------------------------------------------------
/test/utils/each.spec.js:
--------------------------------------------------------------------------------
 1 | import each from '../../src/utils/each'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('each()', () => {
 5 | 		function Fixture() {
 6 | 			this.foo = 'bar'
 7 | 			this.baz = 'bun'
 8 | 		}
 9 | 
10 | 		describe('if passed an object literal...', () => {
11 | 			it('should invoke callback for each property', () => {
12 | 				const fixture = new Fixture()
13 | 				const spy = sinon.spy()
14 | 				each(fixture, spy)
15 | 				expect(spy).to.have.been.calledTwice
16 | 			})
17 | 
18 | 			it('should ignore properties on the prototype chain', () => {
19 | 				Fixture.prototype.biff = 'baff'
20 | 				const fixture = new Fixture()
21 | 				const spy = sinon.spy()
22 | 				each(fixture, spy)
23 | 				expect(spy).to.have.been.calledTwice
24 | 			})
25 | 
26 | 			it('should pass the value, key and collection to the callback', () => {
27 | 				const fixture = new Fixture()
28 | 				let _value, _key, _collection
29 | 				each(fixture, (value, key, collection) => {
30 | 					_value = value
31 | 					_key = key
32 | 					_collection = collection
33 | 				})
34 | 				expect(_value).to.equal('bun')
35 | 				expect(_key).to.equal('baz')
36 | 				expect(_collection).to.deep.equal(fixture)
37 | 			})
38 | 		})
39 | 
40 | 		describe('if passed an array...', () => {
41 | 			const fixture = ['apple', 'orange', 'banana']
42 | 
43 | 			it('should invoke callback for each value', () => {
44 | 				const spy = sinon.spy()
45 | 				each(fixture, spy)
46 | 				expect(spy).to.have.been.calledThrice
47 | 			})
48 | 
49 | 			it('should pass the value, index and collection to the callback', () => {
50 | 				let _value, _index, _collection
51 | 				each(fixture, (value, index, collection) => {
52 | 					_value = value
53 | 					_index = index
54 | 					_collection = collection
55 | 				})
56 | 				expect(_value).to.equal('banana')
57 | 				expect(_index).to.equal(2)
58 | 				expect(_collection).to.deep.equal(fixture)
59 | 			})
60 | 		})
61 | 
62 | 		describe('else', () => {
63 | 			it('should throw a type error when passed an invalid collection', () => {
64 | 				let caught
65 | 				try {
66 | 					each(null, () => {})
67 | 				} catch (error) {
68 | 					caught = error
69 | 				}
70 | 				expect(caught).to.exist
71 | 				expect(caught).to.be.an.instanceof(TypeError)
72 | 			})
73 | 		})
74 | 	})
75 | })
76 | 


--------------------------------------------------------------------------------
/test/utils/get-prefixed-css-prop.spec.js:
--------------------------------------------------------------------------------
 1 | import getPrefixedCssProp from '../../src/utils/get-prefixed-css-prop'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('getPrefixedCssProp()', () => {
 5 | 		beforeEach('clear cache', () => {
 6 | 			getPrefixedCssProp.clearCache()
 7 | 		})
 8 | 
 9 | 		it('should return unprefixed properties before prefixed', () => {
10 | 			const source = {
11 | 				transform: '',
12 | 				'-webkit-transform': ''
13 | 			}
14 | 			const result = getPrefixedCssProp('transform', source)
15 | 			expect(result).to.equal('transform')
16 | 		})
17 | 
18 | 		it('should return prefixed property names', () => {
19 | 			const source = { '-webkit-transform': '' }
20 | 			const result = getPrefixedCssProp('transform', source)
21 | 			expect(result).to.equal('-webkit-transform')
22 | 		})
23 | 
24 | 		it('should return property names from cache when available', () => {
25 | 			const source = { '-webkit-transform': '' }
26 | 			getPrefixedCssProp('transform', source)
27 | 			const result = getPrefixedCssProp('transform', {})
28 | 			expect(result).to.equal('-webkit-transform')
29 | 		})
30 | 
31 | 		it('should throw a range error when no property is found', () => {
32 | 			let caught
33 | 			try {
34 | 				getPrefixedCssProp('transform', {})
35 | 			} catch (error) {
36 | 				caught = error
37 | 			}
38 | 			expect(caught).to.exist
39 | 			expect(caught).to.be.an.instanceof(RangeError)
40 | 		})
41 | 
42 | 		it('should throw a type error if not passed a string', () => {
43 | 			let caught
44 | 			try {
45 | 				getPrefixedCssProp(null)
46 | 			} catch (error) {
47 | 				caught = error
48 | 			}
49 | 			expect(caught).to.exist
50 | 			expect(caught).to.be.an.instanceof(TypeError)
51 | 		})
52 | 	})
53 | })
54 | 


--------------------------------------------------------------------------------
/test/utils/is-mobile.spec.js:
--------------------------------------------------------------------------------
 1 | import isMobile from '../../src/utils/is-mobile'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('isMobile()', () => {
 5 | 		it('should return true when passed a mobile user agent', () => {
 6 | 			const android = `Mozilla/5.0 (Linux; U; Android 4.2; en-us;
 7 | 				Android SDK built for x86 Build/JOP40C) AppleWebKit/534.30
 8 | 				(KHTML, like Gecko) Version/4.0 Mobile Safari/534.30`
 9 | 
10 | 			const iPhone = `Mozilla/5.0 (iPhone; CPU iPhone OS 10_10_5 like Mac OS X)
11 | 				AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B411 Safari/600.1.4`
12 | 
13 | 			expect(isMobile(android)).to.be.true
14 | 			expect(isMobile(iPhone)).to.be.true
15 | 		})
16 | 
17 | 		it('should return false when passed a desktop user agent', () => {
18 | 			const chrome = `Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
19 | 				(KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36`
20 | 
21 | 			const firefox =
22 | 				'Mozilla/5.0 (X11; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0'
23 | 
24 | 			const ie10 = `Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1;
25 | 				WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E)`
26 | 
27 | 			expect(isMobile(chrome)).to.be.false
28 | 			expect(isMobile(firefox)).to.be.false
29 | 			expect(isMobile(ie10)).to.be.false
30 | 		})
31 | 
32 | 		it('should work when not passed an explicit user agent', () => {
33 | 			expect(isMobile()).to.be.a('boolean')
34 | 		})
35 | 	})
36 | })
37 | 


--------------------------------------------------------------------------------
/test/utils/is-object.spec.js:
--------------------------------------------------------------------------------
 1 | import isObject from '../../src/utils/is-object'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('isObject()', () => {
 5 | 		it('should return true when passed an object literal', () => {
 6 | 			const result = isObject({})
 7 | 			expect(result).to.be.true
 8 | 		})
 9 | 
10 | 		it('should return false when passed a function', () => {
11 | 			const result = isObject(() => {})
12 | 			expect(result).to.be.false
13 | 		})
14 | 
15 | 		it('should return false when passed an array', () => {
16 | 			const result = isObject([])
17 | 			expect(result).to.be.false
18 | 		})
19 | 
20 | 		it('should return false when passed null', () => {
21 | 			const result = isObject(null)
22 | 			expect(result).to.be.false
23 | 		})
24 | 
25 | 		it('should return false when passed undefined', () => {
26 | 			const result = isObject(undefined)
27 | 			expect(result).to.be.false
28 | 		})
29 | 	})
30 | })
31 | 


--------------------------------------------------------------------------------
/test/utils/is-transform-supported.spec.js:
--------------------------------------------------------------------------------
 1 | import isTransformSupported from '../../src/utils/is-transform-supported'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('isTransformSupported()', () => {
 5 | 		it('should return true', () => {
 6 | 			expect(isTransformSupported()).to.be.true
 7 | 		})
 8 | 	})
 9 | })
10 | 


--------------------------------------------------------------------------------
/test/utils/is-transition-supported.spec.js:
--------------------------------------------------------------------------------
 1 | import isTransitionSupported from '../../src/utils/is-transition-supported'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('isTransitionSupported()', () => {
 5 | 		it('should return true', () => {
 6 | 			expect(isTransitionSupported()).to.be.true
 7 | 		})
 8 | 	})
 9 | })
10 | 


--------------------------------------------------------------------------------
/test/utils/logger.spec.js:
--------------------------------------------------------------------------------
 1 | import logger from '../../src/utils/logger'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('logger()', () => {
 5 | 		const mock = { constructor: { debug: true } }
 6 | 
 7 | 		let spy
 8 | 		let stub
 9 | 
10 | 		beforeEach('stub console log', () => {
11 | 			spy = sinon.spy()
12 | 			stub = sinon.stub(console, 'log').callsFake(spy)
13 | 		})
14 | 
15 | 		it('should invoke console.log', () => {
16 | 			logger.call(mock)
17 | 			expect(spy).to.have.been.called
18 | 		})
19 | 
20 | 		it('should prepend output with `ScrollReveal: `', () => {
21 | 			logger.call(mock, 'test')
22 | 			const result = '%cScrollReveal: test'
23 | 			const style = 'color: #ea654b;'
24 | 			expect(spy).to.have.been.calledWith(result, style)
25 | 		})
26 | 
27 | 		it('should accept multiple arguments as message details', () => {
28 | 			logger.call(mock, 'message', 'detail one', 'detail two')
29 | 			const result = '%cScrollReveal: message\n — detail one\n — detail two'
30 | 			const style = 'color: #ea654b;'
31 | 			expect(spy).to.have.been.calledWith(result, style)
32 | 		})
33 | 
34 | 		afterEach('restore console log', () => stub.restore())
35 | 	})
36 | })
37 | 


--------------------------------------------------------------------------------
/test/utils/next-unique-id.spec.js:
--------------------------------------------------------------------------------
 1 | import nextUniqueId from '../../src/utils/next-unique-id'
 2 | 
 3 | describe('Utilities', () => {
 4 | 	describe('nextUniqueId()', () => {
 5 | 		it('should start at 0', () => {
 6 | 			const result = nextUniqueId()
 7 | 			expect(result).to.equal(0)
 8 | 		})
 9 | 
10 | 		it('should increment by 1', () => {
11 | 			const result = nextUniqueId()
12 | 			expect(result).to.equal(1)
13 | 		})
14 | 
15 | 		it('should return a number', () => {
16 | 			const result = nextUniqueId()
17 | 			expect(result).to.be.a('number')
18 | 		})
19 | 	})
20 | })
21 | 


--------------------------------------------------------------------------------