Repository: jlmakes/scrollreveal Branch: master Commit: b5f4e01a6558 Files: 54 Total size: 152.0 KB Directory structure: gitextract_vf2236hi/ ├── .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 ├── dist/ │ ├── scrollreveal.es.js │ └── scrollreveal.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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "es6": true, "amd": true, "browser": true, "mocha": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { "sourceType": "module" }, "globals": { "describe": true, "it": true, "expect": true, "sinon": true }, "rules": { "no-cond-assign": 2, "no-console": 1, "no-const-assign": 2, "no-class-assign": 2, "no-this-before-super": 2, "no-unused-vars": 1, "no-var": 2, "object-shorthand": [2, "always"] } } ================================================ FILE: .gitattributes ================================================ *.js eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Environment * Operating System: * Browser Version: * ScrollReveal Version: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .ignore/ .vscode/ node_modules/ yarn.lock package-lock.json ================================================ FILE: .travis.yml ================================================ language: node_js dist: trusty node_js: - '9' addons: chrome: stable hosts: localsauce sudo: required before_script: - 'sudo chown root /opt/google/chrome/chrome-sandbox' - 'sudo chmod 4755 /opt/google/chrome/chrome-sandbox' after_success: - npm run coverage ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## [4.0.9] - 2021-03-04 ### Fixed - Styles applied using CSSOM don't drop `:` characters. ## [4.0.8] - 2021-03-02 ### Fixed - Avoid Content Security Policy (CSP) violations. [@lambdacasserole](https://github.com/lambdacasserole) [#431](https://github.com/jlmakes/scrollreveal/pull/431) ## [4.0.7] - 2020-07-15 ### Fixed - Ensure element geometry exists. [#437](https://github.com/jlmakes/scrollreveal/issues/437) ## [4.0.6] - 2020-03-15 ### Fixed - Default transition values of `none` are now correctly ignored. [#231](https://github.com/jlmakes/scrollreveal/issues/231) ### Fixed ## [4.0.5] - 2018-10-20 ### Fixed - Calling `reveal()` on the same `target` breaking animation. [#468](https://github.com/jlmakes/scrollreveal/issues/468) ## [4.0.4] - 2018-09-22 ### Fixed - Malformed `package.json` ## [4.0.3] - 2018-09-21 ### Fixed – `options.cleanup` is now correctly set to `false` by default. [#457](https://github.com/jlmakes/scrollreveal/issues/457) ## [4.0.2] - 2018-09-11 ### Fixed - Null property assignment regression in mount function. [#456](https://github.com/jlmakes/scrollreveal/issues/456) ## [4.0.1] - 2018-09-09 ### Fixed - Noop instances were not correctly unmounting from the DOM. [#455](https://github.com/jlmakes/scrollreveal/issues/455) - Readme links to pricing page no longer 404. ## [4.0.0] - 2018-08-06 ### Added - ScrollReveal can be enabled/disabled on desktops using `options.desktop`. - The class `sr` is added to `` during instantiation when supported. [#294](https://github.com/jlmakes/scrollreveal/issues/294) - `height: 100%` is added to `` during instantiation when supported. [#298](https://github.com/jlmakes/scrollreveal/issues/298) - Unused containers are removed from the store, and their event listeners destroyed. - ScrollReveal skips generating opacity styles when `options.opacity` is set to `null`. - ScrollReveal retains element CSS transformations. [#251](https://github.comjlmakes/scrollreveal/issues/251) - New `options.cleanup` toggles whether generated styles are removed upon reveal completion (when `options.reset` is `false`). [#292](https://github.comjlmakes/scrollreveal/issues/292) - ScrollReveal tracks scroll direction as container store data. [#384](https://github.com/jlmakes/scrollreveal/issues/384) - New `clean()` method removes specific generated styles and event listeners. [#227](https://github.com/jlmakes/scrollreveal/issues/227) - New `destroy()` method removes all generated styles and event listeners. [#227](https://github.com/jlmakes/scrollreveal/issues/227) - New `debug` static property toggles error messages in console. [#351](https://github.com/jlmakes/scrollreveal/issues/351) - Instance methods now accept native arrays of HTML elements. ### Changed - **Breaking:** The `reveal()` method no longer accepts an `interval` parameter. Instead, sequence intervals are now defined with `options.interval`. - **Breaking:** The instance method `isSupported()` is now static. - **Breaking:** `options.distance` supports only `em` `px` and `%` values. - **Breaking:** ScrollReveal methods are no longer chainable. - **Breaking:** ScrollReveal requires a commercial license, unless for [GPL-3.0](https://opensource.org/licenses/GPL-3.0) compatible open source projects. - Elements in a reveal sequence are no longer grouped, and reveal progressively when visible. - ScrollReveal uses a single `matrix3d()` property, with the correct prefix and only when necessary. [#292](https://github.com/jlmakes/scrollreveal/issues/292) - ScrollReveal returns a non-operational instance when instantiated in unsupported browsers. - ScrollReveal `version` is now a read-only instance property. - ScrollReveal methods are now bound read-only instance properties. - `options.viewFactor` clamps values outside of `0.0` to `1.0`. - ScrollReveal constructor now returns a singleton. ### Fixed - The `requestAnimationFrame` polyfill now reliably throttles callback invocations. ## [3.3.6] - 2017-06-23 ### Fixed - Element visibility now checks left and right boundaries correctly. [#352](https://github.com/jlmakes/scrollreveal/issues/352) - Library version instance property is again accurate. ## [3.3.5] - 2017-04-05 ### Fixed - Patched to ensure version 3 is the default NPM package. ## [3.3.4] - 2017-02-18 ### Fixed - Update stale CDN link in README. ### Changed - Add deprecation warnings to README. ## [3.3.3] - 2017-02-18 ### Fixed - Fix error when using Bower and Wordpress due to missing semi-colon. [#278](https://github.com/jlmakes/scrollreveal/issues/278) ## [3.3.2] - 2016-10-02 ### Changed - Updated Starting Defaults section in README. [#273](https://github.comjlmakes/scrollreveal/issues/273) ### Fixed - Using a selector to define a default container during instantiation now works. [#289](https://github.com/jlmakes/scrollreveal/issues/289) ## [3.3.1] - 2016-07-22 ### Fixed - Instance variable `version` updated with correct library version. ## [3.3.0] - 2016-07-22 ### Added - New callback `beforeReveal(el)`. [#273](https://github.comjlmakes/scrollreveal/issues/273) - New callback `beforeReset(el)`. [#273](https://github.com/jlmakes/scrollreveal/issues/273) ## [3.2.0] - 2016-07-08 ### Added - New `isNodeList()` method added to `Tools`. - New `version` instance variable contains library version. - HTML Collections are now supported as the first argument in `reveal()`. [#246](https://github.com/jlmakes/scrollreveal/issues/246) - Added fallback for `requestAnimationFrame`. [#267](https://github.comjlmakes/scrollreveal/issues/267) ### Changed - Updated Starting Defaults section in README. ### Fixed - 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) - Refactored AMD/CommonJS module wrapper to work with Codekit. [#253](https://github.com/jlmakes/scrollreveal/issues/253) ## [3.1.5] - 2016-07-06 ### Fixed - `sync()` method now properly supports sequences. ## [3.1.4] - 2016-03-28 ### Changed - Added `console.log` calls back to non-minified distribution. [#235](https://github.com/jlmakes/scrollreveal/issues/235) ## [3.1.3] - 2016-03-28 ### Removed - Removed `console.log` calls from distribution. [#235](https://github.comjlmakes/scrollreveal/issues/235) ## [3.1.2] - 2016-03-23 ### Fixed - Removed stray quotation mark in `reveal()` error message. ## [3.1.1] - 2016-03-08 ### Fixed - `config.reset` now works properly with sequences. [#241](https://github.comjlmakes/scrollreveal/issues/241) ## [3.1.0] - 2016-03-07 ### Added - New `isNode()` method added to `Tools`. - HTML elements are now supported as the first argument in `reveal()`. - Selector strings assigned to `config.container` are now supported. - `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) - New section on sequenced animations added to README. ### Changed - Messages logged to console are now prepended with `ScrollReveal:` for clarity. - Revised and renamed `supported()` method to `isSupported()`. - Updated Custom Containers section in README with an example using a selector. - Updated Tips section in README. ### Fixed - Added semi-colon before global IIFE to improve reliability. [#228](https://github.com/jlmakes/scrollreveal/issues/228) - The existence of `console.log` is now confirmed for IE9. [#230](https://github.com/jlmakes/scrollreveal/issues/230) - Typos, indentation and semicolons corrected in README. ## [3.0.9] - 2016-01-14 ### Changed - Updated example site links in the README. ### Fixed - Fixed operator mismatch inside `supported()`. [#220](https://github.comjlmakes/scrollreveal/issues/220) ## [3.0.8] - 2016-01-13 ### Changed - Public methods now verify that ScrollReveal is supported. ### Fixed - Updated Tips section in README. ## [3.0.7] - 2016-01-13 ### Added - Added brower support information to README. [#219](https://github.comjlmakes/scrollreveal/issues/219) ### Changed - `console.log` is now used instead of `console.warn`. [#215](https://github.com/jlmakes/scrollreveal/issues/215) - Moved `tools.isSupported` method to `ScrollReveal.prototype.supported`. - Updated the configuration and tips documentation in the README. ### Removed - The `init()` method was removed. ### Fixed - Using `config.mobile` in `reveal()` now works. [#216](https://github.comjlmakes/scrollreveal/issues/216) ## [3.0.6] - 2016-01-02 ### Fixed - Custom default containers are now used. - Critical issues affecting Chrome on iOS were (finally) solved. [#196](https://github.com/jlmakes/scrollreveal/issues/196) - Revisited `3.0.4` changes to chaining `reveal()` calls. [#212](https://github.com/jlmakes/scrollreveal/issues/212) ## [3.0.5] - 2015-12-30 ### Fixed - Fixed compatibility issues with Webpack. [#209](https://github.comjlmakes/scrollreveal/issues/209) ## [3.0.4] - 2015-12-30 ### Fixed - Squashed Webkit browser bugs due to syntax errors. [#208](https://github.comjlmakes/scrollreveal/issues/208) - Chaining `reveal()` calls no longer prematurely initialize animation. - Cleaned up README typos, and stale reference to `config.wait`. ## [3.0.3] - 2015-12-22 ### Changed - `reveal()` and `sync()` now return the ScrollReveal instance even on failure. [#198](https://github.com/jlmakes/scrollreveal/issues/198) ## [3.0.2] - 2015-12-22 ### Added - Added `bower.json` to release package. [#199](https://github.comjlmakes/scrollreveal/issues/199) ### Fixed - Preexisting CSS transition styles are no longer destroyed. [#197](https://github.com/jlmakes/scrollreveal/issues/197) ## [3.0.1] - 2015-12-21 ### Changed - Updated Getting Started section in the README. ### Fixed - Hard learned NPM and Bower issues related to release management were endured. - 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) ## [3.0.0] - 2015-12-15 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. ### Added - New method `reveal()`. [#1](https://github.com/jlmakes/scrollreveal/issues/1) [#122](https://github.com/jlmakes/scrollreveal/issues/122) - New method `sync()`. - New callback `config.afterReset`. - Horizontal scrolling is now supported. [#184](https://github.comjlmakes/scrollreveal/issues/184) ### Changed - **Breaking:** `config.enter` renamed `config.origin`. - **Breaking:** `config.wait` renamed `config.delay`. - **Breaking:** `config.delay` renamed `config.useDelay`. - **Breaking:** `config.over` renamed `config.duration`. - **Breaking:** `config.move` renamed `config.distance`. - **Breaking:** `config.viewport` renamed `config.container`. - **Breaking:** `config.vFactor` renamed `config.viewFactor`. - **Breaking:** `config.complete` renamed `config.afterReveal`. - **Breaking:** Time values are now expected in milliseconds (instead of `string`). - **Breaking:** `config.scale` expects value type `number` (instead of `object`). - **Breaking:** `config.rotation` axis values require `string` with unit type (instead of `number`). - **Breaking:** ScrollReveal constructor is now capitalized. - Reveals now resolve to element's computed opacity, instead of `1`. [#185](https://github.com/jlmakes/scrollreveal/issues/185) ### Removed - ScrollReveal no longer recognizes `data-sr` attributes. ### Fixed - Improved reliability of callback timers. ## [2.3.2] - 2015-06-15 ### Changed - Updated `bower.json` syntax. [#150](https://github.com/jlmakes/scrollreveal/issues/150) ## [2.3.1] - 2015-06-04 ### Added - Simple instantiation (without `new` keyword) is now supported. [#148](https://github.com/jlmakes/scrollreveal/issues/148) ## [2.3.0] - 2015-04-25 ### Added - New keyword `vFactor` and alias `vF` control when an element is considered visible. - New keyword `opacity` controls starting opacity. ### Removed - The easing keyword `hustle` was removed. ## [2.2.0] - 2015-03-18 ### Added - New keyword `spin` controls yaw. - New keyword `roll` controls roll. - New keyword `flip` controls pitch. ### Changed - Improved Basic Usage examples in README. ## [2.1.0] - 2014-11-25 ### Added - Various tablets added to mobile device detection. [#32](https://github.comjlmakes/scrollreveal/issues/32) [#81](https://github.com/jlmakes/scrollreveal/issues/81) - CSS Transition support is now confirmed during instantiation. [#109](https://github.com/jlmakes/scrollreveal/issues/109) ## [2.0.5] - 2014-11-23 ### Changed - Reverted `2.0.4` change to element animation logic. [#108](https://github.comjlmakes/scrollreveal/issues/108) ## [2.0.4] - 2014-11-21 ### Changed - Revised how element animations are handled. - Reverted `2.0.3` change to element visibility logic. [#106](https://github.com/jlmakes/scrollreveal/issues/106) ## [2.0.3] - 2014-11-14 ### Added - `data-sr` attributes are now stripped from initialized elements. [#100](https://github.com/jlmakes/scrollreveal/issues/100) @orapouso. - Live Reload added to development environment. ### Changed - Revised how element visibility is determined. ### Removed - Multiple instances sharing the same viewport element no longer throw an error. [#98](https://github.com/jlmakes/scrollreveal/issues/98) @orapouso. ### Fixed - Incomplete support for `config.delay = "onload"` was addressed. - Issues related to `setTimeout`, `config.complete` and incorrect animation timing were addressed. [#96](https://github.com/jlmakes/scrollreveal/issues/96) ## [2.0.2] - 2014-10-23 ### Added - An error is now thrown when multiple instances share the same viewport element. [#91](https://github.com/jlmakes/scrollreveal/issues/91) ### Fixed - Updated NPM and Bower references with new distribution path. ## [2.0.1] - 2014-10-18 ### Fixed - Incomplete support for `config.viewport` was addressed. [#67](https://github.com/jlmakes/scrollreveal/issues/67) [#68](https://github.comjlmakes/scrollreveal/issues/68) ## [2.0.0] - 2014-10-17 ### Added - New keyword `scale` controls element starting size. - New option `config.complete` defines a callback for when reveals finish. - New option `config.viewport` defines custom viewports. - New option `config.mobile` enables/disables ScrollReveal on mobile devices. - New option `config.delay` controls when animations are delayed. ### Changed - **BREAKING:** ScrollReveal now uses the `data-sr` instead of `data-scroll-reveal`. - Repository now follows [Semantic Versioning](http://semver.org/). ### Removed - The `after` keyword was removed. ## 0.1.3 - 2014-05-26 [YANKED] ### Added - Configuration now includes starting opacity. [#33](https://github.comjlmakes/scrollreveal/issues/33) @kierzniak - New `data-scroll-reveal-id` attribute added to revealed DOM elements. ### Changed - Scroll event handling now uses `requestAnimationFrame`. [#48](https://github.com/jlmakes/scrollreveal/issues/48) @pazguille - 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 ## 0.1.2 - 2014-03-13 [YANKED] ### Added - Elements with `position: fixed` are now supported. [#35](https://github.comjlmakes/scrollreveal/issues/35) ### Fixed - Generated styles are now more specific. [#37](https://github.comjlmakes/scrollreveal/issues/37) ## 0.1.1 - 2014-03-06 [YANKED] ### Fixed - 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 ## 0.1.0 - 2014-03-05 [YANKED] ### Added - Distribution now supports AMD/CommonJS. - Repository now uses Gulp. - Boilerplate Testline suite added to repository. ### Changed - **BREAKING:** ScrollReveal now uses the `data-scroll-reveal` attribute to parse animation instructions, in place of `data-scrollReveal`. ## 0.0.4 - 2014-02-28 [YANKED] ### Fixed - ScrollReveal no longer destroys the existing style attribute on revealed elements, but instead, now appends the necessary animation styles to existing inline styles. ## 0.0.3 - 2014-02-22 [YANKED] ### Fixed - Removed unused CSS Transition/Transform prefixes for Mozilla and Opera. ## 0.0.2 - 2014-02-13 [YANKED] ### Added - Constructor now accepts a configuration object to customize defaults. - New `reset` keyword allows elements to reveal each time they enter the viewport. - The `move` keyword can now be replaced with with CSS easing keywords (e.g. `ease-in-out`). - Library documentation and code examples added to README. ### Changed - ScrollReveal is no longer automatically instantiated by the `DOMContentLoaded` event. ## 0.0.1 - 2014-01-22 [YANKED] ### Hello World [4.0.9]: https://github.com/jlmakes/scrollreveal/compare/v4.0.8...v4.0.9 [4.0.8]: https://github.com/jlmakes/scrollreveal/compare/v4.0.7...v4.0.8 [4.0.7]: https://github.com/jlmakes/scrollreveal/compare/v4.0.6...v4.0.7 [4.0.6]: https://github.com/jlmakes/scrollreveal/compare/v4.0.5...v4.0.6 [4.0.5]: https://github.com/jlmakes/scrollreveal/compare/v4.0.4...v4.0.5 [4.0.4]: https://github.com/jlmakes/scrollreveal/compare/v4.0.3...v4.0.4 [4.0.3]: https://github.com/jlmakes/scrollreveal/compare/v4.0.2...v4.0.3 [4.0.2]: https://github.com/jlmakes/scrollreveal/compare/v4.0.1...v4.0.2 [4.0.1]: https://github.com/jlmakes/scrollreveal/compare/v4.0.0...v4.0.1 [4.0.0]: https://github.com/jlmakes/scrollreveal/compare/v3.3.6...v4.0.0 [3.3.6]: https://github.com/jlmakes/scrollreveal/compare/v3.3.5...v3.3.6 [3.3.5]: https://github.com/jlmakes/scrollreveal/compare/v3.3.4...v3.3.5 [3.3.4]: https://github.com/jlmakes/scrollreveal/compare/v3.3.3...v3.3.4 [3.3.3]: https://github.com/jlmakes/scrollreveal/compare/v3.2.2...v3.3.3 [3.3.2]: https://github.com/jlmakes/scrollreveal/compare/v3.3.1...v3.3.2 [3.3.1]: https://github.com/jlmakes/scrollreveal/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/jlmakes/scrollreveal/compare/v3.2.0...v3.3.0 [3.2.0]: https://github.com/jlmakes/scrollreveal/compare/v3.1.5...v3.2.0 [3.1.5]: https://github.com/jlmakes/scrollreveal/compare/v3.1.4...v3.1.5 [3.1.4]: https://github.com/jlmakes/scrollreveal/compare/v3.1.3...v3.1.4 [3.1.3]: https://github.com/jlmakes/scrollreveal/compare/v3.1.2...v3.1.3 [3.1.2]: https://github.com/jlmakes/scrollreveal/compare/v3.1.1...v3.1.2 [3.1.1]: https://github.com/jlmakes/scrollreveal/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/jlmakes/scrollreveal/compare/v3.0.9...v3.1.0 [3.0.9]: https://github.com/jlmakes/scrollreveal/compare/v3.0.8...v3.0.9 [3.0.8]: https://github.com/jlmakes/scrollreveal/compare/v3.0.7...v3.0.8 [3.0.7]: https://github.com/jlmakes/scrollreveal/compare/v3.0.6...v3.0.7 [3.0.6]: https://github.com/jlmakes/scrollreveal/compare/v3.0.5...v3.0.6 [3.0.5]: https://github.com/jlmakes/scrollreveal/compare/v3.0.4...v3.0.5 [3.0.4]: https://github.com/jlmakes/scrollreveal/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/jlmakes/scrollreveal/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/jlmakes/scrollreveal/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/jlmakes/scrollreveal/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/jlmakes/scrollreveal/compare/v2.3.2...v3.0.0 [2.3.2]: https://github.com/jlmakes/scrollreveal/compare/v2.3.1...v2.3.2 [2.3.1]: https://github.com/jlmakes/scrollreveal/compare/v2.3.0...v2.3.1 [2.3.0]: https://github.com/jlmakes/scrollreveal/compare/v2.2.0...v2.3.0 [2.2.0]: https://github.com/jlmakes/scrollreveal/compare/v2.1.0...v2.2.0 [2.1.0]: https://github.com/jlmakes/scrollreveal/compare/v2.0.5...v2.1.0 [2.0.5]: https://github.com/jlmakes/scrollreveal/compare/v2.0.4...v2.0.5 [2.0.4]: https://github.com/jlmakes/scrollreveal/compare/v2.0.3...v2.0.4 [2.0.3]: https://github.com/jlmakes/scrollreveal/compare/v2.0.2...v2.0.3 [2.0.2]: https://github.com/jlmakes/scrollreveal/compare/v2.0.1...v2.0.2 [2.0.1]: https://github.com/jlmakes/scrollreveal/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/jlmakes/scrollreveal/tree/v2.0.0 ================================================ FILE: README.md ================================================

ScrollReveal


ScrollReveal

Animate elements as they scroll into view.

Build status Monthly downloads Version 5.7 kB min+gzip GPLv3 License


# Installation ## Browser A simple and fast way to get started is to include this script on your page: ```html ``` This will create the global variable `ScrollReveal` > 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) ## Module ```bash $ npm install scrollreveal ``` #### CommonJS ```js const ScrollReveal = require('scrollreveal') ``` #### ES2015 ```js import ScrollReveal from 'scrollreveal' ```
# Usage 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. > 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. 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: ```html

Widget Inc.

``` ```js ScrollReveal().reveal('.headline') ``` **🔎 See this demo live on [JSBin](http://jsbin.com/jufohaxonu/edit?html,output)**
--- ### The full documentation can be found at [https://scrollrevealjs.org](https://scrollrevealjs.org) > If you’re using an older version of ScrollReveal, you can find legacy documentation in the [wiki](https://github.com/jlmakes/scrollreveal/wiki) ---
Commercial License Badge
# License **For commercial sites, themes, projects, and applications, keep your source code private/proprietary by purchasing a [Commercial License](https://scrollrevealjs.org/pricing/).** Licensed under the GNU General Public License 3.0 for compatible open source projects and non-commercial use.
Copyright 2023 Fisssion LLC ================================================ FILE: build/rollup.conf.banner.js ================================================ const { version } = require('../package.json') const banner = `/*! @license ScrollReveal v${version} Copyright 2021 Fisssion LLC. Licensed under the GNU General Public License 3.0 for compatible open source projects and non-commercial use. For commercial sites, themes, projects, and applications, keep your source code private/proprietary by purchasing a commercial license from https://scrollrevealjs.org/ */` export default banner ================================================ FILE: build/rollup.conf.js ================================================ import buble from 'rollup-plugin-buble' import json from 'rollup-plugin-json' import pkg from '../package.json' import nodeResolve from 'rollup-plugin-node-resolve' import banner from './rollup.conf.banner' const base = { input: './src/index.js', plugins: [json(), nodeResolve(), buble()] } const es = Object.assign({}, base, { external: [...Object.keys(pkg.dependencies || {})], output: { banner, format: 'es', file: './dist/scrollreveal.es.js' } }) const umd = Object.assign({}, base, { output: { banner, format: 'umd', file: './dist/scrollreveal.js', name: 'ScrollReveal' } }) export default [es, umd] ================================================ FILE: dist/scrollreveal.es.js ================================================ /*! @license ScrollReveal v4.0.9 Copyright 2021 Fisssion LLC. Licensed under the GNU General Public License 3.0 for compatible open source projects and non-commercial use. For commercial sites, themes, projects, and applications, keep your source code private/proprietary by purchasing a commercial license from https://scrollrevealjs.org/ */ import $ from 'tealight'; import { translateY, translateX, rotateX, rotateY, rotateZ, scale, parse, multiply } from 'rematrix'; import raf from 'miniraf'; var defaults = { 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: false, container: document.documentElement, desktop: true, mobile: true, reset: false, useDelay: 'always', viewFactor: 0.0, viewOffset: { top: 0, right: 0, bottom: 0, left: 0 }, afterReset: function afterReset() {}, afterReveal: function afterReveal() {}, beforeReset: function beforeReset() {}, beforeReveal: function beforeReveal() {} }; function failure() { document.documentElement.classList.remove('sr'); return { clean: function clean() {}, destroy: function destroy() {}, reveal: function reveal() {}, sync: function sync() {}, get noop() { return true } } } function success() { document.documentElement.classList.add('sr'); if (document.body) { document.body.style.height = '100%'; } else { document.addEventListener('DOMContentLoaded', function () { document.body.style.height = '100%'; }); } } var mount = { success: success, failure: failure }; function isObject(x) { return ( x !== null && x instanceof Object && (x.constructor === Object || Object.prototype.toString.call(x) === '[object Object]') ) } function each(collection, callback) { if (isObject(collection)) { var keys = Object.keys(collection); return keys.forEach(function (key) { return callback(collection[key], key, collection); }) } if (collection instanceof Array) { return collection.forEach(function (item, i) { return callback(item, i, collection); }) } throw new TypeError('Expected either an array or object literal.') } function logger(message) { var details = [], len = arguments.length - 1; while ( len-- > 0 ) details[ len ] = arguments[ len + 1 ]; if (this.constructor.debug && console) { var report = "%cScrollReveal: " + message; details.forEach(function (detail) { return (report += "\n — " + detail); }); console.log(report, 'color: #ea654b;'); // eslint-disable-line no-console } } function rinse() { var this$1 = this; var struct = function () { return ({ active: [], stale: [] }); }; var elementIds = struct(); var sequenceIds = struct(); var containerIds = struct(); /** * Take stock of active element IDs. */ try { each($('[data-sr-id]'), function (node) { var id = parseInt(node.getAttribute('data-sr-id')); elementIds.active.push(id); }); } catch (e) { throw e } /** * Destroy stale elements. */ each(this.store.elements, function (element) { if (elementIds.active.indexOf(element.id) === -1) { elementIds.stale.push(element.id); } }); each(elementIds.stale, function (staleId) { return delete this$1.store.elements[staleId]; }); /** * Take stock of active container and sequence IDs. */ each(this.store.elements, function (element) { if (containerIds.active.indexOf(element.containerId) === -1) { containerIds.active.push(element.containerId); } if (element.hasOwnProperty('sequence')) { if (sequenceIds.active.indexOf(element.sequence.id) === -1) { sequenceIds.active.push(element.sequence.id); } } }); /** * Destroy stale containers. */ each(this.store.containers, function (container) { if (containerIds.active.indexOf(container.id) === -1) { containerIds.stale.push(container.id); } }); each(containerIds.stale, function (staleId) { var stale = this$1.store.containers[staleId].node; stale.removeEventListener('scroll', this$1.delegate); stale.removeEventListener('resize', this$1.delegate); delete this$1.store.containers[staleId]; }); /** * Destroy stale sequences. */ each(this.store.sequences, function (sequence) { if (sequenceIds.active.indexOf(sequence.id) === -1) { sequenceIds.stale.push(sequence.id); } }); each(sequenceIds.stale, function (staleId) { return delete this$1.store.sequences[staleId]; }); } var getPrefixedCssProp = (function () { var properties = {}; var style = document.documentElement.style; function getPrefixedCssProperty(name, source) { if ( source === void 0 ) source = style; if (name && typeof name === 'string') { if (properties[name]) { return properties[name] } if (typeof source[name] === 'string') { return (properties[name] = name) } if (typeof source[("-webkit-" + name)] === 'string') { return (properties[name] = "-webkit-" + name) } throw new RangeError(("Unable to find \"" + name + "\" style property.")) } throw new TypeError('Expected a string.') } getPrefixedCssProperty.clearCache = function () { return (properties = {}); }; return getPrefixedCssProperty })(); function style(element) { var computed = window.getComputedStyle(element.node); var position = computed.position; var config = element.config; /** * Generate inline styles */ var inline = {}; var inlineStyle = element.node.getAttribute('style') || ''; var inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || []; inline.computed = inlineMatch ? inlineMatch.map(function (m) { return m.trim(); }).join('; ') + ';' : ''; inline.generated = inlineMatch.some(function (m) { return m.match(/visibility\s?:\s?visible/i); }) ? inline.computed : inlineMatch.concat( ['visibility: visible']).map(function (m) { return m.trim(); }).join('; ') + ';'; /** * Generate opacity styles */ var computedOpacity = parseFloat(computed.opacity); var configOpacity = !isNaN(parseFloat(config.opacity)) ? parseFloat(config.opacity) : parseFloat(computed.opacity); var opacity = { computed: computedOpacity !== configOpacity ? ("opacity: " + computedOpacity + ";") : '', generated: computedOpacity !== configOpacity ? ("opacity: " + configOpacity + ";") : '' }; /** * Generate transformation styles */ var transformations = []; if (parseFloat(config.distance)) { var axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X'; /** * Let’s make sure our our pixel distances are negative for top and left. * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS. */ var distance = config.distance; if (config.origin === 'top' || config.origin === 'left') { distance = /^-/.test(distance) ? distance.substr(1) : ("-" + distance); } var ref = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g); var value = ref[0]; var unit = ref[1]; switch (unit) { case 'em': distance = parseInt(computed.fontSize) * value; break case 'px': distance = value; break case '%': /** * Here we use `getBoundingClientRect` instead of * the existing data attached to `element.geometry` * because only the former includes any transformations * current applied to the element. * * If that behavior ends up being unintuitive, this * logic could instead utilize `element.geometry.height` * and `element.geoemetry.width` for the distance calculation */ distance = axis === 'Y' ? (element.node.getBoundingClientRect().height * value) / 100 : (element.node.getBoundingClientRect().width * value) / 100; break default: throw new RangeError('Unrecognized or missing distance unit.') } if (axis === 'Y') { transformations.push(translateY(distance)); } else { transformations.push(translateX(distance)); } } if (config.rotate.x) { transformations.push(rotateX(config.rotate.x)); } if (config.rotate.y) { transformations.push(rotateY(config.rotate.y)); } if (config.rotate.z) { transformations.push(rotateZ(config.rotate.z)); } if (config.scale !== 1) { if (config.scale === 0) { /** * The CSS Transforms matrix interpolation specification * basically disallows transitions of non-invertible * matrixes, which means browsers won't transition * elements with zero scale. * * That’s inconvenient for the API and developer * experience, so we simply nudge their value * slightly above zero; this allows browsers * to transition our element as expected. * * `0.0002` was the smallest number * that performed across browsers. */ transformations.push(scale(0.0002)); } else { transformations.push(scale(config.scale)); } } var transform = {}; if (transformations.length) { transform.property = getPrefixedCssProp('transform'); /** * The default computed transform value should be one of: * undefined || 'none' || 'matrix()' || 'matrix3d()' */ transform.computed = { raw: computed[transform.property], matrix: parse(computed[transform.property]) }; transformations.unshift(transform.computed.matrix); var product = transformations.reduce(multiply); transform.generated = { initial: ((transform.property) + ": matrix3d(" + (product.join(', ')) + ");"), final: ((transform.property) + ": matrix3d(" + (transform.computed.matrix.join(', ')) + ");") }; } else { transform.generated = { initial: '', final: '' }; } /** * Generate transition styles */ var transition = {}; if (opacity.generated || transform.generated.initial) { transition.property = getPrefixedCssProp('transition'); transition.computed = computed[transition.property]; transition.fragments = []; var delay = config.delay; var duration = config.duration; var easing = config.easing; if (opacity.generated) { transition.fragments.push({ delayed: ("opacity " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"), instant: ("opacity " + (duration / 1000) + "s " + easing + " 0s") }); } if (transform.generated.initial) { transition.fragments.push({ delayed: ((transform.property) + " " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"), instant: ((transform.property) + " " + (duration / 1000) + "s " + easing + " 0s") }); } /** * The default computed transition property should be undefined, or one of: * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()' */ var hasCustomTransition = transition.computed && !transition.computed.match(/all 0s|none 0s/); if (hasCustomTransition) { transition.fragments.unshift({ delayed: transition.computed, instant: transition.computed }); } var composed = transition.fragments.reduce( function (composition, fragment, i) { composition.delayed += i === 0 ? fragment.delayed : (", " + (fragment.delayed)); composition.instant += i === 0 ? fragment.instant : (", " + (fragment.instant)); return composition }, { delayed: '', instant: '' } ); transition.generated = { delayed: ((transition.property) + ": " + (composed.delayed) + ";"), instant: ((transition.property) + ": " + (composed.instant) + ";") }; } else { transition.generated = { delayed: '', instant: '' }; } return { inline: inline, opacity: opacity, position: position, transform: transform, transition: transition } } /** * apply a CSS string to an element using the CSSOM (element.style) rather * than setAttribute, which may violate the content security policy. * * @param {Node} [el] Element to receive styles. * @param {string} [declaration] Styles to apply. */ function applyStyle (el, declaration) { declaration.split(';').forEach(function (pair) { var ref = pair.split(':'); var property = ref[0]; var value = ref.slice(1); if (property && value) { el.style[property.trim()] = value.join(':'); } }); } function clean(target) { var this$1 = this; var dirty; try { each($(target), function (node) { var id = node.getAttribute('data-sr-id'); if (id !== null) { dirty = true; var element = this$1.store.elements[id]; if (element.callbackTimer) { window.clearTimeout(element.callbackTimer.clock); } applyStyle(element.node, element.styles.inline.generated); node.removeAttribute('data-sr-id'); delete this$1.store.elements[id]; } }); } catch (e) { return logger.call(this, 'Clean failed.', e.message) } if (dirty) { try { rinse.call(this); } catch (e) { return logger.call(this, 'Clean failed.', e.message) } } } function destroy() { var this$1 = this; /** * Remove all generated styles and element ids */ each(this.store.elements, function (element) { applyStyle(element.node, element.styles.inline.generated); element.node.removeAttribute('data-sr-id'); }); /** * Remove all event listeners. */ each(this.store.containers, function (container) { var target = container.node === document.documentElement ? window : container.node; target.removeEventListener('scroll', this$1.delegate); target.removeEventListener('resize', this$1.delegate); }); /** * Clear all data from the store */ this.store = { containers: {}, elements: {}, history: [], sequences: {} }; } function deepAssign(target) { var sources = [], len = arguments.length - 1; while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ]; if (isObject(target)) { each(sources, function (source) { each(source, function (data, key) { if (isObject(data)) { if (!target[key] || !isObject(target[key])) { target[key] = {}; } deepAssign(target[key], data); } else { target[key] = data; } }); }); return target } else { throw new TypeError('Target must be an object literal.') } } function isMobile(agent) { if ( agent === void 0 ) agent = navigator.userAgent; return /Android|iPhone|iPad|iPod/i.test(agent) } var nextUniqueId = (function () { var uid = 0; return function () { return uid++; } })(); function initialize() { var this$1 = this; rinse.call(this); each(this.store.elements, function (element) { var styles = [element.styles.inline.generated]; if (element.visible) { styles.push(element.styles.opacity.computed); styles.push(element.styles.transform.generated.final); element.revealed = true; } else { styles.push(element.styles.opacity.generated); styles.push(element.styles.transform.generated.initial); element.revealed = false; } applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); }); each(this.store.containers, function (container) { var target = container.node === document.documentElement ? window : container.node; target.addEventListener('scroll', this$1.delegate); target.addEventListener('resize', this$1.delegate); }); /** * Manually invoke delegate once to capture * element and container dimensions, container * scroll position, and trigger any valid reveals */ this.delegate(); /** * Wipe any existing `setTimeout` now * that initialization has completed. */ this.initTimeout = null; } function animate(element, force) { if ( force === void 0 ) force = {}; var pristine = force.pristine || this.pristine; var delayed = element.config.useDelay === 'always' || (element.config.useDelay === 'onload' && pristine) || (element.config.useDelay === 'once' && !element.seen); var shouldReveal = element.visible && !element.revealed; var shouldReset = !element.visible && element.revealed && element.config.reset; if (force.reveal || shouldReveal) { return triggerReveal.call(this, element, delayed) } if (force.reset || shouldReset) { return triggerReset.call(this, element) } } function triggerReveal(element, delayed) { var styles = [ element.styles.inline.generated, element.styles.opacity.computed, element.styles.transform.generated.final ]; if (delayed) { styles.push(element.styles.transition.generated.delayed); } else { styles.push(element.styles.transition.generated.instant); } element.revealed = element.seen = true; applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); registerCallbacks.call(this, element, delayed); } function triggerReset(element) { var styles = [ element.styles.inline.generated, element.styles.opacity.generated, element.styles.transform.generated.initial, element.styles.transition.generated.instant ]; element.revealed = false; applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); registerCallbacks.call(this, element); } function registerCallbacks(element, isDelayed) { var this$1 = this; var duration = isDelayed ? element.config.duration + element.config.delay : element.config.duration; var beforeCallback = element.revealed ? element.config.beforeReveal : element.config.beforeReset; var afterCallback = element.revealed ? element.config.afterReveal : element.config.afterReset; var elapsed = 0; if (element.callbackTimer) { elapsed = Date.now() - element.callbackTimer.start; window.clearTimeout(element.callbackTimer.clock); } beforeCallback(element.node); element.callbackTimer = { start: Date.now(), clock: window.setTimeout(function () { afterCallback(element.node); element.callbackTimer = null; if (element.revealed && !element.config.reset && element.config.cleanup) { clean.call(this$1, element.node); } }, duration - elapsed) }; } function sequence(element, pristine) { if ( pristine === void 0 ) pristine = this.pristine; /** * We first check if the element should reset. */ if (!element.visible && element.revealed && element.config.reset) { return animate.call(this, element, { reset: true }) } var seq = this.store.sequences[element.sequence.id]; var i = element.sequence.index; if (seq) { var visible = new SequenceModel(seq, 'visible', this.store); var revealed = new SequenceModel(seq, 'revealed', this.store); seq.models = { visible: visible, revealed: revealed }; /** * If the sequence has no revealed members, * then we reveal the first visible element * within that sequence. * * The sequence then cues a recursive call * in both directions. */ if (!revealed.body.length) { var nextId = seq.members[visible.body[0]]; var nextElement = this.store.elements[nextId]; if (nextElement) { cue.call(this, seq, visible.body[0], -1, pristine); cue.call(this, seq, visible.body[0], +1, pristine); return animate.call(this, nextElement, { reveal: true, pristine: pristine }) } } /** * If our element isn’t resetting, we check the * element sequence index against the head, and * then the foot of the sequence. */ if ( !seq.blocked.head && i === [].concat( revealed.head ).pop() && i >= [].concat( visible.body ).shift() ) { cue.call(this, seq, i, -1, pristine); return animate.call(this, element, { reveal: true, pristine: pristine }) } if ( !seq.blocked.foot && i === [].concat( revealed.foot ).shift() && i <= [].concat( visible.body ).pop() ) { cue.call(this, seq, i, +1, pristine); return animate.call(this, element, { reveal: true, pristine: pristine }) } } } function Sequence(interval) { var i = Math.abs(interval); if (!isNaN(i)) { this.id = nextUniqueId(); this.interval = Math.max(i, 16); this.members = []; this.models = {}; this.blocked = { head: false, foot: false }; } else { throw new RangeError('Invalid sequence interval.') } } function SequenceModel(seq, prop, store) { var this$1 = this; this.head = []; this.body = []; this.foot = []; each(seq.members, function (id, index) { var element = store.elements[id]; if (element && element[prop]) { this$1.body.push(index); } }); if (this.body.length) { each(seq.members, function (id, index) { var element = store.elements[id]; if (element && !element[prop]) { if (index < this$1.body[0]) { this$1.head.push(index); } else { this$1.foot.push(index); } } }); } } function cue(seq, i, direction, pristine) { var this$1 = this; var blocked = ['head', null, 'foot'][1 + direction]; var nextId = seq.members[i + direction]; var nextElement = this.store.elements[nextId]; seq.blocked[blocked] = true; setTimeout(function () { seq.blocked[blocked] = false; if (nextElement) { sequence.call(this$1, nextElement, pristine); } }, seq.interval); } function reveal(target, options, syncing) { var this$1 = this; if ( options === void 0 ) options = {}; if ( syncing === void 0 ) syncing = false; var containerBuffer = []; var sequence$$1; var interval = options.interval || defaults.interval; try { if (interval) { sequence$$1 = new Sequence(interval); } var nodes = $(target); if (!nodes.length) { throw new Error('Invalid reveal target.') } var elements = nodes.reduce(function (elementBuffer, elementNode) { var element = {}; var existingId = elementNode.getAttribute('data-sr-id'); if (existingId) { deepAssign(element, this$1.store.elements[existingId]); /** * In order to prevent previously generated styles * from throwing off the new styles, the style tag * has to be reverted to its pre-reveal state. */ applyStyle(element.node, element.styles.inline.computed); } else { element.id = nextUniqueId(); element.node = elementNode; element.seen = false; element.revealed = false; element.visible = false; } var config = deepAssign({}, element.config || this$1.defaults, options); if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { if (existingId) { clean.call(this$1, element); } return elementBuffer // skip elements that are disabled } var containerNode = $(config.container)[0]; if (!containerNode) { throw new Error('Invalid container.') } if (!containerNode.contains(elementNode)) { return elementBuffer // skip elements found outside the container } var containerId; { containerId = getContainerId( containerNode, containerBuffer, this$1.store.containers ); if (containerId === null) { containerId = nextUniqueId(); containerBuffer.push({ id: containerId, node: containerNode }); } } element.config = config; element.containerId = containerId; element.styles = style(element); if (sequence$$1) { element.sequence = { id: sequence$$1.id, index: sequence$$1.members.length }; sequence$$1.members.push(element.id); } elementBuffer.push(element); return elementBuffer }, []); /** * Modifying the DOM via setAttribute needs to be handled * separately from reading computed styles in the map above * for the browser to batch DOM changes (limiting reflows) */ each(elements, function (element) { this$1.store.elements[element.id] = element; element.node.setAttribute('data-sr-id', element.id); }); } catch (e) { return logger.call(this, 'Reveal failed.', e.message) } /** * Now that element set-up is complete... * Let’s commit any container and sequence data we have to the store. */ each(containerBuffer, function (container) { this$1.store.containers[container.id] = { id: container.id, node: container.node }; }); if (sequence$$1) { this.store.sequences[sequence$$1.id] = sequence$$1; } /** * If reveal wasn't invoked by sync, we want to * make sure to add this call to the history. */ if (syncing !== true) { this.store.history.push({ target: target, options: options }); /** * Push initialization to the event queue, giving * multiple reveal calls time to be interpreted. */ if (this.initTimeout) { window.clearTimeout(this.initTimeout); } this.initTimeout = window.setTimeout(initialize.bind(this), 0); } } function getContainerId(node) { var collections = [], len = arguments.length - 1; while ( len-- > 0 ) collections[ len ] = arguments[ len + 1 ]; var id = null; each(collections, function (collection) { each(collection, function (container) { if (id === null && container.node === node) { id = container.id; } }); }); return id } /** * Re-runs the reveal method for each record stored in history, * for capturing new content asynchronously loaded into the DOM. */ function sync() { var this$1 = this; each(this.store.history, function (record) { reveal.call(this$1, record.target, record.options, true); }); initialize.call(this); } var polyfill = function (x) { return (x > 0) - (x < 0) || +x; }; var mathSign = Math.sign || polyfill; function getGeometry(target, isContainer) { /** * We want to ignore padding and scrollbars for container elements. * More information here: https://goo.gl/vOZpbz */ var height = isContainer ? target.node.clientHeight : target.node.offsetHeight; var width = isContainer ? target.node.clientWidth : target.node.offsetWidth; var offsetTop = 0; var offsetLeft = 0; var node = target.node; do { if (!isNaN(node.offsetTop)) { offsetTop += node.offsetTop; } if (!isNaN(node.offsetLeft)) { offsetLeft += node.offsetLeft; } node = node.offsetParent; } while (node) return { bounds: { top: offsetTop, right: offsetLeft + width, bottom: offsetTop + height, left: offsetLeft }, height: height, width: width } } function getScrolled(container) { var top, left; if (container.node === document.documentElement) { top = window.pageYOffset; left = window.pageXOffset; } else { top = container.node.scrollTop; left = container.node.scrollLeft; } return { top: top, left: left } } function isElementVisible(element) { if ( element === void 0 ) element = {}; var container = this.store.containers[element.containerId]; if (!container) { return } var viewFactor = Math.max(0, Math.min(1, element.config.viewFactor)); var viewOffset = element.config.viewOffset; var elementBounds = { top: element.geometry.bounds.top + element.geometry.height * viewFactor, right: element.geometry.bounds.right - element.geometry.width * viewFactor, bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor, left: element.geometry.bounds.left + element.geometry.width * viewFactor }; var containerBounds = { top: container.geometry.bounds.top + container.scroll.top + viewOffset.top, right: container.geometry.bounds.right + container.scroll.left - viewOffset.right, bottom: container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom, left: container.geometry.bounds.left + container.scroll.left + viewOffset.left }; return ( (elementBounds.top < containerBounds.bottom && elementBounds.right > containerBounds.left && elementBounds.bottom > containerBounds.top && elementBounds.left < containerBounds.right) || element.styles.position === 'fixed' ) } function delegate( event, elements ) { var this$1 = this; if ( event === void 0 ) event = { type: 'init' }; if ( elements === void 0 ) elements = this.store.elements; raf(function () { var stale = event.type === 'init' || event.type === 'resize'; each(this$1.store.containers, function (container) { if (stale) { container.geometry = getGeometry.call(this$1, container, true); } var scroll = getScrolled.call(this$1, container); if (container.scroll) { container.direction = { x: mathSign(scroll.left - container.scroll.left), y: mathSign(scroll.top - container.scroll.top) }; } container.scroll = scroll; }); /** * Due to how the sequencer is implemented, it’s * important that we update the state of all * elements, before any animation logic is * evaluated (in the second loop below). */ each(elements, function (element) { if (stale || element.geometry === undefined) { element.geometry = getGeometry.call(this$1, element); } element.visible = isElementVisible.call(this$1, element); }); each(elements, function (element) { if (element.sequence) { sequence.call(this$1, element); } else { animate.call(this$1, element); } }); this$1.pristine = false; }); } function isTransformSupported() { var style = document.documentElement.style; return 'transform' in style || 'WebkitTransform' in style } function isTransitionSupported() { var style = document.documentElement.style; return 'transition' in style || 'WebkitTransition' in style } var version = "4.0.9"; var boundDelegate; var boundDestroy; var boundReveal; var boundClean; var boundSync; var config; var debug; var instance; function ScrollReveal(options) { if ( options === void 0 ) options = {}; var invokedWithoutNew = typeof this === 'undefined' || Object.getPrototypeOf(this) !== ScrollReveal.prototype; if (invokedWithoutNew) { return new ScrollReveal(options) } if (!ScrollReveal.isSupported()) { logger.call(this, 'Instantiation failed.', 'This browser is not supported.'); return mount.failure() } var buffer; try { buffer = config ? deepAssign({}, config, options) : deepAssign({}, defaults, options); } catch (e) { logger.call(this, 'Invalid configuration.', e.message); return mount.failure() } try { var container = $(buffer.container)[0]; if (!container) { throw new Error('Invalid container.') } } catch (e) { logger.call(this, e.message); return mount.failure() } config = buffer; if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { logger.call( this, 'This device is disabled.', ("desktop: " + (config.desktop)), ("mobile: " + (config.mobile)) ); return mount.failure() } mount.success(); this.store = { containers: {}, elements: {}, history: [], sequences: {} }; this.pristine = true; boundDelegate = boundDelegate || delegate.bind(this); boundDestroy = boundDestroy || destroy.bind(this); boundReveal = boundReveal || reveal.bind(this); boundClean = boundClean || clean.bind(this); boundSync = boundSync || sync.bind(this); Object.defineProperty(this, 'delegate', { get: function () { return boundDelegate; } }); Object.defineProperty(this, 'destroy', { get: function () { return boundDestroy; } }); Object.defineProperty(this, 'reveal', { get: function () { return boundReveal; } }); Object.defineProperty(this, 'clean', { get: function () { return boundClean; } }); Object.defineProperty(this, 'sync', { get: function () { return boundSync; } }); Object.defineProperty(this, 'defaults', { get: function () { return config; } }); Object.defineProperty(this, 'version', { get: function () { return version; } }); Object.defineProperty(this, 'noop', { get: function () { return false; } }); return instance ? instance : (instance = this) } ScrollReveal.isSupported = function () { return isTransformSupported() && isTransitionSupported(); }; Object.defineProperty(ScrollReveal, 'debug', { get: function () { return debug || false; }, set: function (value) { return (debug = typeof value === 'boolean' ? value : debug); } }); ScrollReveal(); export default ScrollReveal; ================================================ FILE: dist/scrollreveal.js ================================================ /*! @license ScrollReveal v4.0.9 Copyright 2021 Fisssion LLC. Licensed under the GNU General Public License 3.0 for compatible open source projects and non-commercial use. For commercial sites, themes, projects, and applications, keep your source code private/proprietary by purchasing a commercial license from https://scrollrevealjs.org/ */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.ScrollReveal = factory()); }(this, function () { 'use strict'; var defaults = { 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: false, container: document.documentElement, desktop: true, mobile: true, reset: false, useDelay: 'always', viewFactor: 0.0, viewOffset: { top: 0, right: 0, bottom: 0, left: 0 }, afterReset: function afterReset() {}, afterReveal: function afterReveal() {}, beforeReset: function beforeReset() {}, beforeReveal: function beforeReveal() {} }; function failure() { document.documentElement.classList.remove('sr'); return { clean: function clean() {}, destroy: function destroy() {}, reveal: function reveal() {}, sync: function sync() {}, get noop() { return true } } } function success() { document.documentElement.classList.add('sr'); if (document.body) { document.body.style.height = '100%'; } else { document.addEventListener('DOMContentLoaded', function () { document.body.style.height = '100%'; }); } } var mount = { success: success, failure: failure }; /*! @license is-dom-node v1.0.4 Copyright 2018 Fisssion LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ function isDomNode(x) { return typeof window.Node === 'object' ? x instanceof window.Node : x !== null && typeof x === 'object' && typeof x.nodeType === 'number' && typeof x.nodeName === 'string' } /*! @license is-dom-node-list v1.2.1 Copyright 2018 Fisssion LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ function isDomNodeList(x) { var prototypeToString = Object.prototype.toString.call(x); var regex = /^\[object (HTMLCollection|NodeList|Object)\]$/; return typeof window.NodeList === 'object' ? x instanceof window.NodeList : x !== null && typeof x === 'object' && typeof x.length === 'number' && regex.test(prototypeToString) && (x.length === 0 || isDomNode(x[0])) } /*! @license Tealight v0.3.6 Copyright 2018 Fisssion LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ function tealight(target, context) { if ( context === void 0 ) { context = document; } if (target instanceof Array) { return target.filter(isDomNode); } if (isDomNode(target)) { return [target]; } if (isDomNodeList(target)) { return Array.prototype.slice.call(target); } if (typeof target === "string") { try { var query = context.querySelectorAll(target); return Array.prototype.slice.call(query); } catch (err) { return []; } } return []; } function isObject(x) { return ( x !== null && x instanceof Object && (x.constructor === Object || Object.prototype.toString.call(x) === '[object Object]') ) } function each(collection, callback) { if (isObject(collection)) { var keys = Object.keys(collection); return keys.forEach(function (key) { return callback(collection[key], key, collection); }) } if (collection instanceof Array) { return collection.forEach(function (item, i) { return callback(item, i, collection); }) } throw new TypeError('Expected either an array or object literal.') } function logger(message) { var details = [], len = arguments.length - 1; while ( len-- > 0 ) details[ len ] = arguments[ len + 1 ]; if (this.constructor.debug && console) { var report = "%cScrollReveal: " + message; details.forEach(function (detail) { return (report += "\n — " + detail); }); console.log(report, 'color: #ea654b;'); // eslint-disable-line no-console } } function rinse() { var this$1 = this; var struct = function () { return ({ active: [], stale: [] }); }; var elementIds = struct(); var sequenceIds = struct(); var containerIds = struct(); /** * Take stock of active element IDs. */ try { each(tealight('[data-sr-id]'), function (node) { var id = parseInt(node.getAttribute('data-sr-id')); elementIds.active.push(id); }); } catch (e) { throw e } /** * Destroy stale elements. */ each(this.store.elements, function (element) { if (elementIds.active.indexOf(element.id) === -1) { elementIds.stale.push(element.id); } }); each(elementIds.stale, function (staleId) { return delete this$1.store.elements[staleId]; }); /** * Take stock of active container and sequence IDs. */ each(this.store.elements, function (element) { if (containerIds.active.indexOf(element.containerId) === -1) { containerIds.active.push(element.containerId); } if (element.hasOwnProperty('sequence')) { if (sequenceIds.active.indexOf(element.sequence.id) === -1) { sequenceIds.active.push(element.sequence.id); } } }); /** * Destroy stale containers. */ each(this.store.containers, function (container) { if (containerIds.active.indexOf(container.id) === -1) { containerIds.stale.push(container.id); } }); each(containerIds.stale, function (staleId) { var stale = this$1.store.containers[staleId].node; stale.removeEventListener('scroll', this$1.delegate); stale.removeEventListener('resize', this$1.delegate); delete this$1.store.containers[staleId]; }); /** * Destroy stale sequences. */ each(this.store.sequences, function (sequence) { if (sequenceIds.active.indexOf(sequence.id) === -1) { sequenceIds.stale.push(sequence.id); } }); each(sequenceIds.stale, function (staleId) { return delete this$1.store.sequences[staleId]; }); } /*! @license Rematrix v0.3.0 Copyright 2018 Julian Lloyd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * @module Rematrix */ /** * Transformation matrices in the browser come in two flavors: * * - `matrix` using 6 values (short) * - `matrix3d` using 16 values (long) * * This utility follows this [conversion guide](https://goo.gl/EJlUQ1) * to expand short form matrices to their equivalent long form. * * @param {array} source - Accepts both short and long form matrices. * @return {array} */ function format(source) { if (source.constructor !== Array) { throw new TypeError('Expected array.') } if (source.length === 16) { return source } if (source.length === 6) { var matrix = identity(); matrix[0] = source[0]; matrix[1] = source[1]; matrix[4] = source[2]; matrix[5] = source[3]; matrix[12] = source[4]; matrix[13] = source[5]; return matrix } throw new RangeError('Expected array with either 6 or 16 values.') } /** * Returns a matrix representing no transformation. The product of any matrix * multiplied by the identity matrix will be the original matrix. * * > **Tip:** Similar to how `5 * 1 === 5`, where `1` is the identity. * * @return {array} */ function identity() { var matrix = []; for (var i = 0; i < 16; i++) { i % 5 == 0 ? matrix.push(1) : matrix.push(0); } return matrix } /** * Returns a 4x4 matrix describing the combined transformations * of both arguments. * * > **Note:** Order is very important. For example, rotating 45° * along the Z-axis, followed by translating 500 pixels along the * Y-axis... is not the same as translating 500 pixels along the * Y-axis, followed by rotating 45° along on the Z-axis. * * @param {array} m - Accepts both short and long form matrices. * @param {array} x - Accepts both short and long form matrices. * @return {array} */ function multiply(m, x) { var fm = format(m); var fx = format(x); var product = []; for (var i = 0; i < 4; i++) { var row = [fm[i], fm[i + 4], fm[i + 8], fm[i + 12]]; for (var j = 0; j < 4; j++) { var k = j * 4; var col = [fx[k], fx[k + 1], fx[k + 2], fx[k + 3]]; var result = row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3]; product[i + k] = result; } } return product } /** * Attempts to return a 4x4 matrix describing the CSS transform * matrix passed in, but will return the identity matrix as a * fallback. * * > **Tip:** This method is used to convert a CSS matrix (retrieved as a * `string` from computed styles) to its equivalent array format. * * @param {string} source - `matrix` or `matrix3d` CSS Transform value. * @return {array} */ function parse(source) { if (typeof source === 'string') { var match = source.match(/matrix(3d)?\(([^)]+)\)/); if (match) { var raw = match[2].split(', ').map(parseFloat); return format(raw) } } return identity() } /** * Returns a 4x4 matrix describing X-axis rotation. * * @param {number} angle - Measured in degrees. * @return {array} */ function rotateX(angle) { var theta = Math.PI / 180 * angle; var matrix = identity(); matrix[5] = matrix[10] = Math.cos(theta); matrix[6] = matrix[9] = Math.sin(theta); matrix[9] *= -1; return matrix } /** * Returns a 4x4 matrix describing Y-axis rotation. * * @param {number} angle - Measured in degrees. * @return {array} */ function rotateY(angle) { var theta = Math.PI / 180 * angle; var matrix = identity(); matrix[0] = matrix[10] = Math.cos(theta); matrix[2] = matrix[8] = Math.sin(theta); matrix[2] *= -1; return matrix } /** * Returns a 4x4 matrix describing Z-axis rotation. * * @param {number} angle - Measured in degrees. * @return {array} */ function rotateZ(angle) { var theta = Math.PI / 180 * angle; var matrix = identity(); matrix[0] = matrix[5] = Math.cos(theta); matrix[1] = matrix[4] = Math.sin(theta); matrix[4] *= -1; return matrix } /** * Returns a 4x4 matrix describing 2D scaling. The first argument * is used for both X and Y-axis scaling, unless an optional * second argument is provided to explicitly define Y-axis scaling. * * @param {number} scalar - Decimal multiplier. * @param {number} [scalarY] - Decimal multiplier. * @return {array} */ function scale(scalar, scalarY) { var matrix = identity(); matrix[0] = scalar; matrix[5] = typeof scalarY === 'number' ? scalarY : scalar; return matrix } /** * Returns a 4x4 matrix describing X-axis translation. * * @param {number} distance - Measured in pixels. * @return {array} */ function translateX(distance) { var matrix = identity(); matrix[12] = distance; return matrix } /** * Returns a 4x4 matrix describing Y-axis translation. * * @param {number} distance - Measured in pixels. * @return {array} */ function translateY(distance) { var matrix = identity(); matrix[13] = distance; return matrix } var getPrefixedCssProp = (function () { var properties = {}; var style = document.documentElement.style; function getPrefixedCssProperty(name, source) { if ( source === void 0 ) source = style; if (name && typeof name === 'string') { if (properties[name]) { return properties[name] } if (typeof source[name] === 'string') { return (properties[name] = name) } if (typeof source[("-webkit-" + name)] === 'string') { return (properties[name] = "-webkit-" + name) } throw new RangeError(("Unable to find \"" + name + "\" style property.")) } throw new TypeError('Expected a string.') } getPrefixedCssProperty.clearCache = function () { return (properties = {}); }; return getPrefixedCssProperty })(); function style(element) { var computed = window.getComputedStyle(element.node); var position = computed.position; var config = element.config; /** * Generate inline styles */ var inline = {}; var inlineStyle = element.node.getAttribute('style') || ''; var inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || []; inline.computed = inlineMatch ? inlineMatch.map(function (m) { return m.trim(); }).join('; ') + ';' : ''; inline.generated = inlineMatch.some(function (m) { return m.match(/visibility\s?:\s?visible/i); }) ? inline.computed : inlineMatch.concat( ['visibility: visible']).map(function (m) { return m.trim(); }).join('; ') + ';'; /** * Generate opacity styles */ var computedOpacity = parseFloat(computed.opacity); var configOpacity = !isNaN(parseFloat(config.opacity)) ? parseFloat(config.opacity) : parseFloat(computed.opacity); var opacity = { computed: computedOpacity !== configOpacity ? ("opacity: " + computedOpacity + ";") : '', generated: computedOpacity !== configOpacity ? ("opacity: " + configOpacity + ";") : '' }; /** * Generate transformation styles */ var transformations = []; if (parseFloat(config.distance)) { var axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X'; /** * Let’s make sure our our pixel distances are negative for top and left. * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS. */ var distance = config.distance; if (config.origin === 'top' || config.origin === 'left') { distance = /^-/.test(distance) ? distance.substr(1) : ("-" + distance); } var ref = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g); var value = ref[0]; var unit = ref[1]; switch (unit) { case 'em': distance = parseInt(computed.fontSize) * value; break case 'px': distance = value; break case '%': /** * Here we use `getBoundingClientRect` instead of * the existing data attached to `element.geometry` * because only the former includes any transformations * current applied to the element. * * If that behavior ends up being unintuitive, this * logic could instead utilize `element.geometry.height` * and `element.geoemetry.width` for the distance calculation */ distance = axis === 'Y' ? (element.node.getBoundingClientRect().height * value) / 100 : (element.node.getBoundingClientRect().width * value) / 100; break default: throw new RangeError('Unrecognized or missing distance unit.') } if (axis === 'Y') { transformations.push(translateY(distance)); } else { transformations.push(translateX(distance)); } } if (config.rotate.x) { transformations.push(rotateX(config.rotate.x)); } if (config.rotate.y) { transformations.push(rotateY(config.rotate.y)); } if (config.rotate.z) { transformations.push(rotateZ(config.rotate.z)); } if (config.scale !== 1) { if (config.scale === 0) { /** * The CSS Transforms matrix interpolation specification * basically disallows transitions of non-invertible * matrixes, which means browsers won't transition * elements with zero scale. * * That’s inconvenient for the API and developer * experience, so we simply nudge their value * slightly above zero; this allows browsers * to transition our element as expected. * * `0.0002` was the smallest number * that performed across browsers. */ transformations.push(scale(0.0002)); } else { transformations.push(scale(config.scale)); } } var transform = {}; if (transformations.length) { transform.property = getPrefixedCssProp('transform'); /** * The default computed transform value should be one of: * undefined || 'none' || 'matrix()' || 'matrix3d()' */ transform.computed = { raw: computed[transform.property], matrix: parse(computed[transform.property]) }; transformations.unshift(transform.computed.matrix); var product = transformations.reduce(multiply); transform.generated = { initial: ((transform.property) + ": matrix3d(" + (product.join(', ')) + ");"), final: ((transform.property) + ": matrix3d(" + (transform.computed.matrix.join(', ')) + ");") }; } else { transform.generated = { initial: '', final: '' }; } /** * Generate transition styles */ var transition = {}; if (opacity.generated || transform.generated.initial) { transition.property = getPrefixedCssProp('transition'); transition.computed = computed[transition.property]; transition.fragments = []; var delay = config.delay; var duration = config.duration; var easing = config.easing; if (opacity.generated) { transition.fragments.push({ delayed: ("opacity " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"), instant: ("opacity " + (duration / 1000) + "s " + easing + " 0s") }); } if (transform.generated.initial) { transition.fragments.push({ delayed: ((transform.property) + " " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"), instant: ((transform.property) + " " + (duration / 1000) + "s " + easing + " 0s") }); } /** * The default computed transition property should be undefined, or one of: * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()' */ var hasCustomTransition = transition.computed && !transition.computed.match(/all 0s|none 0s/); if (hasCustomTransition) { transition.fragments.unshift({ delayed: transition.computed, instant: transition.computed }); } var composed = transition.fragments.reduce( function (composition, fragment, i) { composition.delayed += i === 0 ? fragment.delayed : (", " + (fragment.delayed)); composition.instant += i === 0 ? fragment.instant : (", " + (fragment.instant)); return composition }, { delayed: '', instant: '' } ); transition.generated = { delayed: ((transition.property) + ": " + (composed.delayed) + ";"), instant: ((transition.property) + ": " + (composed.instant) + ";") }; } else { transition.generated = { delayed: '', instant: '' }; } return { inline: inline, opacity: opacity, position: position, transform: transform, transition: transition } } /** * apply a CSS string to an element using the CSSOM (element.style) rather * than setAttribute, which may violate the content security policy. * * @param {Node} [el] Element to receive styles. * @param {string} [declaration] Styles to apply. */ function applyStyle (el, declaration) { declaration.split(';').forEach(function (pair) { var ref = pair.split(':'); var property = ref[0]; var value = ref.slice(1); if (property && value) { el.style[property.trim()] = value.join(':'); } }); } function clean(target) { var this$1 = this; var dirty; try { each(tealight(target), function (node) { var id = node.getAttribute('data-sr-id'); if (id !== null) { dirty = true; var element = this$1.store.elements[id]; if (element.callbackTimer) { window.clearTimeout(element.callbackTimer.clock); } applyStyle(element.node, element.styles.inline.generated); node.removeAttribute('data-sr-id'); delete this$1.store.elements[id]; } }); } catch (e) { return logger.call(this, 'Clean failed.', e.message) } if (dirty) { try { rinse.call(this); } catch (e) { return logger.call(this, 'Clean failed.', e.message) } } } function destroy() { var this$1 = this; /** * Remove all generated styles and element ids */ each(this.store.elements, function (element) { applyStyle(element.node, element.styles.inline.generated); element.node.removeAttribute('data-sr-id'); }); /** * Remove all event listeners. */ each(this.store.containers, function (container) { var target = container.node === document.documentElement ? window : container.node; target.removeEventListener('scroll', this$1.delegate); target.removeEventListener('resize', this$1.delegate); }); /** * Clear all data from the store */ this.store = { containers: {}, elements: {}, history: [], sequences: {} }; } function deepAssign(target) { var sources = [], len = arguments.length - 1; while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ]; if (isObject(target)) { each(sources, function (source) { each(source, function (data, key) { if (isObject(data)) { if (!target[key] || !isObject(target[key])) { target[key] = {}; } deepAssign(target[key], data); } else { target[key] = data; } }); }); return target } else { throw new TypeError('Target must be an object literal.') } } function isMobile(agent) { if ( agent === void 0 ) agent = navigator.userAgent; return /Android|iPhone|iPad|iPod/i.test(agent) } var nextUniqueId = (function () { var uid = 0; return function () { return uid++; } })(); function initialize() { var this$1 = this; rinse.call(this); each(this.store.elements, function (element) { var styles = [element.styles.inline.generated]; if (element.visible) { styles.push(element.styles.opacity.computed); styles.push(element.styles.transform.generated.final); element.revealed = true; } else { styles.push(element.styles.opacity.generated); styles.push(element.styles.transform.generated.initial); element.revealed = false; } applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); }); each(this.store.containers, function (container) { var target = container.node === document.documentElement ? window : container.node; target.addEventListener('scroll', this$1.delegate); target.addEventListener('resize', this$1.delegate); }); /** * Manually invoke delegate once to capture * element and container dimensions, container * scroll position, and trigger any valid reveals */ this.delegate(); /** * Wipe any existing `setTimeout` now * that initialization has completed. */ this.initTimeout = null; } function animate(element, force) { if ( force === void 0 ) force = {}; var pristine = force.pristine || this.pristine; var delayed = element.config.useDelay === 'always' || (element.config.useDelay === 'onload' && pristine) || (element.config.useDelay === 'once' && !element.seen); var shouldReveal = element.visible && !element.revealed; var shouldReset = !element.visible && element.revealed && element.config.reset; if (force.reveal || shouldReveal) { return triggerReveal.call(this, element, delayed) } if (force.reset || shouldReset) { return triggerReset.call(this, element) } } function triggerReveal(element, delayed) { var styles = [ element.styles.inline.generated, element.styles.opacity.computed, element.styles.transform.generated.final ]; if (delayed) { styles.push(element.styles.transition.generated.delayed); } else { styles.push(element.styles.transition.generated.instant); } element.revealed = element.seen = true; applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); registerCallbacks.call(this, element, delayed); } function triggerReset(element) { var styles = [ element.styles.inline.generated, element.styles.opacity.generated, element.styles.transform.generated.initial, element.styles.transition.generated.instant ]; element.revealed = false; applyStyle(element.node, styles.filter(function (s) { return s !== ''; }).join(' ')); registerCallbacks.call(this, element); } function registerCallbacks(element, isDelayed) { var this$1 = this; var duration = isDelayed ? element.config.duration + element.config.delay : element.config.duration; var beforeCallback = element.revealed ? element.config.beforeReveal : element.config.beforeReset; var afterCallback = element.revealed ? element.config.afterReveal : element.config.afterReset; var elapsed = 0; if (element.callbackTimer) { elapsed = Date.now() - element.callbackTimer.start; window.clearTimeout(element.callbackTimer.clock); } beforeCallback(element.node); element.callbackTimer = { start: Date.now(), clock: window.setTimeout(function () { afterCallback(element.node); element.callbackTimer = null; if (element.revealed && !element.config.reset && element.config.cleanup) { clean.call(this$1, element.node); } }, duration - elapsed) }; } function sequence(element, pristine) { if ( pristine === void 0 ) pristine = this.pristine; /** * We first check if the element should reset. */ if (!element.visible && element.revealed && element.config.reset) { return animate.call(this, element, { reset: true }) } var seq = this.store.sequences[element.sequence.id]; var i = element.sequence.index; if (seq) { var visible = new SequenceModel(seq, 'visible', this.store); var revealed = new SequenceModel(seq, 'revealed', this.store); seq.models = { visible: visible, revealed: revealed }; /** * If the sequence has no revealed members, * then we reveal the first visible element * within that sequence. * * The sequence then cues a recursive call * in both directions. */ if (!revealed.body.length) { var nextId = seq.members[visible.body[0]]; var nextElement = this.store.elements[nextId]; if (nextElement) { cue.call(this, seq, visible.body[0], -1, pristine); cue.call(this, seq, visible.body[0], +1, pristine); return animate.call(this, nextElement, { reveal: true, pristine: pristine }) } } /** * If our element isn’t resetting, we check the * element sequence index against the head, and * then the foot of the sequence. */ if ( !seq.blocked.head && i === [].concat( revealed.head ).pop() && i >= [].concat( visible.body ).shift() ) { cue.call(this, seq, i, -1, pristine); return animate.call(this, element, { reveal: true, pristine: pristine }) } if ( !seq.blocked.foot && i === [].concat( revealed.foot ).shift() && i <= [].concat( visible.body ).pop() ) { cue.call(this, seq, i, +1, pristine); return animate.call(this, element, { reveal: true, pristine: pristine }) } } } function Sequence(interval) { var i = Math.abs(interval); if (!isNaN(i)) { this.id = nextUniqueId(); this.interval = Math.max(i, 16); this.members = []; this.models = {}; this.blocked = { head: false, foot: false }; } else { throw new RangeError('Invalid sequence interval.') } } function SequenceModel(seq, prop, store) { var this$1 = this; this.head = []; this.body = []; this.foot = []; each(seq.members, function (id, index) { var element = store.elements[id]; if (element && element[prop]) { this$1.body.push(index); } }); if (this.body.length) { each(seq.members, function (id, index) { var element = store.elements[id]; if (element && !element[prop]) { if (index < this$1.body[0]) { this$1.head.push(index); } else { this$1.foot.push(index); } } }); } } function cue(seq, i, direction, pristine) { var this$1 = this; var blocked = ['head', null, 'foot'][1 + direction]; var nextId = seq.members[i + direction]; var nextElement = this.store.elements[nextId]; seq.blocked[blocked] = true; setTimeout(function () { seq.blocked[blocked] = false; if (nextElement) { sequence.call(this$1, nextElement, pristine); } }, seq.interval); } function reveal(target, options, syncing) { var this$1 = this; if ( options === void 0 ) options = {}; if ( syncing === void 0 ) syncing = false; var containerBuffer = []; var sequence$$1; var interval = options.interval || defaults.interval; try { if (interval) { sequence$$1 = new Sequence(interval); } var nodes = tealight(target); if (!nodes.length) { throw new Error('Invalid reveal target.') } var elements = nodes.reduce(function (elementBuffer, elementNode) { var element = {}; var existingId = elementNode.getAttribute('data-sr-id'); if (existingId) { deepAssign(element, this$1.store.elements[existingId]); /** * In order to prevent previously generated styles * from throwing off the new styles, the style tag * has to be reverted to its pre-reveal state. */ applyStyle(element.node, element.styles.inline.computed); } else { element.id = nextUniqueId(); element.node = elementNode; element.seen = false; element.revealed = false; element.visible = false; } var config = deepAssign({}, element.config || this$1.defaults, options); if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { if (existingId) { clean.call(this$1, element); } return elementBuffer // skip elements that are disabled } var containerNode = tealight(config.container)[0]; if (!containerNode) { throw new Error('Invalid container.') } if (!containerNode.contains(elementNode)) { return elementBuffer // skip elements found outside the container } var containerId; { containerId = getContainerId( containerNode, containerBuffer, this$1.store.containers ); if (containerId === null) { containerId = nextUniqueId(); containerBuffer.push({ id: containerId, node: containerNode }); } } element.config = config; element.containerId = containerId; element.styles = style(element); if (sequence$$1) { element.sequence = { id: sequence$$1.id, index: sequence$$1.members.length }; sequence$$1.members.push(element.id); } elementBuffer.push(element); return elementBuffer }, []); /** * Modifying the DOM via setAttribute needs to be handled * separately from reading computed styles in the map above * for the browser to batch DOM changes (limiting reflows) */ each(elements, function (element) { this$1.store.elements[element.id] = element; element.node.setAttribute('data-sr-id', element.id); }); } catch (e) { return logger.call(this, 'Reveal failed.', e.message) } /** * Now that element set-up is complete... * Let’s commit any container and sequence data we have to the store. */ each(containerBuffer, function (container) { this$1.store.containers[container.id] = { id: container.id, node: container.node }; }); if (sequence$$1) { this.store.sequences[sequence$$1.id] = sequence$$1; } /** * If reveal wasn't invoked by sync, we want to * make sure to add this call to the history. */ if (syncing !== true) { this.store.history.push({ target: target, options: options }); /** * Push initialization to the event queue, giving * multiple reveal calls time to be interpreted. */ if (this.initTimeout) { window.clearTimeout(this.initTimeout); } this.initTimeout = window.setTimeout(initialize.bind(this), 0); } } function getContainerId(node) { var collections = [], len = arguments.length - 1; while ( len-- > 0 ) collections[ len ] = arguments[ len + 1 ]; var id = null; each(collections, function (collection) { each(collection, function (container) { if (id === null && container.node === node) { id = container.id; } }); }); return id } /** * Re-runs the reveal method for each record stored in history, * for capturing new content asynchronously loaded into the DOM. */ function sync() { var this$1 = this; each(this.store.history, function (record) { reveal.call(this$1, record.target, record.options, true); }); initialize.call(this); } var polyfill = function (x) { return (x > 0) - (x < 0) || +x; }; var mathSign = Math.sign || polyfill; /*! @license miniraf v1.0.1 Copyright 2018 Fisssion LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var polyfill$1 = (function () { var clock = Date.now(); return function (callback) { var currentTime = Date.now(); if (currentTime - clock > 16) { clock = currentTime; callback(currentTime); } else { setTimeout(function () { return polyfill$1(callback); }, 0); } } })(); var miniraf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || polyfill$1; function getGeometry(target, isContainer) { /** * We want to ignore padding and scrollbars for container elements. * More information here: https://goo.gl/vOZpbz */ var height = isContainer ? target.node.clientHeight : target.node.offsetHeight; var width = isContainer ? target.node.clientWidth : target.node.offsetWidth; var offsetTop = 0; var offsetLeft = 0; var node = target.node; do { if (!isNaN(node.offsetTop)) { offsetTop += node.offsetTop; } if (!isNaN(node.offsetLeft)) { offsetLeft += node.offsetLeft; } node = node.offsetParent; } while (node) return { bounds: { top: offsetTop, right: offsetLeft + width, bottom: offsetTop + height, left: offsetLeft }, height: height, width: width } } function getScrolled(container) { var top, left; if (container.node === document.documentElement) { top = window.pageYOffset; left = window.pageXOffset; } else { top = container.node.scrollTop; left = container.node.scrollLeft; } return { top: top, left: left } } function isElementVisible(element) { if ( element === void 0 ) element = {}; var container = this.store.containers[element.containerId]; if (!container) { return } var viewFactor = Math.max(0, Math.min(1, element.config.viewFactor)); var viewOffset = element.config.viewOffset; var elementBounds = { top: element.geometry.bounds.top + element.geometry.height * viewFactor, right: element.geometry.bounds.right - element.geometry.width * viewFactor, bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor, left: element.geometry.bounds.left + element.geometry.width * viewFactor }; var containerBounds = { top: container.geometry.bounds.top + container.scroll.top + viewOffset.top, right: container.geometry.bounds.right + container.scroll.left - viewOffset.right, bottom: container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom, left: container.geometry.bounds.left + container.scroll.left + viewOffset.left }; return ( (elementBounds.top < containerBounds.bottom && elementBounds.right > containerBounds.left && elementBounds.bottom > containerBounds.top && elementBounds.left < containerBounds.right) || element.styles.position === 'fixed' ) } function delegate( event, elements ) { var this$1 = this; if ( event === void 0 ) event = { type: 'init' }; if ( elements === void 0 ) elements = this.store.elements; miniraf(function () { var stale = event.type === 'init' || event.type === 'resize'; each(this$1.store.containers, function (container) { if (stale) { container.geometry = getGeometry.call(this$1, container, true); } var scroll = getScrolled.call(this$1, container); if (container.scroll) { container.direction = { x: mathSign(scroll.left - container.scroll.left), y: mathSign(scroll.top - container.scroll.top) }; } container.scroll = scroll; }); /** * Due to how the sequencer is implemented, it’s * important that we update the state of all * elements, before any animation logic is * evaluated (in the second loop below). */ each(elements, function (element) { if (stale || element.geometry === undefined) { element.geometry = getGeometry.call(this$1, element); } element.visible = isElementVisible.call(this$1, element); }); each(elements, function (element) { if (element.sequence) { sequence.call(this$1, element); } else { animate.call(this$1, element); } }); this$1.pristine = false; }); } function isTransformSupported() { var style = document.documentElement.style; return 'transform' in style || 'WebkitTransform' in style } function isTransitionSupported() { var style = document.documentElement.style; return 'transition' in style || 'WebkitTransition' in style } var version = "4.0.9"; var boundDelegate; var boundDestroy; var boundReveal; var boundClean; var boundSync; var config; var debug; var instance; function ScrollReveal(options) { if ( options === void 0 ) options = {}; var invokedWithoutNew = typeof this === 'undefined' || Object.getPrototypeOf(this) !== ScrollReveal.prototype; if (invokedWithoutNew) { return new ScrollReveal(options) } if (!ScrollReveal.isSupported()) { logger.call(this, 'Instantiation failed.', 'This browser is not supported.'); return mount.failure() } var buffer; try { buffer = config ? deepAssign({}, config, options) : deepAssign({}, defaults, options); } catch (e) { logger.call(this, 'Invalid configuration.', e.message); return mount.failure() } try { var container = tealight(buffer.container)[0]; if (!container) { throw new Error('Invalid container.') } } catch (e) { logger.call(this, e.message); return mount.failure() } config = buffer; if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { logger.call( this, 'This device is disabled.', ("desktop: " + (config.desktop)), ("mobile: " + (config.mobile)) ); return mount.failure() } mount.success(); this.store = { containers: {}, elements: {}, history: [], sequences: {} }; this.pristine = true; boundDelegate = boundDelegate || delegate.bind(this); boundDestroy = boundDestroy || destroy.bind(this); boundReveal = boundReveal || reveal.bind(this); boundClean = boundClean || clean.bind(this); boundSync = boundSync || sync.bind(this); Object.defineProperty(this, 'delegate', { get: function () { return boundDelegate; } }); Object.defineProperty(this, 'destroy', { get: function () { return boundDestroy; } }); Object.defineProperty(this, 'reveal', { get: function () { return boundReveal; } }); Object.defineProperty(this, 'clean', { get: function () { return boundClean; } }); Object.defineProperty(this, 'sync', { get: function () { return boundSync; } }); Object.defineProperty(this, 'defaults', { get: function () { return config; } }); Object.defineProperty(this, 'version', { get: function () { return version; } }); Object.defineProperty(this, 'noop', { get: function () { return false; } }); return instance ? instance : (instance = this) } ScrollReveal.isSupported = function () { return isTransformSupported() && isTransitionSupported(); }; Object.defineProperty(ScrollReveal, 'debug', { get: function () { return debug || false; }, set: function (value) { return (debug = typeof value === 'boolean' ? value : debug); } }); ScrollReveal(); return ScrollReveal; })); ================================================ FILE: package.json ================================================ { "name": "scrollreveal", "version": "4.0.9", "description": "Animate elements as they scroll into view", "homepage": "https://scrollrevealjs.org", "main": "dist/scrollreveal.js", "module": "dist/scrollreveal.es.js", "jsnext:main": "dist/scrollreveal.es.js", "files": [ "dist" ], "scripts": { "prebuild": "rm -rf dist/*", "build": "npm run bundle && npm run bundle:min", "bundle": "./node_modules/rollup/bin/rollup -c ./build/rollup.conf.js", "bundle:min": "./node_modules/rollup/bin/rollup -c ./build/rollup.conf.min.js", "lint": "./node_modules/eslint/bin/eslint.js src test", "pretest": "rm -rf .ignore/coverage/**/ && npm run lint", "test": "./node_modules/karma/bin/karma start ./test/karma.conf.js", "testing": "cross-env COVERAGE=true npm test -- --no-single-run", "coverage": "cross-env COVERAGE=true npm test", "sandbox:bundle": "./node_modules/rollup/bin/rollup -w -c ./.ignore/sandbox/rollup.conf.sandbox.js", "sandbox:server": "node ./.ignore/sandbox/server.sandbox.js", "coverage:server": "node ./.ignore/coverage/server.coverage.js", "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')\"" }, "keywords": [ "scroll", "animation", "reveal", "css", "transform", "transition" ], "repository": { "type": "git", "url": "https://github.com/jlmakes/scrollreveal.git" }, "bugs": { "url": "https://github.com/jlmakes/scrollreveal/issues" }, "dependencies": { "miniraf": "1.0.0", "rematrix": "0.3.0", "tealight": "0.3.6" }, "devDependencies": { "chai": "^4.1.2", "cross-env": "^5.1.3", "eslint": "^4.16.0", "karma": "^2.0.0", "karma-chrome-launcher": "^2.0.0", "karma-coverage": "^1.1.1", "karma-coveralls": "^1.1.2", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", "karma-rollup-preprocessor": "^5.1.1", "karma-sauce-launcher": "^1.1.0", "karma-sinon-chai": "^1.3.3", "live-server": "jlmakes/live-server", "mocha": "^4.0.1", "rollup": "^0.55.0", "rollup-plugin-buble": "^0.x", "rollup-plugin-istanbul": "^1.1.0", "rollup-plugin-json": "^2.1.0", "rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-strip": "^1.1.1", "rollup-plugin-uglify": "^2.0.1", "rollup-watch": "^4.3.1", "sinon": "^4.2.0", "sinon-chai": "^2.8.0" }, "author": "Julian Lloyd", "license": "GPL-3.0" } ================================================ FILE: src/index.js ================================================ import Constructor from './instance/constructor' Constructor() export default Constructor ================================================ FILE: src/instance/constructor.js ================================================ import defaults from './defaults' import mount from './mount' import clean from './methods/clean' import destroy from './methods/destroy' import reveal from './methods/reveal' import sync from './methods/sync' import delegate from './functions/delegate' import isMobile from '../utils/is-mobile' import isTransformSupported from '../utils/is-transform-supported' import isTransitionSupported from '../utils/is-transition-supported' import deepAssign from '../utils/deep-assign' import logger from '../utils/logger' import $ from 'tealight' import { version } from '../../package.json' let boundDelegate let boundDestroy let boundReveal let boundClean let boundSync let config let debug let instance export default function ScrollReveal(options = {}) { const invokedWithoutNew = typeof this === 'undefined' || Object.getPrototypeOf(this) !== ScrollReveal.prototype if (invokedWithoutNew) { return new ScrollReveal(options) } if (!ScrollReveal.isSupported()) { logger.call(this, 'Instantiation failed.', 'This browser is not supported.') return mount.failure() } let buffer try { buffer = config ? deepAssign({}, config, options) : deepAssign({}, defaults, options) } catch (e) { logger.call(this, 'Invalid configuration.', e.message) return mount.failure() } try { const container = $(buffer.container)[0] if (!container) { throw new Error('Invalid container.') } } catch (e) { logger.call(this, e.message) return mount.failure() } config = buffer if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { logger.call( this, 'This device is disabled.', `desktop: ${config.desktop}`, `mobile: ${config.mobile}` ) return mount.failure() } mount.success() this.store = { containers: {}, elements: {}, history: [], sequences: {} } this.pristine = true boundDelegate = boundDelegate || delegate.bind(this) boundDestroy = boundDestroy || destroy.bind(this) boundReveal = boundReveal || reveal.bind(this) boundClean = boundClean || clean.bind(this) boundSync = boundSync || sync.bind(this) Object.defineProperty(this, 'delegate', { get: () => boundDelegate }) Object.defineProperty(this, 'destroy', { get: () => boundDestroy }) Object.defineProperty(this, 'reveal', { get: () => boundReveal }) Object.defineProperty(this, 'clean', { get: () => boundClean }) Object.defineProperty(this, 'sync', { get: () => boundSync }) Object.defineProperty(this, 'defaults', { get: () => config }) Object.defineProperty(this, 'version', { get: () => version }) Object.defineProperty(this, 'noop', { get: () => false }) return instance ? instance : (instance = this) } ScrollReveal.isSupported = () => isTransformSupported() && isTransitionSupported() Object.defineProperty(ScrollReveal, 'debug', { get: () => debug || false, set: value => (debug = typeof value === 'boolean' ? value : debug) }) ================================================ FILE: src/instance/defaults.js ================================================ export default { 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: false, container: document.documentElement, desktop: true, mobile: true, reset: false, useDelay: 'always', viewFactor: 0.0, viewOffset: { top: 0, right: 0, bottom: 0, left: 0 }, afterReset() {}, afterReveal() {}, beforeReset() {}, beforeReveal() {} } ================================================ FILE: src/instance/functions/animate.js ================================================ import { applyStyle } from '../functions/style' import clean from '../methods/clean' export default function animate(element, force = {}) { const pristine = force.pristine || this.pristine const delayed = element.config.useDelay === 'always' || (element.config.useDelay === 'onload' && pristine) || (element.config.useDelay === 'once' && !element.seen) const shouldReveal = element.visible && !element.revealed const shouldReset = !element.visible && element.revealed && element.config.reset if (force.reveal || shouldReveal) { return triggerReveal.call(this, element, delayed) } if (force.reset || shouldReset) { return triggerReset.call(this, element) } } function triggerReveal(element, delayed) { const styles = [ element.styles.inline.generated, element.styles.opacity.computed, element.styles.transform.generated.final ] if (delayed) { styles.push(element.styles.transition.generated.delayed) } else { styles.push(element.styles.transition.generated.instant) } element.revealed = element.seen = true applyStyle(element.node, styles.filter((s) => s !== '').join(' ')) registerCallbacks.call(this, element, delayed) } function triggerReset(element) { const styles = [ element.styles.inline.generated, element.styles.opacity.generated, element.styles.transform.generated.initial, element.styles.transition.generated.instant ] element.revealed = false applyStyle(element.node, styles.filter((s) => s !== '').join(' ')) registerCallbacks.call(this, element) } function registerCallbacks(element, isDelayed) { const duration = isDelayed ? element.config.duration + element.config.delay : element.config.duration const beforeCallback = element.revealed ? element.config.beforeReveal : element.config.beforeReset const afterCallback = element.revealed ? element.config.afterReveal : element.config.afterReset let elapsed = 0 if (element.callbackTimer) { elapsed = Date.now() - element.callbackTimer.start window.clearTimeout(element.callbackTimer.clock) } beforeCallback(element.node) element.callbackTimer = { start: Date.now(), clock: window.setTimeout(() => { afterCallback(element.node) element.callbackTimer = null if (element.revealed && !element.config.reset && element.config.cleanup) { clean.call(this, element.node) } }, duration - elapsed) } } ================================================ FILE: src/instance/functions/delegate.js ================================================ import animate from './animate' import sequence from './sequence' import mathSign from '../../polyfills/math-sign' import raf from 'miniraf' import each from '../../utils/each' import getGeometry from '../../utils/get-geometry' import getScrolled from '../../utils/get-scrolled' import isElementVisible from '../../utils/is-element-visible' export default function delegate( event = { type: 'init' }, elements = this.store.elements ) { raf(() => { const stale = event.type === 'init' || event.type === 'resize' each(this.store.containers, container => { if (stale) { container.geometry = getGeometry.call(this, container, true) } const scroll = getScrolled.call(this, container) if (container.scroll) { container.direction = { x: mathSign(scroll.left - container.scroll.left), y: mathSign(scroll.top - container.scroll.top) } } container.scroll = scroll }) /** * Due to how the sequencer is implemented, it’s * important that we update the state of all * elements, before any animation logic is * evaluated (in the second loop below). */ each(elements, element => { if (stale || element.geometry === undefined) { element.geometry = getGeometry.call(this, element) } element.visible = isElementVisible.call(this, element) }) each(elements, element => { if (element.sequence) { sequence.call(this, element) } else { animate.call(this, element) } }) this.pristine = false }) } ================================================ FILE: src/instance/functions/initialize.js ================================================ import each from '../../utils/each' import { applyStyle } from '../functions/style' import rinse from './rinse' export default function initialize() { rinse.call(this) each(this.store.elements, element => { let styles = [element.styles.inline.generated] if (element.visible) { styles.push(element.styles.opacity.computed) styles.push(element.styles.transform.generated.final) element.revealed = true } else { styles.push(element.styles.opacity.generated) styles.push(element.styles.transform.generated.initial) element.revealed = false } applyStyle(element.node, styles.filter((s) => s !== '').join(' ')) }) each(this.store.containers, container => { const target = container.node === document.documentElement ? window : container.node target.addEventListener('scroll', this.delegate) target.addEventListener('resize', this.delegate) }) /** * Manually invoke delegate once to capture * element and container dimensions, container * scroll position, and trigger any valid reveals */ this.delegate() /** * Wipe any existing `setTimeout` now * that initialization has completed. */ this.initTimeout = null } ================================================ FILE: src/instance/functions/rinse.js ================================================ import $ from 'tealight' import each from '../../utils/each' export default function rinse() { const struct = () => ({ active: [], stale: [] }) const elementIds = struct() const sequenceIds = struct() const containerIds = struct() /** * Take stock of active element IDs. */ try { each($('[data-sr-id]'), node => { const id = parseInt(node.getAttribute('data-sr-id')) elementIds.active.push(id) }) } catch (e) { throw e } /** * Destroy stale elements. */ each(this.store.elements, element => { if (elementIds.active.indexOf(element.id) === -1) { elementIds.stale.push(element.id) } }) each(elementIds.stale, staleId => delete this.store.elements[staleId]) /** * Take stock of active container and sequence IDs. */ each(this.store.elements, element => { if (containerIds.active.indexOf(element.containerId) === -1) { containerIds.active.push(element.containerId) } if (element.hasOwnProperty('sequence')) { if (sequenceIds.active.indexOf(element.sequence.id) === -1) { sequenceIds.active.push(element.sequence.id) } } }) /** * Destroy stale containers. */ each(this.store.containers, container => { if (containerIds.active.indexOf(container.id) === -1) { containerIds.stale.push(container.id) } }) each(containerIds.stale, staleId => { const stale = this.store.containers[staleId].node stale.removeEventListener('scroll', this.delegate) stale.removeEventListener('resize', this.delegate) delete this.store.containers[staleId] }) /** * Destroy stale sequences. */ each(this.store.sequences, sequence => { if (sequenceIds.active.indexOf(sequence.id) === -1) { sequenceIds.stale.push(sequence.id) } }) each(sequenceIds.stale, staleId => delete this.store.sequences[staleId]) } ================================================ FILE: src/instance/functions/sequence.js ================================================ import animate from './animate' import each from '../../utils/each' import nextUniqueId from '../../utils/next-unique-id' export default function sequence(element, pristine = this.pristine) { /** * We first check if the element should reset. */ if (!element.visible && element.revealed && element.config.reset) { return animate.call(this, element, { reset: true }) } const seq = this.store.sequences[element.sequence.id] const i = element.sequence.index if (seq) { const visible = new SequenceModel(seq, 'visible', this.store) const revealed = new SequenceModel(seq, 'revealed', this.store) seq.models = { visible, revealed } /** * If the sequence has no revealed members, * then we reveal the first visible element * within that sequence. * * The sequence then cues a recursive call * in both directions. */ if (!revealed.body.length) { const nextId = seq.members[visible.body[0]] const nextElement = this.store.elements[nextId] if (nextElement) { cue.call(this, seq, visible.body[0], -1, pristine) cue.call(this, seq, visible.body[0], +1, pristine) return animate.call(this, nextElement, { reveal: true, pristine }) } } /** * If our element isn’t resetting, we check the * element sequence index against the head, and * then the foot of the sequence. */ if ( !seq.blocked.head && i === [...revealed.head].pop() && i >= [...visible.body].shift() ) { cue.call(this, seq, i, -1, pristine) return animate.call(this, element, { reveal: true, pristine }) } if ( !seq.blocked.foot && i === [...revealed.foot].shift() && i <= [...visible.body].pop() ) { cue.call(this, seq, i, +1, pristine) return animate.call(this, element, { reveal: true, pristine }) } } } export function Sequence(interval) { const i = Math.abs(interval) if (!isNaN(i)) { this.id = nextUniqueId() this.interval = Math.max(i, 16) this.members = [] this.models = {} this.blocked = { head: false, foot: false } } else { throw new RangeError('Invalid sequence interval.') } } function SequenceModel(seq, prop, store) { this.head = [] this.body = [] this.foot = [] each(seq.members, (id, index) => { const element = store.elements[id] if (element && element[prop]) { this.body.push(index) } }) if (this.body.length) { each(seq.members, (id, index) => { const element = store.elements[id] if (element && !element[prop]) { if (index < this.body[0]) { this.head.push(index) } else { this.foot.push(index) } } }) } } function cue(seq, i, direction, pristine) { const blocked = ['head', null, 'foot'][1 + direction] const nextId = seq.members[i + direction] const nextElement = this.store.elements[nextId] seq.blocked[blocked] = true setTimeout(() => { seq.blocked[blocked] = false if (nextElement) { sequence.call(this, nextElement, pristine) } }, seq.interval) } ================================================ FILE: src/instance/functions/style.js ================================================ import { multiply, parse, rotateX, rotateY, rotateZ, scale, translateX, translateY } from 'rematrix' import getPrefixedCssProp from '../../utils/get-prefixed-css-prop' export default function style(element) { const computed = window.getComputedStyle(element.node) const position = computed.position const config = element.config /** * Generate inline styles */ const inline = {} const inlineStyle = element.node.getAttribute('style') || '' const inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || [] inline.computed = inlineMatch ? inlineMatch.map(m => m.trim()).join('; ') + ';' : '' inline.generated = inlineMatch.some(m => m.match(/visibility\s?:\s?visible/i)) ? inline.computed : [...inlineMatch, 'visibility: visible'].map(m => m.trim()).join('; ') + ';' /** * Generate opacity styles */ const computedOpacity = parseFloat(computed.opacity) const configOpacity = !isNaN(parseFloat(config.opacity)) ? parseFloat(config.opacity) : parseFloat(computed.opacity) const opacity = { computed: computedOpacity !== configOpacity ? `opacity: ${computedOpacity};` : '', generated: computedOpacity !== configOpacity ? `opacity: ${configOpacity};` : '' } /** * Generate transformation styles */ const transformations = [] if (parseFloat(config.distance)) { const axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X' /** * Let’s make sure our our pixel distances are negative for top and left. * e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS. */ let distance = config.distance if (config.origin === 'top' || config.origin === 'left') { distance = /^-/.test(distance) ? distance.substr(1) : `-${distance}` } const [value, unit] = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g) switch (unit) { case 'em': distance = parseInt(computed.fontSize) * value break case 'px': distance = value break case '%': /** * Here we use `getBoundingClientRect` instead of * the existing data attached to `element.geometry` * because only the former includes any transformations * current applied to the element. * * If that behavior ends up being unintuitive, this * logic could instead utilize `element.geometry.height` * and `element.geoemetry.width` for the distance calculation */ distance = axis === 'Y' ? (element.node.getBoundingClientRect().height * value) / 100 : (element.node.getBoundingClientRect().width * value) / 100 break default: throw new RangeError('Unrecognized or missing distance unit.') } if (axis === 'Y') { transformations.push(translateY(distance)) } else { transformations.push(translateX(distance)) } } if (config.rotate.x) transformations.push(rotateX(config.rotate.x)) if (config.rotate.y) transformations.push(rotateY(config.rotate.y)) if (config.rotate.z) transformations.push(rotateZ(config.rotate.z)) if (config.scale !== 1) { if (config.scale === 0) { /** * The CSS Transforms matrix interpolation specification * basically disallows transitions of non-invertible * matrixes, which means browsers won't transition * elements with zero scale. * * That’s inconvenient for the API and developer * experience, so we simply nudge their value * slightly above zero; this allows browsers * to transition our element as expected. * * `0.0002` was the smallest number * that performed across browsers. */ transformations.push(scale(0.0002)) } else { transformations.push(scale(config.scale)) } } const transform = {} if (transformations.length) { transform.property = getPrefixedCssProp('transform') /** * The default computed transform value should be one of: * undefined || 'none' || 'matrix()' || 'matrix3d()' */ transform.computed = { raw: computed[transform.property], matrix: parse(computed[transform.property]) } transformations.unshift(transform.computed.matrix) const product = transformations.reduce(multiply) transform.generated = { initial: `${transform.property}: matrix3d(${product.join(', ')});`, final: `${transform.property}: matrix3d(${transform.computed.matrix.join(', ')});` } } else { transform.generated = { initial: '', final: '' } } /** * Generate transition styles */ let transition = {} if (opacity.generated || transform.generated.initial) { transition.property = getPrefixedCssProp('transition') transition.computed = computed[transition.property] transition.fragments = [] const { delay, duration, easing } = config if (opacity.generated) { transition.fragments.push({ delayed: `opacity ${duration / 1000}s ${easing} ${delay / 1000}s`, instant: `opacity ${duration / 1000}s ${easing} 0s` }) } if (transform.generated.initial) { transition.fragments.push({ delayed: `${transform.property} ${duration / 1000}s ${easing} ${delay / 1000}s`, instant: `${transform.property} ${duration / 1000}s ${easing} 0s` }) } /** * The default computed transition property should be undefined, or one of: * '' || 'none 0s ease 0s' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()' */ let hasCustomTransition = transition.computed && !transition.computed.match(/all 0s|none 0s/) if (hasCustomTransition) { transition.fragments.unshift({ delayed: transition.computed, instant: transition.computed }) } const composed = transition.fragments.reduce( (composition, fragment, i) => { composition.delayed += i === 0 ? fragment.delayed : `, ${fragment.delayed}` composition.instant += i === 0 ? fragment.instant : `, ${fragment.instant}` return composition }, { delayed: '', instant: '' } ) transition.generated = { delayed: `${transition.property}: ${composed.delayed};`, instant: `${transition.property}: ${composed.instant};` } } else { transition.generated = { delayed: '', instant: '' } } return { inline, opacity, position, transform, transition } } /** * apply a CSS string to an element using the CSSOM (element.style) rather * than setAttribute, which may violate the content security policy. * * @param {Node} [el] Element to receive styles. * @param {string} [declaration] Styles to apply. */ export function applyStyle (el, declaration) { declaration.split(';').forEach(pair => { const [property, ...value] = pair.split(':') if (property && value) { el.style[property.trim()] = value.join(':') } }) } ================================================ FILE: src/instance/methods/clean.js ================================================ import $ from 'tealight' import each from '../../utils/each' import logger from '../../utils/logger' import rinse from '../functions/rinse' import { applyStyle } from '../functions/style' export default function clean(target) { let dirty try { each($(target), node => { const id = node.getAttribute('data-sr-id') if (id !== null) { dirty = true const element = this.store.elements[id] if (element.callbackTimer) { window.clearTimeout(element.callbackTimer.clock) } applyStyle(element.node, element.styles.inline.generated) node.removeAttribute('data-sr-id') delete this.store.elements[id] } }) } catch (e) { return logger.call(this, 'Clean failed.', e.message) } if (dirty) { try { rinse.call(this) } catch (e) { return logger.call(this, 'Clean failed.', e.message) } } } ================================================ FILE: src/instance/methods/destroy.js ================================================ import each from '../../utils/each' import { applyStyle } from '../functions/style' export default function destroy() { /** * Remove all generated styles and element ids */ each(this.store.elements, element => { applyStyle(element.node, element.styles.inline.generated) element.node.removeAttribute('data-sr-id') }) /** * Remove all event listeners. */ each(this.store.containers, container => { const target = container.node === document.documentElement ? window : container.node target.removeEventListener('scroll', this.delegate) target.removeEventListener('resize', this.delegate) }) /** * Clear all data from the store */ this.store = { containers: {}, elements: {}, history: [], sequences: {} } } ================================================ FILE: src/instance/methods/reveal.js ================================================ import tealight from 'tealight' import deepAssign from '../../utils/deep-assign' import each from '../../utils/each' import isMobile from '../../utils/is-mobile' import logger from '../../utils/logger' import nextUniqueId from '../../utils/next-unique-id' import defaults from '../defaults' import initialize from '../functions/initialize' import { Sequence } from '../functions/sequence' import style, { applyStyle } from '../functions/style' import clean from '../methods/clean' export default function reveal(target, options = {}, syncing = false) { const containerBuffer = [] let sequence let interval = options.interval || defaults.interval try { if (interval) { sequence = new Sequence(interval) } const nodes = tealight(target) if (!nodes.length) { throw new Error('Invalid reveal target.') } const elements = nodes.reduce((elementBuffer, elementNode) => { const element = {} const existingId = elementNode.getAttribute('data-sr-id') if (existingId) { deepAssign(element, this.store.elements[existingId]) /** * In order to prevent previously generated styles * from throwing off the new styles, the style tag * has to be reverted to its pre-reveal state. */ applyStyle(element.node, element.styles.inline.computed) } else { element.id = nextUniqueId() element.node = elementNode element.seen = false element.revealed = false element.visible = false } const config = deepAssign({}, element.config || this.defaults, options) if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) { if (existingId) { clean.call(this, element) } return elementBuffer // skip elements that are disabled } const containerNode = tealight(config.container)[0] if (!containerNode) { throw new Error('Invalid container.') } if (!containerNode.contains(elementNode)) { return elementBuffer // skip elements found outside the container } let containerId { containerId = getContainerId( containerNode, containerBuffer, this.store.containers ) if (containerId === null) { containerId = nextUniqueId() containerBuffer.push({ id: containerId, node: containerNode }) } } element.config = config element.containerId = containerId element.styles = style(element) if (sequence) { element.sequence = { id: sequence.id, index: sequence.members.length } sequence.members.push(element.id) } elementBuffer.push(element) return elementBuffer }, []) /** * Modifying the DOM via setAttribute needs to be handled * separately from reading computed styles in the map above * for the browser to batch DOM changes (limiting reflows) */ each(elements, element => { this.store.elements[element.id] = element element.node.setAttribute('data-sr-id', element.id) }) } catch (e) { return logger.call(this, 'Reveal failed.', e.message) } /** * Now that element set-up is complete... * Let’s commit any container and sequence data we have to the store. */ each(containerBuffer, container => { this.store.containers[container.id] = { id: container.id, node: container.node } }) if (sequence) { this.store.sequences[sequence.id] = sequence } /** * If reveal wasn't invoked by sync, we want to * make sure to add this call to the history. */ if (syncing !== true) { this.store.history.push({ target, options }) /** * Push initialization to the event queue, giving * multiple reveal calls time to be interpreted. */ if (this.initTimeout) { window.clearTimeout(this.initTimeout) } this.initTimeout = window.setTimeout(initialize.bind(this), 0) } } function getContainerId(node, ...collections) { let id = null each(collections, collection => { each(collection, container => { if (id === null && container.node === node) { id = container.id } }) }) return id } ================================================ FILE: src/instance/methods/sync.js ================================================ import initialize from '../functions/initialize' import each from '../../utils/each' import reveal from './reveal' /** * Re-runs the reveal method for each record stored in history, * for capturing new content asynchronously loaded into the DOM. */ export default function sync() { each(this.store.history, record => { reveal.call(this, record.target, record.options, true) }) initialize.call(this) } ================================================ FILE: src/instance/mount.js ================================================ function failure() { document.documentElement.classList.remove('sr') return { clean() {}, destroy() {}, reveal() {}, sync() {}, get noop() { return true } } } function success() { document.documentElement.classList.add('sr') if (document.body) { document.body.style.height = '100%' } else { document.addEventListener('DOMContentLoaded', () => { document.body.style.height = '100%' }) } } export default { success, failure } ================================================ FILE: src/polyfills/math-sign.js ================================================ export const polyfill = x => (x > 0) - (x < 0) || +x export default Math.sign || polyfill ================================================ FILE: src/utils/deep-assign.js ================================================ import isObject from './is-object' import each from './each' export default function deepAssign(target, ...sources) { if (isObject(target)) { each(sources, source => { each(source, (data, key) => { if (isObject(data)) { if (!target[key] || !isObject(target[key])) { target[key] = {} } deepAssign(target[key], data) } else { target[key] = data } }) }) return target } else { throw new TypeError('Target must be an object literal.') } } ================================================ FILE: src/utils/each.js ================================================ import isObject from './is-object' export default function each(collection, callback) { if (isObject(collection)) { const keys = Object.keys(collection) return keys.forEach(key => callback(collection[key], key, collection)) } if (collection instanceof Array) { return collection.forEach((item, i) => callback(item, i, collection)) } throw new TypeError('Expected either an array or object literal.') } ================================================ FILE: src/utils/get-geometry.js ================================================ export default function getGeometry(target, isContainer) { /** * We want to ignore padding and scrollbars for container elements. * More information here: https://goo.gl/vOZpbz */ const height = isContainer ? target.node.clientHeight : target.node.offsetHeight const width = isContainer ? target.node.clientWidth : target.node.offsetWidth let offsetTop = 0 let offsetLeft = 0 let node = target.node do { if (!isNaN(node.offsetTop)) { offsetTop += node.offsetTop } if (!isNaN(node.offsetLeft)) { offsetLeft += node.offsetLeft } node = node.offsetParent } while (node) return { bounds: { top: offsetTop, right: offsetLeft + width, bottom: offsetTop + height, left: offsetLeft }, height, width } } ================================================ FILE: src/utils/get-prefixed-css-prop.js ================================================ const getPrefixedCssProp = (() => { let properties = {} const style = document.documentElement.style function getPrefixedCssProperty(name, source = style) { if (name && typeof name === 'string') { if (properties[name]) { return properties[name] } if (typeof source[name] === 'string') { return (properties[name] = name) } if (typeof source[`-webkit-${name}`] === 'string') { return (properties[name] = `-webkit-${name}`) } throw new RangeError(`Unable to find "${name}" style property.`) } throw new TypeError('Expected a string.') } getPrefixedCssProperty.clearCache = () => (properties = {}) return getPrefixedCssProperty })() export default getPrefixedCssProp ================================================ FILE: src/utils/get-scrolled.js ================================================ export default function getScrolled(container) { let top, left if (container.node === document.documentElement) { top = window.pageYOffset left = window.pageXOffset } else { top = container.node.scrollTop left = container.node.scrollLeft } return { top, left } } ================================================ FILE: src/utils/is-element-visible.js ================================================ export default function isElementVisible(element = {}) { const container = this.store.containers[element.containerId] if (!container) return const viewFactor = Math.max(0, Math.min(1, element.config.viewFactor)) const viewOffset = element.config.viewOffset const elementBounds = { top: element.geometry.bounds.top + element.geometry.height * viewFactor, right: element.geometry.bounds.right - element.geometry.width * viewFactor, bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor, left: element.geometry.bounds.left + element.geometry.width * viewFactor } const containerBounds = { top: container.geometry.bounds.top + container.scroll.top + viewOffset.top, right: container.geometry.bounds.right + container.scroll.left - viewOffset.right, bottom: container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom, left: container.geometry.bounds.left + container.scroll.left + viewOffset.left } return ( (elementBounds.top < containerBounds.bottom && elementBounds.right > containerBounds.left && elementBounds.bottom > containerBounds.top && elementBounds.left < containerBounds.right) || element.styles.position === 'fixed' ) } ================================================ FILE: src/utils/is-mobile.js ================================================ export default function isMobile(agent = navigator.userAgent) { return /Android|iPhone|iPad|iPod/i.test(agent) } ================================================ FILE: src/utils/is-object.js ================================================ export default function isObject(x) { return ( x !== null && x instanceof Object && (x.constructor === Object || Object.prototype.toString.call(x) === '[object Object]') ) } ================================================ FILE: src/utils/is-transform-supported.js ================================================ export default function isTransformSupported() { const style = document.documentElement.style return 'transform' in style || 'WebkitTransform' in style } ================================================ FILE: src/utils/is-transition-supported.js ================================================ export default function isTransitionSupported() { const style = document.documentElement.style return 'transition' in style || 'WebkitTransition' in style } ================================================ FILE: src/utils/logger.js ================================================ export default function logger(message, ...details) { if (this.constructor.debug && console) { let report = `%cScrollReveal: ${message}` details.forEach(detail => (report += `\n — ${detail}`)) console.log(report, 'color: #ea654b;') // eslint-disable-line no-console } } ================================================ FILE: src/utils/next-unique-id.js ================================================ const nextUniqueId = (() => { let uid = 0 return () => uid++ })() export default nextUniqueId ================================================ FILE: test/instance/constructor.spec.js ================================================ import ScrollReveal from '../../src/instance/constructor' import isMobile from '../../src/utils/is-mobile' import { version } from '../../package.json' describe('ScrollReveal', () => { describe('Constructor', () => { it('should return a new instance with `new` keyword', () => { const sr = new ScrollReveal() expect(sr).to.exist }) it('should return a new instance without `new` keyword', () => { const sr = ScrollReveal() expect(sr).to.exist }) it('should add the class `sr` to `` element', () => { document.documentElement.classList.remove('sr') ScrollReveal() const result = document.documentElement.classList.contains('sr') expect(result).to.be.true }) it('should add `height: 100%` to `` element', () => { document.body.style.height = 'auto' ScrollReveal() const result = document.body.style.height === '100%' expect(result).to.be.true }) it('should return a noop instance when not supported', () => { const stubs = [ sinon.stub(console, 'log'), sinon.stub(ScrollReveal, 'isSupported') ] const sr = ScrollReveal() stubs.forEach(stub => stub.restore()) expect(sr.noop).to.be.true }) it('should return a noop instance when device is disabled', () => { isMobile() ? expect(ScrollReveal({ mobile: false }).noop).to.be.true : expect(ScrollReveal({ desktop: false }).noop).to.be.true ScrollReveal({ desktop: true, mobile: true }) }) it('should return a noop instance when container is invalid', () => { const stub = sinon.stub(console, 'log') const sr = ScrollReveal({ container: null }) stub.restore() expect(sr.noop).to.be.true }) it('should return a noop instance when passed non-object options', () => { const stub = sinon.stub(console, 'log') let sr { sr = ScrollReveal(null) expect(sr.noop).to.be.true sr = ScrollReveal('foo') expect(sr.noop).to.be.true } stub.restore() }) it('should return a singleton', () => { const A = ScrollReveal() const B = ScrollReveal() expect(A === B).to.be.true }) it('should not update the defaults when re-invoked with invalid options', () => { ScrollReveal({ duration: 1000 }) ScrollReveal(null) expect(ScrollReveal().defaults.duration).to.equal(1000) }) it('should update the defaults when re-invoked with valid options', () => { ScrollReveal({ duration: 1000 }) ScrollReveal({ duration: 5000 }) expect(ScrollReveal().defaults.duration).to.equal(5000) }) it('should have a static `debug` property', () => { expect(ScrollReveal.debug).to.exist expect(ScrollReveal.debug).to.be.a('boolean') }) it('should accept boolean value for static `debug` property', () => { ScrollReveal.debug = true expect(ScrollReveal.debug).to.be.true }) it('should ignore non-boolean values assigned to static `debug` property', () => { ScrollReveal.debug = null expect(ScrollReveal.debug).to.exist expect(ScrollReveal.debug).to.be.a('boolean') }) }) describe('Instance', () => { const sr = new ScrollReveal() it('should have a `clean` method', () => { expect(sr.clean).to.exist expect(sr.clean).to.be.a('function') }) it('should have a `destroy` method', () => { expect(sr.destroy).to.exist expect(sr.destroy).to.be.a('function') }) it('should have a `reveal` method', () => { expect(sr.reveal).to.exist expect(sr.reveal).to.be.a('function') }) it('should have a `sync` method', () => { expect(sr.sync).to.exist expect(sr.sync).to.be.a('function') }) it('should have a `delegate` property', () => { expect(sr.delegate).to.exist expect(sr.delegate).to.be.a('function') }) it('should have a `version` property', () => { expect(sr.version).to.exist expect(sr.version).to.be.equal(version) }) it('should have a `noop` property set to `false`', () => { expect(sr.noop).to.exist expect(sr.noop).to.be.false }) }) describe('Non-operational Instance', () => { const stubs = [ sinon.stub(console, 'log'), sinon.stub(ScrollReveal, 'isSupported') ] const sr = ScrollReveal() stubs.forEach(stub => stub.restore()) it('should have a `clean` method', () => { expect(sr.clean).to.exist expect(sr.clean).to.be.a('function') }) it('should have a `destroy` method', () => { expect(sr.destroy).to.exist expect(sr.destroy).to.be.a('function') }) it('should have a `reveal` method', () => { expect(sr.reveal).to.exist expect(sr.reveal).to.be.a('function') }) it('should have a `sync` method', () => { expect(sr.sync).to.exist expect(sr.sync).to.be.a('function') }) it('should have a `noop` property set to `true`', () => { expect(sr.noop).to.exist expect(sr.noop).to.be.true }) }) }) ================================================ FILE: test/karma.conf.js ================================================ const rollupPlugins = [ require('rollup-plugin-json')(), require('rollup-plugin-node-resolve')({ jsnext: true, main: true }), require('rollup-plugin-buble')() ] if (process.env.COVERAGE) { rollupPlugins.push( require('rollup-plugin-istanbul')({ exclude: [ '../package.json', '../src/index.js', './**/*.spec.js', '**/node_modules/**' ], instrumenterConfig: { embedSource: true } }) ) } module.exports = function(karma) { karma.set({ frameworks: ['mocha', 'sinon-chai'], preprocessors: { './**/*.spec.js': ['rollup'] }, files: [{ pattern: './**/*.spec.js', watched: false }], rollupPreprocessor: { plugins: rollupPlugins, output: { format: 'iife', name: 'ScrollReveal', sourcemap: 'inline' } }, colors: true, concurrency: 10, logLevel: karma.LOG_ERROR, singleRun: true, browserDisconnectTolerance: 1, browserDisconnectTimeout: 60 * 1000, browserNoActivityTimeout: 60 * 1000, // browserNoActivityTimeout: 60 * 1000 * 10 * 6, // dev tools debugging captureTimeout: 4 * 60 * 1000 }) if (process.env.TRAVIS) { if (process.env.COVERAGE) { karma.set({ autoWatch: false, browsers: ['ChromeHeadless'], coverageReporter: { type: 'lcovonly', dir: 'coverage/' }, reporters: ['mocha', 'coverage', 'coveralls'] }) } else { const customLaunchers = require('./sauce.conf') karma.set({ autoWatch: false, browsers: Object.keys(customLaunchers), customLaunchers, reporters: ['dots', 'saucelabs'], hostname: 'localsauce', sauceLabs: { testName: 'ScrollReveal', build: process.env.TRAVIS_BUILD_NUMBER || 'manual', tunnelIdentifier: process.env.TRAVIS_BUILD_NUMBER || 'autoGeneratedTunnelID', recordVideo: true, connectOptions: { tunnelDomains: 'localsauce' // because Android 8 has an SSL error? } } }) } } else { karma.set({ browsers: ['ChromeHeadless'], // browsers: ['Chrome'], // dev tools debugging coverageReporter: { type: 'lcov', dir: '../.ignore/coverage/' }, reporters: ['mocha', 'coverage'] }) } } ================================================ FILE: test/polyfills/math-sign.spec.js ================================================ import { polyfill } from '../../src/polyfills/math-sign' describe('Polyfills', () => { describe('mathSign()', () => { it('should be a function', () => { expect(polyfill).to.be.a('function') }) it('should return -1 when passed values smaller than 0', () => { expect(polyfill(-500)).to.equal(-1) }) it('should return 1 when passed values larger than 0', () => { expect(polyfill(500)).to.equal(1) }) it('should return 1 when passed true', () => { expect(polyfill(true)).to.equal(1) }) it('should return -0 when passed -0', () => { expect(polyfill(-0)).to.equal(-0) }) it('should return 0 when passed 0', () => { expect(polyfill(0)).to.equal(0) }) it('should return 0 when passed falsey values', () => { expect(polyfill(false)).to.equal(0) expect(polyfill('')).to.equal(0) expect(polyfill([])).to.equal(0) expect(polyfill(null)).to.equal(0) }) it('should return NaN when passed non-falsey non-numbers', () => { expect(polyfill('foo')).to.be.NaN expect(polyfill({})).to.be.NaN expect(polyfill([1, 2, 3])).to.be.NaN }) }) }) ================================================ FILE: test/sauce.conf.js ================================================ let launchers = {} let mobileLaunchers = [ ['iOS', '10.3', 'Safari', 'iPhone 7 Simulator', '1.9.1'], ['iOS', '11.3', 'Safari', 'iPhone 7 Simulator', '1.9.1'], ['iOS', '12.2', 'Safari', 'iPhone 7 Simulator', '1.13.0'], ['iOS', '13.0', 'Safari', 'iPhone 7 Simulator', '1.15.0'], ['Android', '5.1', 'Browser', 'Android Emulator', '1.15.0'], ['Android', '6.0', 'Chrome', 'Android Emulator', '1.15.0'], ['Android', '8.0', 'Chrome', 'Android Emulator', '1.15.0'] ] for (let [platform, version, browser, device, appium] of mobileLaunchers) { let launcher = `sl_${platform}_${version}_${browser}` .replace(/[^a-z0-9]/gi, '_') .toLowerCase() launchers[launcher] = { name: `${browser}, ${platform} ${version}`, platformName: platform, platformVersion: version, browserName: browser, deviceName: device, deviceOrientation: 'portrait', appiumVersion: appium } } let desktopLaunchers = [ ['Windows 8.1', 'Internet Explorer', '11.0'], ['Windows 8', 'Internet Explorer', '10.0'], ['macOS 10.12', 'Safari', '11.0'], ['OS X 10.11', 'Safari', '10.0'], ['OS X 10.11', 'Safari', '9.0'] ] for (let [platform, browser, version] of desktopLaunchers) { let launcher = `sl_${platform}_${browser}_${version}` .replace(/[^a-z0-9]/gi, '_') .toLowerCase() launchers[launcher] = { name: `${browser} ${version}, ${platform}`, browserName: browser, version, platform } } for (let browser of ['Chrome', 'Firefox', 'MicrosoftEdge']) { let pastVersions = 3 do { pastVersions-- let postfix = pastVersions > 0 ? `-${pastVersions}` : '' let version = 'latest' + postfix let browserName = browser === 'MicrosoftEdge' ? 'Edge' : browser let launcher = `sl_win10_${browser}_latest${postfix}`.replace(/-/g, '_').toLowerCase() launchers[launcher] = { name: `${browserName} ${version}, Windows 10`, browserName: browser, version, platform: 'Windows 10' } } while (pastVersions) } for (let launcher in launchers) { launchers[launcher].base = 'SauceLabs' } module.exports = launchers ================================================ FILE: test/timeout.spec.js ================================================ // describe('suite delay for DOM inspection', function () { // it('should delay by 10 minutes', function (done) { // document.documentElement.style = 'background-color: #eee; height: 100%' // const time = 1000 * 60 * 10 * 6 // this.timeout(time) // setTimeout(done, time - 500) // }) // }) ================================================ FILE: test/utils/deep-assign.spec.js ================================================ import deepAssign from '../../src/utils/deep-assign' describe('Utilities', () => { describe('deepAssign()', () => { it('should assign source values to target object', () => { const target = { foo: 'bar', bun: 'baz' } const source = { foo: 'bonk!', bif: 'baff' } const goal = { foo: 'bonk!', bun: 'baz', bif: 'baff' } deepAssign(target, source) expect(target).to.deep.equal(goal) }) it('should assign nested source values to target object', () => { // each property tests a // different execution path const target = { foo: 'initial', bar: 'initial', kel: { pow: 'pop' }, zad: null } const source = { foo: 'bonk!', bar: { baz: 'baff' }, kel: { pow: 'lol' }, zad: { min: 'max' } } const goal = { foo: 'bonk!', bar: { baz: 'baff' }, kel: { pow: 'lol' }, zad: { min: 'max' } } deepAssign(target, source) expect(target).to.deep.equal(goal) }) it('should accept multiple sources', () => { const target = { foo: 'bar', bun: 'baz' } const source1 = { foo: 'bonk!', bif: 'baff' } const source2 = { foo: 'pow!' } const goal = { foo: 'pow!', bun: 'baz', bif: 'baff' } deepAssign(target, source1, source2) expect(target).to.deep.equal(goal) }) it('should throw a type error when not passed an object literal', () => { let caught try { deepAssign(null, null) } catch (error) { caught = error } expect(caught).to.exist expect(caught).to.be.an.instanceof(TypeError) }) }) }) ================================================ FILE: test/utils/each.spec.js ================================================ import each from '../../src/utils/each' describe('Utilities', () => { describe('each()', () => { function Fixture() { this.foo = 'bar' this.baz = 'bun' } describe('if passed an object literal...', () => { it('should invoke callback for each property', () => { const fixture = new Fixture() const spy = sinon.spy() each(fixture, spy) expect(spy).to.have.been.calledTwice }) it('should ignore properties on the prototype chain', () => { Fixture.prototype.biff = 'baff' const fixture = new Fixture() const spy = sinon.spy() each(fixture, spy) expect(spy).to.have.been.calledTwice }) it('should pass the value, key and collection to the callback', () => { const fixture = new Fixture() let _value, _key, _collection each(fixture, (value, key, collection) => { _value = value _key = key _collection = collection }) expect(_value).to.equal('bun') expect(_key).to.equal('baz') expect(_collection).to.deep.equal(fixture) }) }) describe('if passed an array...', () => { const fixture = ['apple', 'orange', 'banana'] it('should invoke callback for each value', () => { const spy = sinon.spy() each(fixture, spy) expect(spy).to.have.been.calledThrice }) it('should pass the value, index and collection to the callback', () => { let _value, _index, _collection each(fixture, (value, index, collection) => { _value = value _index = index _collection = collection }) expect(_value).to.equal('banana') expect(_index).to.equal(2) expect(_collection).to.deep.equal(fixture) }) }) describe('else', () => { it('should throw a type error when passed an invalid collection', () => { let caught try { each(null, () => {}) } catch (error) { caught = error } expect(caught).to.exist expect(caught).to.be.an.instanceof(TypeError) }) }) }) }) ================================================ FILE: test/utils/get-prefixed-css-prop.spec.js ================================================ import getPrefixedCssProp from '../../src/utils/get-prefixed-css-prop' describe('Utilities', () => { describe('getPrefixedCssProp()', () => { beforeEach('clear cache', () => { getPrefixedCssProp.clearCache() }) it('should return unprefixed properties before prefixed', () => { const source = { transform: '', '-webkit-transform': '' } const result = getPrefixedCssProp('transform', source) expect(result).to.equal('transform') }) it('should return prefixed property names', () => { const source = { '-webkit-transform': '' } const result = getPrefixedCssProp('transform', source) expect(result).to.equal('-webkit-transform') }) it('should return property names from cache when available', () => { const source = { '-webkit-transform': '' } getPrefixedCssProp('transform', source) const result = getPrefixedCssProp('transform', {}) expect(result).to.equal('-webkit-transform') }) it('should throw a range error when no property is found', () => { let caught try { getPrefixedCssProp('transform', {}) } catch (error) { caught = error } expect(caught).to.exist expect(caught).to.be.an.instanceof(RangeError) }) it('should throw a type error if not passed a string', () => { let caught try { getPrefixedCssProp(null) } catch (error) { caught = error } expect(caught).to.exist expect(caught).to.be.an.instanceof(TypeError) }) }) }) ================================================ FILE: test/utils/is-mobile.spec.js ================================================ import isMobile from '../../src/utils/is-mobile' describe('Utilities', () => { describe('isMobile()', () => { it('should return true when passed a mobile user agent', () => { const android = `Mozilla/5.0 (Linux; U; Android 4.2; en-us; Android SDK built for x86 Build/JOP40C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` const iPhone = `Mozilla/5.0 (iPhone; CPU iPhone OS 10_10_5 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B411 Safari/600.1.4` expect(isMobile(android)).to.be.true expect(isMobile(iPhone)).to.be.true }) it('should return false when passed a desktop user agent', () => { const chrome = `Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36` const firefox = 'Mozilla/5.0 (X11; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0' const ie10 = `Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E)` expect(isMobile(chrome)).to.be.false expect(isMobile(firefox)).to.be.false expect(isMobile(ie10)).to.be.false }) it('should work when not passed an explicit user agent', () => { expect(isMobile()).to.be.a('boolean') }) }) }) ================================================ FILE: test/utils/is-object.spec.js ================================================ import isObject from '../../src/utils/is-object' describe('Utilities', () => { describe('isObject()', () => { it('should return true when passed an object literal', () => { const result = isObject({}) expect(result).to.be.true }) it('should return false when passed a function', () => { const result = isObject(() => {}) expect(result).to.be.false }) it('should return false when passed an array', () => { const result = isObject([]) expect(result).to.be.false }) it('should return false when passed null', () => { const result = isObject(null) expect(result).to.be.false }) it('should return false when passed undefined', () => { const result = isObject(undefined) expect(result).to.be.false }) }) }) ================================================ FILE: test/utils/is-transform-supported.spec.js ================================================ import isTransformSupported from '../../src/utils/is-transform-supported' describe('Utilities', () => { describe('isTransformSupported()', () => { it('should return true', () => { expect(isTransformSupported()).to.be.true }) }) }) ================================================ FILE: test/utils/is-transition-supported.spec.js ================================================ import isTransitionSupported from '../../src/utils/is-transition-supported' describe('Utilities', () => { describe('isTransitionSupported()', () => { it('should return true', () => { expect(isTransitionSupported()).to.be.true }) }) }) ================================================ FILE: test/utils/logger.spec.js ================================================ import logger from '../../src/utils/logger' describe('Utilities', () => { describe('logger()', () => { const mock = { constructor: { debug: true } } let spy let stub beforeEach('stub console log', () => { spy = sinon.spy() stub = sinon.stub(console, 'log').callsFake(spy) }) it('should invoke console.log', () => { logger.call(mock) expect(spy).to.have.been.called }) it('should prepend output with `ScrollReveal: `', () => { logger.call(mock, 'test') const result = '%cScrollReveal: test' const style = 'color: #ea654b;' expect(spy).to.have.been.calledWith(result, style) }) it('should accept multiple arguments as message details', () => { logger.call(mock, 'message', 'detail one', 'detail two') const result = '%cScrollReveal: message\n — detail one\n — detail two' const style = 'color: #ea654b;' expect(spy).to.have.been.calledWith(result, style) }) afterEach('restore console log', () => stub.restore()) }) }) ================================================ FILE: test/utils/next-unique-id.spec.js ================================================ import nextUniqueId from '../../src/utils/next-unique-id' describe('Utilities', () => { describe('nextUniqueId()', () => { it('should start at 0', () => { const result = nextUniqueId() expect(result).to.equal(0) }) it('should increment by 1', () => { const result = nextUniqueId() expect(result).to.equal(1) }) it('should return a number', () => { const result = nextUniqueId() expect(result).to.be.a('number') }) }) })