├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .prettierrc
├── .spmignore
├── LICENSE
├── README.md
├── bower.json
├── demos
├── divInADiv.html
├── fixed.html
├── list.html
├── listInDiv.html
├── listdata.json
├── reloadTest.html
├── scoreboard.html
├── stress.html
├── stressTestInDiv.html
└── vh-units.html
├── dist
├── module
│ ├── index.d.ts
│ ├── index.js
│ ├── index.js.map
│ └── src
│ │ ├── constants.d.ts
│ │ ├── constants.js
│ │ ├── constants.js.map
│ │ ├── container.d.ts
│ │ ├── container.js
│ │ ├── container.js.map
│ │ ├── types.d.ts
│ │ ├── types.js
│ │ ├── types.js.map
│ │ ├── watcher.d.ts
│ │ ├── watcher.js
│ │ └── watcher.js.map
└── umd
│ ├── index.d.ts
│ ├── index.js
│ ├── index.js.map
│ └── src
│ ├── constants.d.ts
│ ├── constants.js
│ ├── constants.js.map
│ ├── container.d.ts
│ ├── container.js
│ ├── container.js.map
│ ├── types.d.ts
│ ├── types.js
│ ├── types.js.map
│ ├── watcher.d.ts
│ ├── watcher.js
│ └── watcher.js.map
├── index.ts
├── package.json
├── src
├── constants.ts
├── container.ts
├── types.ts
└── watcher.ts
├── test
├── index.js
├── polyfills.js
└── tests.js
├── testEntry.ts
├── testem.json
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "eslint:recommended",
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "amd": true,
8 | "commonjs": true,
9 | "es6": true
10 | },
11 | "globals": {
12 | "app": true,
13 | "PROD": false,
14 | "DEV": false
15 | },
16 | "parserOptions": {
17 | "ecmaFeatures": {
18 | "modules": true,
19 | "jsx": true
20 | },
21 | "ecmaVersion": 6,
22 | "sourceType": "module",
23 | "jsx": true
24 | },
25 | "plugins": [
26 | "react"
27 | ],
28 | "rules": {
29 | "eqeqeq": 2,
30 | "quotes": [1, "single"],
31 | "no-console": 1,
32 | "dot-location": [1, "property"],
33 | "no-caller": 2,
34 | "no-fallthrough": 2,
35 | "no-floating-decimal": 1,
36 | "no-loop-func": 0,
37 | "no-new-func": 2,
38 | "no-sequences": 1,
39 | "no-throw-literal": 2,
40 | "no-label-var": 2,
41 | "no-shadow-restricted-names": 2,
42 | "no-shadow": 2,
43 | "no-use-before-define": 2,
44 | "react/jsx-uses-vars": 2,
45 | "camelcase": 1,
46 | "no-lonely-if": 1,
47 | "no-nested-ternary": 2,
48 | "no-unneeded-ternary": 1,
49 | "no-trailing-spaces": 1,
50 | "semi": 2
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | testOutput
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .bowerrc
2 | .gitignore
3 | .npmignore
4 | .jshintignore
5 | .spmignore
6 | .jshintrc
7 | .DS_Store
8 | karma.conf.js
9 | spec-runner
10 | bower.json
11 | testem.json
12 | **/.*
13 | *.yml
14 | *.xml
15 | bower_components
16 | test
17 | tests
18 | testling.js
19 | npm-debug.log
20 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "tabWidth": 4
6 | }
--------------------------------------------------------------------------------
/.spmignore:
--------------------------------------------------------------------------------
1 | demo
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 Stu Kabakoff and contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | scrollMonitor
2 | =============
3 |
4 | The scroll monitor allows you to receive events when elements enter or exit a viewport. It does this using watcher objects, which watch an element and trigger events. Watcher objects also contain information about the element they watch, including the element's visibility and location relative to the viewport. If your scroll container is an element other than the body you can create a container that creates watchers.
5 |
6 | The scroll monitor was designed to be very fast. On each scroll event the DOM is only touched twice, once to find the document height and again to find the viewport top. No variables are declared, nor are any objects, arrays, or strings created. Watchers are _very_ cheap. Create them liberally.
7 |
8 | The code is vanilla javascript and has no external dependencies, however _the script cannot be put in the head_.
9 |
10 | Also see the [React hooks](https://github.com/stutrek/scrollmonitor-hooks), [React component](https://github.com/stutrek/scrollmonitor-react) and the [parallax library](https://github.com/stutrek/scrollmonitor-parallax).
11 |
12 | ## Basic Usage
13 |
14 | ### When the body scrolls
15 |
16 | ```javascript
17 | var scrollMonitor = require("scrollmonitor"); // if you're old school you can use the scrollMonitor global.
18 | var myElement = document.getElementById("itemToWatch");
19 |
20 | var elementWatcher = scrollMonitor.create( myElement );
21 |
22 | elementWatcher.enterViewport(function() {
23 | console.log( 'I have entered the viewport' );
24 | });
25 | elementWatcher.exitViewport(function() {
26 | console.log( 'I have left the viewport' );
27 | });
28 | ```
29 |
30 | ### For a scroll container
31 |
32 | ```javascript
33 | var containerElement = document.getElementById("container");
34 |
35 | var containerMonitor = scrollMonitor.createContainer(containerElement);
36 | // this containerMonitor is an instance of the scroll monitor
37 | // that listens to scroll events on your container.
38 |
39 | var childElement = document.getElementById("child-of-container");
40 | var elementWatcher = containerMonitor.create(childElement);
41 |
42 | elementWatcher.enterViewport(function() {
43 | console.log( 'I have entered the viewport' );
44 | });
45 | elementWatcher.exitViewport(function() {
46 | console.log( 'I have left the viewport' );
47 | });
48 | ```
49 |
50 | _Note: an element is said to be in the viewport if it is scrolled into its parent, it does not matter if the parent is in the viewport._
51 |
52 | ## Demos
53 |
54 | * [Stress Test](http://stutrek.github.io/scrollmonitor/demos/stress.html) - Test with as many watchers as you'd like
55 | * [Stress Test in a div](http://stutrek.github.io/scrollmonitor/demos/stressTestInDiv.html) - Note how much slower scrolling a div is than scrolling the body.
56 | * [Nested scrollers](http://stutrek.github.io/scrollmonitor/demos/divInADiv.html)
57 | * [Fixed Positioning and Locking](http://stutrek.github.io/scrollmonitor/demos/fixed.html)
58 | * [Anchored section headers](http://stutrek.github.io/scrollmonitor/demos/list.html)
59 | * [Complex sidebar behavior](http://stutrek.github.io/scrollmonitor/demos/scoreboard.html)
60 |
61 | ## Watcher Objects
62 |
63 | Create watcher objects with `scrollMonitor.create( watchItem )`. An optional second argument lets you receive events before or after this element enters the viewport. _See "[Offsets](#offsets)"_.
64 |
65 | `watchItem` can be one of the following:
66 |
67 | * **DOM Element** - the watcher will watch the area contained by the DOM element.
68 | * **Object** - `obj.top` and `obj.bottom` will be used for watcher.top and watcher.bottom.
69 | * **Number** - the watcher will watch a 1px area this many pixels from the top. Negative numbers will watch from the bottom.
70 | * **jQuery object** - it will use the first DOM element.
71 | * **NodeList** or **Array** - it will use the first DOM element.
72 | * **string** - it will use the string as a CSS selector and watch the first match.
73 |
74 | Watchers are automatically recalculated on the first scroll event after the height of the document changes.
75 |
76 | ### Events
77 |
78 | Element watchers trigger six events:
79 |
80 | * `visibilityChange` - when the element enters or exits the viewport.
81 | * `stateChange` - similar to `visibilityChange` but is also called if the element goes from below the viewport to above it in one scroll event or when the element goes from partially to fully visible or vice versa.
82 | * `enterViewport` - when the element enters the viewport.
83 | * `fullyEnterViewport` - when the element is completely in the viewport [1].
84 | * `exitViewport` - when the element completely leaves the viewport.
85 | * `partiallyExitViewport` - when the element goes from being fully in the viewport to only partially [2].
86 |
87 | 1. If the element is larger than the viewport `fullyEnterViewport` will be triggered when the element spans the entire viewport.
88 | 2. If the element is larger than the viewport `partiallyExitViewport` will be triggered when the element no longer spans the entire viewport.
89 |
90 | ### Properties
91 |
92 | * `elementWatcher.isInViewport` - true if any part of the element is visible, false if not.
93 | * `elementWatcher.isFullyInViewport` - true if the entire element is visible [1].
94 | * `elementWatcher.isAboveViewport` - true if any part of the element is above the viewport.
95 | * `elementWatcher.isBelowViewport` - true if any part of the element is below the viewport.
96 | * `elementWatcher.top` - distance from the top of the document to the top of this watcher.
97 | * `elementWatcher.bottom` - distance from the top of the document to the bottom of this watcher.
98 | * `elementWatcher.height` - top - bottom.
99 | * `elementWatcher.watchItem` - the element, number, or object that this watcher is watching.
100 | * `elementWatcher.offsets` - an object that determines the offsets of this watcher. _See "[Offsets](#offsets)"_.
101 |
102 | 1. If the element is larger than the viewport `isFullyInViewport` is true when the element spans the entire viewport.
103 |
104 | ### Methods
105 |
106 | * `elementWatcher.on/off/one` - the standard event functions.
107 | * `elementWatcher.recalculateLocation` - recalculates the location of the element in relation to the document.
108 | * `elementWatcher.destroy` - removes this watcher and clears out its event listeners.
109 | * `elementWatcher.lock` - locks this watcher at its current location. _See "[Locking](#locking)"_.
110 | * `elementWatcher.unlock` - unlocks this watcher.
111 |
112 | These methods are automatically called by the scrollMonitor, you should never need them:
113 |
114 | * `elementWatcher.update` - updates the boolean properties in relation to the viewport. Does not trigger events.
115 | * `elementWatcher.triggerCallbacks` - triggers any callbacks that need to be called.
116 |
117 | ### Locking
118 |
119 | Sometimes you want to change the element you're watching, but want to continue watching the original area. One common use case is setting `position: fixed` on an element when it exits the viewport, then removing positioning when it when it reenters.
120 |
121 | ```javascript
122 | var watcher = scrollMonitor.create( $element );
123 | watcher.lock(); // ensure that we're always watching the place the element originally was
124 |
125 | watcher.exitViewport(function() {
126 | $element.addClass('fixed');
127 | });
128 | watcher.enterViewport(function() {
129 | $element.removeClass('fixed');
130 | });
131 | ```
132 |
133 | Because the watcher was locked on the second line, the scroll monitor will never recalculate its location.
134 |
135 | ### Offsets
136 |
137 | If you want to trigger an event when the edge of an element is near the edge of the viewport, you can use offsets. The offset is the second argument to `scrollMonitor.create`.
138 |
139 | This will trigger events when an element gets within 200px of the viewport:
140 | ```javascript
141 | scrollMonitor.create( element, 200 )
142 | ```
143 |
144 | This will trigger when the element is 200px inside the viewport:
145 | ```javascript
146 | scrollMonitor.create( element, -200 )
147 | ```
148 |
149 | If you only want it to affect the top and bottom differently you can send an object in.
150 | ```javascript
151 | scrollMonitor.create( element, {top: 200, bottom: 50})
152 | ```
153 |
154 | If you only want it to affect the top and not the bottom you can use only one property in.
155 | ```javascript
156 | scrollMonitor.create( element, {top: 200})
157 | ```
158 |
159 | ## scrollMonitor Module
160 |
161 | ### Methods
162 | * `scrollMonitor.createContainer( containerEl )` - returns a new ScrollMonitorContainer that can be used just like the scrollMonitor module.
163 | * `scrollMonitor.create( watchItem, offsets )` - Returns a new watcher. `watchItem` is a DOM element, jQuery object, NodeList, CSS selector, object with .top and .bottom, or a number.
164 | * `scrollMonitor.update()` - update and trigger all watchers.
165 | * `scrollMonitor.recalculateLocations()` - recalculate the location of all unlocked watchers and trigger if needed.
166 |
167 | ### Properties
168 | * `scrollMonitor.viewportTop` - distance from the top of the document to the top of the viewport.
169 | * `scrollMonitor.viewportBottom` - distance from the top of the document to the bottom of the viewport.
170 | * `scrollMonitor.viewportHeight` - height of the viewport.
171 | * `scrollMonitor.documentHeight` - height of the document.
172 |
173 | # Contributing
174 |
175 | There is a set of unit tests written with Mocha + Chai that run in testem. Getting them running is simple:
176 |
177 | ```
178 | npm install
179 | npm test
180 | ```
181 |
182 | then open http://localhost:7357
183 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scrollMonitor",
3 | "version": "1.2.2",
4 | "main": "./scrollMonitor.js",
5 | "dependencies": {},
6 | "devDependencies": {},
7 | "ignore": [
8 | ".bowerrc",
9 | ".gitignore",
10 | ".npmignore",
11 | ".jshintignore",
12 | ".spmignore",
13 | ".jshintrc",
14 | "karma.conf.js",
15 | "spec-runner",
16 | "package.json",
17 | "testem.json",
18 | "**/.*",
19 | "*.yml",
20 | "*.xml",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "tests",
25 | "testling.js"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/demos/divInADiv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
80 |
81 |
82 |
83 |
84 |
85 |
Here are a couple of boxes from the stress test.
86 |
87 |
88 |
89 |
90 |
91 |
And a few more, just to prove that it works down here too.
92 |
93 |
94 |
95 |
96 |
97 |
And a scroller in a scroller.
98 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
137 |
138 |
--------------------------------------------------------------------------------
/demos/fixed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
32 |
33 |
34 | One!
35 | Two!
36 | Three!
37 |
38 |
39 |
40 |
41 |
57 |
58 |
--------------------------------------------------------------------------------
/demos/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
95 |
96 |
--------------------------------------------------------------------------------
/demos/listInDiv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
113 |
114 |
--------------------------------------------------------------------------------
/demos/listdata.json:
--------------------------------------------------------------------------------
1 | {"American-Style Amber Lager":{"Hudepohl-Schoenling":["Amber Lager"],"Coors":["AC Golden Colorado Native Lager","George Killian's Irish Red"],"Sapporo (Chuo)":["Reserve"],"Wyoming Territory":["Buffalo Bill Cody Beer"],"Iron City":["Augustiner Premium Amber Lager"],"Yuengling":["Traditional Lager"],"Lakefront":["Riverwest Stein Beer"],"Stevens Point":["Oktoberfest","Amber Lager"],"Florida":["Ybor Gold Amber Lager"],"Flying Dog":["Old Scratch Amber Lager","Dogtoberfest Octoberfest"],"Anheuser-Busch (Fairfield)":["Green Valley Wild Hop Lager"],"Cape Ann":["Fisherman's Brew"],"Anheuser-Busch (St. Louis)":["Michelob Black & Tan"],"Cold Spring":["Aspen Meadow Black and Tan"],"Capital":["Wisconsin Amber"],"Gray":["Black and Tan"],"Genesee":["Michael Shea's Porter and Lager Black and Tan"],"Blue Hen":["Black and Tan"]},"American-Style Lager":{"Miller (Milwaukee)":["Stag Beer","High Life","Genuine Draft"],"Schell":["Hauenstein New Ulm Beer"],"Narragansett":["Lager"],"Whistle Stop":["Platinum Lager"],"Rhinelander":["Export"],"F.X. Matt":["Brewmaster's Choice Big Flats 1901"],"Minhas":["Boxer Lager","Huber Berghoff Original Lager Beer"],"SchillingBridge":["Git-R-Done"],"Porterhouse":["Chiller","Lager"],"Pabst":["Old Milwaukee","Blue Ribbon Beer","Old Style","Falstaff Ballantine IPA"],"Anheuser-Busch (Newark)":["Rolling Rock"],"Schlitz":["Schlitz Beer"],"Stevens Point":["Special Beer"],"Dixie":["Beer"],"New Glarus (Elmer Road)":["Totally Naked"],"Great Divide":["Samurai"],"Seabright":["Brew Ribbon"],"Central City":["Springboard Lager"],"Big Ridge":["Lager"],"Russell":["Special Lager"],"Taylor's Crossing":["Dominion Lager"],"Steamworks (Vancouver)":["Lions Gate Lager"],"Marine Pub":["Premium Gold"],"Granville Island":["Island Lager"],"Sly Fox (Royersford)":["Featherweight Lager"],"Crabby Larry's":["Summer Lager"],"South Shore":["Honey Pilsner"],"Denver ChopHouse":["Pilsner Lager"],"Kenya":["Tusker Premium Lager"],"Green Mill (Saint Paul)":["Kabeelo Lodge Lager"],"Gordon Biersch (San Diego)":["Premium Light Lager"],"Appleton":["Adler Bräu Eagle Lager"],"Hops Haven":["Port Washington Pier 96 Lager"],"New Century":["Edison Light Beer"],"Boston Beer Company (Cincinnati)":["Samuel Adams Boston Lager"],"Guinness":["Harp Lager"],"Labatt":["Blue","Ice","Canadian Ale"],"Day":["Louisiana's Golden Lager"],"City":["Pale Ale","Lager"],"Cold Spring":["Stite Golden Pilsner"],"Sprecher":["Pale Lager"],"Tommyknocker":["Alpine Glacier Lager"],"Carlton and United (Melbourne)":["Foster's Lager"],"Cugino":["Beer","Light"],"Qingdao":["Green Beer"],"Great Dane (Fitchburg)":["Landmark Light"],"Saxer":["Pilsner"],"First Coast":["Golden Lager"],"Brown Street (Rhinelander)":["Aussie Lager"],"Moosejaw":["Pilsner"],"Courthouse":["Canadian Light"],"Upper Mississippi":["Bartles and Lager"],"Skol":["Royal Challenge Indian Premium Lager"],"Rock Bottom (Minneapolis)":["North Star Premium Lager"],"Granite City (Saint Cloud)":["Victory Lager","Northern Light"],"Oaken Barrel":["Meridian Street Premium Lager"],"Brickhouse":["Laughing Ass"],"Molson":["Golden","Canadian Lager","Ice"],"Moosehead":["Canadian Lager Beer"],"Fratellos (Appleton)":["Fox Light"],"Mad Anthony":["Lager"],"Thirsty Dog (North Canton)":["Man's Best Friend","Airship Light"],"T-Bonz (Mount Pleasant)":["Low Country Light Lager"],"Southend (Charleston SC)":["Blonde","Blonde Light"],"Route 66":["Lake Shore Light"],"Hereford and Hops (Wausau)":["Lichthouse Lager"],"Brewmasters (South Kenosha)":["Icemaster"],"Coast":["Biloxi Light"],"Cross Plains":["Esser's Cross Plains Special","Esser's Best"],"Panther":["Ziggy Socky Premium Lager Beer","Three Stooges Beer"],"Grumpy Troll":["Lager"],"Dirt Cheap":["Beer"],"Choc":["American Lager"],"Hoppers (Midvale)":["Uno Mas"],"Heavenly Daze (Denver)":["El Rey Cerveza"],"Hops (Cherry Creek)":["Lightning Bold Gold","Clearwater Light"],"New Road":["Peckiomen Pils"],"Water Street Lake Country":["Honey Lager Light"],"Hereford & Hops (Escanaba)":["Lichthaus Lager"],"Brick":["Premium Lager"],"La Constancia":["Regia Extra","Suprema","Pilsner of El Salvador","Premier"],"Thunderhead":["Honey Lager"],"Steel":["High Gravity Lager"],"Leinenkugel (Milwaukee)":["Northwoods Lager"],"Moctezuma":["Dos Equis Special Lager"],"Blitz-Weinhard":["Henry Weinhard's Blue Boar Red Lager"],"Stroh (Tampa)":["Hatuey Beer"],"Latrobe":["Rolling Rock"],"Water Street":["Honey Lager Light","Pils"],"Port Washington":["Pier 96 Lager"],"Rail House":["Pilsner (discontinued)"],"Swan":["Lager"],"Tunner's Guild":["Rock River Lager Beer"],"Great Dane (Downtown)":["Pro-Tel Memorial Malt Liquor"],"Goose Island":["Baderbräu Pilsener"],"Leinenkugel":["Original"],"Heileman (La Crosse)":["Old Style","Special Export"],"Spoetzl":["Shiner Kosmos Reserve","Shiner Blonde"],"Nacional (Panamá)":["Balboa Cerveza Pilsner","Atlas Lager"],"Stroh (Detroit)":["Schlitz","Primo Beer"],"Ostankinskij":["Beer"],"Rogue":["Artisan Lager"],"Gold Crown":["Premium Lager"],"Big Bang":["Space-Aged Lager"],"O'Grady's":["Haymarket Pilsner"],"Weinkeller (Berwyn)":["Pilsner"],"Genesee":["Michael Shea's Irish Amber Brand Pub Style Lager"],"Bad Frog":["Amber Lager"],"Canadian":["Listwin's Imported Kodiak Canadian Premium Lager"],"Old City":["Pecan Street Lager"],"Routh Street":["Pilsner"],"Carolina":["Lager"],"Pyramid (Kalama)":["Sun Fest"],"Golden Pacific":["Golden Bear Lager"],"Rock Bottom (San Jose)":["Pilsner"],"Pyramid (Berkeley)":["Sun Fest"],"Stroh (Longview)":["Lone Star"],"Cuauhtémoc (Nuevo Leon)":["Carta Blanca"],"United":["Flying Horse Royal Lager Beer"],"Hapa*s":["Moonset Lager"],"Anheuser-Busch (St. Louis)":["Michelob Golden Pilsner"],"Sapporo (Chuo)":["Draft"],"Lanchester":["Storm Super Premium Malt Liquor"],"Wainwright":["Black Jack Black and Tan"],"Jinro Coors":["Cass Fresh"],"Tahiti":["Hinano"],"Minnesota":["McMahon's Irish-Style Potato Ale"],"Gottberg":["Old Powerhouse Lager (discontinued)"],"Twisted X":["Lager"],"Pittsburgh":["Iron City"],"Fredimo":["Morechamp International Lager Beer"],"Lion":["1857 Lager (discontinued)"],"Evansville":["Gerst Amber Lager"],"Rock Bottom (Denver)":["Rockies Premium Draft"],"Cardinal":["Devil Anse Lager"],"Solibra":["Mamba"],"Kirin":["Lager"],"Rainbow Ridge":["Blue Ridge Lager"],"New Zealand":["Steinlager"],"Cuauhtémoc (Baja California)":["Cerveza Tecate"],"Thomas Kemper":["Pale Lager"],"Flying Aces":["Growlin' Gator Lager"],"Brewski's (Hermosa Beach)":["Pilsner"],"Castlemaine Perkins":["XXXX Export Lager"],"Hogshead":["Light Lager"],"Zhejiang":["Chung Hua"],"Ocean Avenue":["Pilsner"],"Newport Beach":["Gold Eagle Premium"],"Black Diamond (Walnut Creek)":["Premium"],"Santa Cruz":["Lighthouse Lager"],"Nevada City":["Golden Lager"],"Los Gatos":["Lager"],"Champion":["Legendary Lager"],"David and Mark":["Black Sheep Light Lager"],"Legend":["Lager"],"Maritime Pacific":["Cape Lager"],"Pete's":["Wicked Lager"],"Willett's":["Willy's Lager"]},"American Lager/Ale or Cream Ale":{"Miller (Milwaukee)":["Hamm's"],"Galena":["Farmer's Cream Ale"],"Genesee":["Cream Ale"],"Copper Creek":["Cream Ale"],"Big Ridge":["#17 Cream Ale"],"Taylor's Crossing":["Baden Powell Cream Ale"],"Steamworks (Vancouver)":["Cascadia Cream Ale"],"Mission Springs":["Cream Ale"],"Brau Brothers":["Brau Light"],"Fitger's":["Lighthouse Ale"],"South Shore":["Cream Ale"],"Oggi's (Vista)":["California Gold"],"Lagunitas":["Sirius"],"North Coast":["Scrimshaw Pilsner Style Beer"],"Thunderhead":["Cream Ale"],"Govnor's":["American Cream Ale","Celtic Cream Ale"],"Miller (Tumwater)":["Henry Weinhard's Ale"],"Bull & Bush":["Cream"],"Stout Brothers":["The Yank Cream Ale"],"Agassiz":["Catfish Cream Ale"],"Rail House":["Silver Cream"],"Gray":["Cream Ale"],"C.B. & Potts (Cheyenne)":["Big Horn Light"],"Titletown":["Old Broadway Cream Ale"],"Golden Gate Park":["Four Sheets Cream Ale"],"Hoffbrau (Dallas)":["Yellow Rose Cream Ale"],"ThirstyBear":["Polar Ale"],"Beach Chalet":["Endless Summer Cream Ale"],"Aloha":["Bruddah's Cream Ale"],"Hoffbrau (Addison)":["Yellow Rose Cream Ale"],"Schell":["Not Guilty 1924 Deer Brand"],"Rogue":["Honey Cream Ale"],"Hudepohl-Schoenling":["Little Kings Cream Ale"],"Riverside":["Raincross Cream Ale"],"Coast Range":["Cream Ale"],"Belmont":["Marathon Pale Ale"],"Coeur D'Alene":["Creme Ale"],"Golden City":["Cream Ale"],"Oliver's":["Cream Ale"],"Willett's":["Ace High Cream Ale"]},"American-Style Premium Lager":{"Capital":["Supper Club"],"Wolverine State":["Wolverine Premium Lager"],"Leinenkugel":["Limited (formerly Northwoods Lager)"],"Boulevard":["Pilsner"],"Anheuser-Busch (St. Louis)":["Budweiser"],"Coors":["Original Banquet Beer"],"Schell":["Grain Belt Premium"],"Nørrebro":["New York Lager"],"Maritime Pacific":["Old Seattle Lager"],"Manayunk":["Bohemian Blonde"],"Indian Wells":["Mojave Gold"]},"Australasian, Latin American, or Tropical-Style Light Lager":{"Modelo":["Especial"],"Hangzhou Qiandaohu":["Lucky Buddha"],"Anheuser-Busch (St. Louis)":["Landshark Lager"],"Ska":["Mexican Logger"],"Kaiser (Jacareí)":["Palma Louca"],"Windward & Leeward":["Piton Lager Beer"],"Boon Rawd (Pathum Thani)":["Singha"],"Florida":["Hurricane Reef Caribbean-Style Pilsner"],"Boag's":["Premium Lager"],"San Miguel":["Premium Lager"],"Desnoes & Geddes":["Red Stripe Lager Beer"],"Sleeman":["Sapporo Premium Beer"],"Cuauhtémoc (Nuevo Leon)":["Cerveza Sol","Bohemia Clásica"],"Tooheys":["New"],"Jerome":["Rubia"],"Macau":["Lager"],"Dix":["Tropical Lager"],"Elysian (TangleTown)":["Foster Child Australian Lager"],"Skagit River":["Del Rio Lager"],"Engine House #9":["Ricardo's XXX Lager"],"Bucanero":["Fuerte"],"Myanmar":["Lager Beer"],"Taiwan":["Beer"],"Hondureña":["Port Royal Export"],"Pacífico":["Pacifico Clara"],"Centro Americana":["Nacional Cabro Extra"],"Samoa":["Vailima"],"Backus y Johnston":["Premium Cristal"],"South Australian":["Broken Hill Lager"],"Qingdao":["Tsingtao Premium"],"Boon Rawd (Samsen)":["Singha"],"Huế":["Beer"]},"American-Style Light Lager":{"Anheuser-Busch (St. Louis)":["Natural Light","Michelob Ultra"],"Upstream (Legacy)":["O! Gold Light"],"Upstream (Old Market)":["O! Gold Light"],"Granite City (Omaha)":["Northern Light Lager"],"Big Ridge":["Light Lager"],"McKenzie (Glen Mills)":["Light Lager"],"John Harvard's (Wilmington)":["Light Lager"],"Iron Hill (Wilmington)":["Light Lager"],"Miller (Milwaukee)":["Lite"]},"American-Style Dark Lager":{"Yuengling":["Bock Beer"],"Full Sail":["Session Black Premium Dark Lager"],"Spoetzl":["Shiner Bock"],"Leinenkugel":["Creamy Dark"],"Blitz-Weinhard":["Henry Weinhard's Classic Dark Lager"],"Nicolet":["Dark Pilsener"],"Stevens Point":["Augsburger Dark"],"Anheuser-Busch (St. Louis)":["Michelob AmberBock"],"Sleeman":["Original Dark"],"Coast":["Beau Rivage Bock"],"Minhas":["Huber Berghoff Genuine Dark"],"Brasal":["Special Amber Lager"],"Salado Creek":["Amber"],"Mississippi":["Mississippi Mud Black and Tan"],"Cold Spring":["Dark"],"Dixie":["Blackened Voodoo Lager"],"Dock Street":["Amber Lager"],"Stroh (St.Paul)":["Red River Valley Select Red Lager"],"Chicago (Chicago)":["Legacy Lager"]},"Vienna-Style Lager":{"New Glarus":["Two Women Lager"],"Baltika":["Arsenal Classic / Арсеналное Классическое","Big Mug Amber Beer / Большая Кружка Янтарное Пиво"],"Švyturys":["Baltijos Dark Red / Tamsusis Alus"],"Bank":["Walleye Chop"],"Chameleon":["Fire Light"],"Schell":["Nordeast","Fire Brick (formerly Snowstorm 1998 Vienna Lager)"],"Crescent City":["Red Stallion"],"Muskie Capital":["Red Lager"],"Minhas":["Swiss Amber"],"Berg":["Ulrichsbier"],"Zirndorfer":["Landbier"],"Sprecher":["Special Amber"],"Aare":["Amber"],"Fauerbach":["Amber Lager"],"Menabrea":["Birra Ambrata"],"Karbacher":["Köhler","Speuzer"],"Unser Bier":["Amber"],"Innstadt":["d'Inn'Staade"],"Yarpivo":["Amber Lager / Яантарное"],"Vesterbro":["Amber Lager"],"Upstream (Legacy)":["Jackson St. Lager"],"Tommyknocker":["Vienna Amber Lager (formerly Ornery Amber Lager)"],"Barfüßer (Ulm)":["Rotgold-Pils"],"Haldengut":["Original Ittinger Klosterbräu"],"Max & Moritz":["Spezial"],"Joh. Albrecht (Konstanz)":["Kupfer"],"Big River (Richmond)":["Vienna Lager"],"Dockside":["Old Bridge Dark Lager"],"Indian Wells":["Mojave Red"],"Glenwood Canyon":["Dos Rios Vienna Lager"],"Karl Strauss Brewery Gardens":["Amber Lager"],"Stevens Point":["Lizard Lager"],"South Shore":["Red Lager"],"Zea":["Lager"],"Logjam":["Lager"],"Heineken Ireland":["Murphy's Red Beer"],"Titletown":["Red Lager"],"Trap Rock":["Hathor Red Lager"],"Courthouse":["Courthouse Copper"],"Old Hat":["Red Lager"],"Southend (Charleston SC)":["Ruby Red Lager"],"Randy's Fun Hunters":["Amber Lager"],"Route 66":["Mile Marker Amber Lager"],"Great Lakes":["Eliot Ness Amber Lager"],"Leinenkugel":["Red Lager"],"Coast":["Ruby Red Lager"],"Dillon Dam":["Dam Straight Lager"],"Denmark":["Valhalla Ale"],"Live Oak":["Big Bark Amber Lager"],"Amerisports":["Hofbrauhaus Brewery & Biergarten Vienna Velvet"],"Millstream":["Schild Brau Amber"],"Aass":["Classic Special Brew"],"James Page":["Iron Range Amber Lager"],"Dubuque":["Star Big Muddy Brown"],"Oconomowoc":["Amber Rye Lager"],"Sturgeon Bay":["Lighthouse Amber Lager"],"Great Dane (Downtown)":["Devil's Lake Red Lager"],"Tommyknocker (Casper)":["Red Eye Lager"],"Moctezuma":["Noche Buena Special Holiday Amber Beer","Dos Equis Amber Lager"],"Hoffbrau (Dallas)":["Red Lager"],"Pyramid (Berkeley)":["Amber Lager"],"Hapa*s":["Red Sky Amber Lager"],"Telluride":["Amber Lager"],"Jack Daniel's":["1866 Classic Amber Lager"],"Flying Dog":["Railyard Ale"],"Modelo":["Negra Modelo"],"New Zealand":["Lion Red Beer"],"Old West (Las Cruces)":["Famous Amber Beer"],"Thomas Kemper":["Amber Lager"],"Truckee":["Amber"],"Louisiana Jack's":["Oktoberfest"],"Woodstock":["Amber Lager"],"Pete's":["Wicked Red"],"Moonlight":["Rising Tides Amber Lager"]},"European-Style Low-Alcohol Lager / German-Style Leicht(bier)":{"Warsteiner":["Premium HiLight"],"Waldhaus":["Light Line"],"Heineken":["Premium Light Lager Beer"]},"American-Style \"Light\" Amber Lager":{"Tied House (San Jose)":["Amber Light"]}}
--------------------------------------------------------------------------------
/demos/reloadTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrolling Test
5 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
45 |
46 |
--------------------------------------------------------------------------------
/demos/scoreboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
37 |
38 |
60 |
61 |
62 |
63 |
109 |
110 |
--------------------------------------------------------------------------------
/demos/stress.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
51 |
52 |
53 | scrollMonitor stress test
54 |
55 | Showing elements.
56 |
57 |
58 |
100 -
59 |
1,000 -
60 |
10,000 -
61 |
20,000 -
62 |
30,000 -
63 |
50,000 -
64 |
100,000 -
65 |
200,000 -
66 |
300,000 -
67 |
500,000 -
68 |
1,000,000
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/demos/stressTestInDiv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
60 |
61 |
62 | scrollMonitor stress test
63 |
64 | Showing elements.
65 |
66 |
67 |
100 -
68 |
1,000 -
69 |
10,000 -
70 |
20,000 -
71 |
30,000 -
72 |
50,000 -
73 |
100,000 -
74 |
200,000 -
75 |
300,000 -
76 |
500,000 -
77 |
1,000,000
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/demos/vh-units.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
27 |
28 |
29 |
30 |
31 |
One!
32 |
Two!
33 |
Three!
34 |
35 |
36 |
37 |
38 |
60 |
61 |
--------------------------------------------------------------------------------
/dist/module/index.d.ts:
--------------------------------------------------------------------------------
1 | export { ScrollMonitorContainer } from './src/container.js';
2 | export { Watcher } from './src/watcher.js';
3 | export * from './src/types';
4 | import { ScrollMonitorContainer } from './src/container.js';
5 | declare const scrollMonitor: ScrollMonitorContainer;
6 | export default scrollMonitor;
7 |
--------------------------------------------------------------------------------
/dist/module/index.js:
--------------------------------------------------------------------------------
1 | export { ScrollMonitorContainer } from './src/container.js';
2 | export { Watcher } from './src/watcher.js';
3 | export * from './src/types';
4 | import { isInBrowser } from './src/constants.js';
5 | import { ScrollMonitorContainer } from './src/container.js';
6 | // this is needed for the type, but if we're not in a browser the only
7 | // way listenToDOM will be called is if you call scrollmonitor.createContainer
8 | // and you can't do that until you have a DOM element.
9 | var scrollMonitor = new ScrollMonitorContainer(isInBrowser ? document.body : undefined);
10 | if (isInBrowser) {
11 | scrollMonitor.updateState();
12 | scrollMonitor.listenToDOM();
13 | //@ts-ignore
14 | window.scrollMonitor = scrollMonitor;
15 | }
16 | export default scrollMonitor;
17 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/dist/module/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,sEAAsE;AACtE,8EAA8E;AAC9E,sDAAsD;AACtD,IAAM,aAAa,GAAG,IAAI,sBAAsB,CAC5C,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAE,SAAoC,CACtE,CAAC;AACF,IAAI,WAAW,EAAE;IACb,aAAa,CAAC,WAAW,EAAE,CAAC;IAC5B,aAAa,CAAC,WAAW,EAAE,CAAC;IAC5B,YAAY;IACZ,MAAM,CAAC,aAAa,GAAG,aAAa,CAAC;CACxC;AAED,eAAe,aAAa,CAAC"}
--------------------------------------------------------------------------------
/dist/module/src/constants.d.ts:
--------------------------------------------------------------------------------
1 | export declare const VISIBILITYCHANGE = "visibilityChange";
2 | export declare const ENTERVIEWPORT = "enterViewport";
3 | export declare const FULLYENTERVIEWPORT = "fullyEnterViewport";
4 | export declare const EXITVIEWPORT = "exitViewport";
5 | export declare const PARTIALLYEXITVIEWPORT = "partiallyExitViewport";
6 | export declare const LOCATIONCHANGE = "locationChange";
7 | export declare const STATECHANGE = "stateChange";
8 | export declare const eventTypes: readonly ["visibilityChange", "enterViewport", "fullyEnterViewport", "exitViewport", "partiallyExitViewport", "locationChange", "stateChange"];
9 | export declare const isOnServer: boolean;
10 | export declare const isInBrowser: boolean;
11 | export declare const defaultOffsets: {
12 | readonly top: 0;
13 | readonly bottom: 0;
14 | };
15 |
--------------------------------------------------------------------------------
/dist/module/src/constants.js:
--------------------------------------------------------------------------------
1 | export var VISIBILITYCHANGE = 'visibilityChange';
2 | export var ENTERVIEWPORT = 'enterViewport';
3 | export var FULLYENTERVIEWPORT = 'fullyEnterViewport';
4 | export var EXITVIEWPORT = 'exitViewport';
5 | export var PARTIALLYEXITVIEWPORT = 'partiallyExitViewport';
6 | export var LOCATIONCHANGE = 'locationChange';
7 | export var STATECHANGE = 'stateChange';
8 | export var eventTypes = [
9 | VISIBILITYCHANGE,
10 | ENTERVIEWPORT,
11 | FULLYENTERVIEWPORT,
12 | EXITVIEWPORT,
13 | PARTIALLYEXITVIEWPORT,
14 | LOCATIONCHANGE,
15 | STATECHANGE,
16 | ];
17 | export var isOnServer = typeof window === 'undefined';
18 | export var isInBrowser = !isOnServer;
19 | export var defaultOffsets = { top: 0, bottom: 0 };
20 | //# sourceMappingURL=constants.js.map
--------------------------------------------------------------------------------
/dist/module/src/constants.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,IAAM,gBAAgB,GAAG,kBAAkB,CAAC;AACnD,MAAM,CAAC,IAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,IAAM,kBAAkB,GAAG,oBAAoB,CAAC;AACvD,MAAM,CAAC,IAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,IAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAC7D,MAAM,CAAC,IAAM,cAAc,GAAG,gBAAgB,CAAC;AAC/C,MAAM,CAAC,IAAM,WAAW,GAAG,aAAa,CAAC;AAEzC,MAAM,CAAC,IAAM,UAAU,GAAG;IACtB,gBAAgB;IAChB,aAAa;IACb,kBAAkB;IAClB,YAAY;IACZ,qBAAqB;IACrB,cAAc;IACd,WAAW;CACL,CAAC;AAEX,MAAM,CAAC,IAAM,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;AACxD,MAAM,CAAC,IAAM,WAAW,GAAG,CAAC,UAAU,CAAC;AAEvC,MAAM,CAAC,IAAM,cAAc,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAW,CAAC"}
--------------------------------------------------------------------------------
/dist/module/src/container.d.ts:
--------------------------------------------------------------------------------
1 | import { Offsets, WatchItemInput, ScrollEvent } from './types.js';
2 | import { Watcher } from './watcher.js';
3 | export declare class ScrollMonitorContainer {
4 | item: HTMLElement;
5 | watchers: Watcher[];
6 | viewportTop: number;
7 | viewportBottom: number;
8 | documentHeight: number;
9 | viewportHeight: number;
10 | contentHeight: number;
11 | containerWatcher: Watcher | undefined;
12 | update: () => void;
13 | recalculateLocations: () => void;
14 | eventTypes: readonly ["visibilityChange", "enterViewport", "fullyEnterViewport", "exitViewport", "partiallyExitViewport", "locationChange", "stateChange"];
15 | constructor(item: HTMLElement, parentWatcher?: ScrollMonitorContainer);
16 | listenToDOM(): void;
17 | destroy(): void;
18 | DOMListener(event: ScrollEvent): void;
19 | updateState(): void;
20 | updateAndTriggerWatchers(event: ScrollEvent): void;
21 | createContainer(input: HTMLElement | NodeList | HTMLElement[] | string): ScrollMonitorContainer;
22 | create(input: WatchItemInput, offsets?: Offsets): Watcher;
23 | /**
24 | * @deprecated since version 1.1
25 | */
26 | beget(input: WatchItemInput, offsets?: Offsets): Watcher;
27 | }
28 |
--------------------------------------------------------------------------------
/dist/module/src/container.js:
--------------------------------------------------------------------------------
1 | import { isOnServer, isInBrowser, eventTypes } from './constants.js';
2 | import { Watcher } from './watcher.js';
3 | function getViewportHeight(element) {
4 | if (isOnServer) {
5 | return 0;
6 | }
7 | if (element === document.body) {
8 | return window.innerHeight || document.documentElement.clientHeight;
9 | }
10 | else {
11 | return element.clientHeight;
12 | }
13 | }
14 | function getContentHeight(element) {
15 | if (isOnServer) {
16 | return 0;
17 | }
18 | if (element === document.body) {
19 | // jQuery approach
20 | // whichever is greatest
21 | return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight);
22 | }
23 | else {
24 | return element.scrollHeight;
25 | }
26 | }
27 | function scrollTop(element) {
28 | if (isOnServer) {
29 | return 0;
30 | }
31 | if (element === document.body) {
32 | return (window.pageYOffset ||
33 | (document.documentElement && document.documentElement.scrollTop) ||
34 | document.body.scrollTop);
35 | }
36 | else {
37 | return element.scrollTop;
38 | }
39 | }
40 | var browserSupportsPassive = false;
41 | if (isInBrowser) {
42 | try {
43 | var opts = Object.defineProperty({}, 'passive', {
44 | get: function () {
45 | browserSupportsPassive = true;
46 | },
47 | });
48 | window.addEventListener('test', null, opts);
49 | }
50 | catch (e) { }
51 | }
52 | var useCapture = browserSupportsPassive ? { capture: false, passive: true } : false;
53 | var ScrollMonitorContainer = /** @class */ (function () {
54 | function ScrollMonitorContainer(item, parentWatcher) {
55 | this.eventTypes = eventTypes;
56 | var self = this;
57 | this.item = item;
58 | this.watchers = [];
59 | this.viewportTop = null;
60 | this.viewportBottom = null;
61 | this.documentHeight = getContentHeight(item);
62 | this.viewportHeight = getViewportHeight(item);
63 | this.DOMListener = function () {
64 | ScrollMonitorContainer.prototype.DOMListener.apply(self, arguments);
65 | };
66 | if (parentWatcher) {
67 | this.containerWatcher = parentWatcher.create(item);
68 | }
69 | var previousDocumentHeight;
70 | var calculateViewportI;
71 | function calculateViewport() {
72 | self.viewportTop = scrollTop(item);
73 | self.viewportBottom = self.viewportTop + self.viewportHeight;
74 | self.documentHeight = getContentHeight(item);
75 | if (self.documentHeight !== previousDocumentHeight) {
76 | calculateViewportI = self.watchers.length;
77 | while (calculateViewportI--) {
78 | self.watchers[calculateViewportI].recalculateLocation();
79 | }
80 | previousDocumentHeight = self.documentHeight;
81 | }
82 | }
83 | var updateAndTriggerWatchersI;
84 | function updateAndTriggerWatchers() {
85 | // update all watchers then trigger the events so one can rely on another being up to date.
86 | updateAndTriggerWatchersI = self.watchers.length;
87 | while (updateAndTriggerWatchersI--) {
88 | self.watchers[updateAndTriggerWatchersI].update();
89 | }
90 | updateAndTriggerWatchersI = self.watchers.length;
91 | while (updateAndTriggerWatchersI--) {
92 | self.watchers[updateAndTriggerWatchersI].triggerCallbacks(undefined);
93 | }
94 | }
95 | this.update = function () {
96 | calculateViewport();
97 | updateAndTriggerWatchers();
98 | };
99 | this.recalculateLocations = function () {
100 | this.documentHeight = 0;
101 | this.update();
102 | };
103 | }
104 | ScrollMonitorContainer.prototype.listenToDOM = function () {
105 | if (isInBrowser) {
106 | if (this.item === document.body) {
107 | window.addEventListener('scroll', this.DOMListener, useCapture);
108 | }
109 | else {
110 | this.item.addEventListener('scroll', this.DOMListener, useCapture);
111 | }
112 | window.addEventListener('resize', this.DOMListener);
113 | this.destroy = function () {
114 | if (this.item === document.body) {
115 | window.removeEventListener('scroll', this.DOMListener, useCapture);
116 | this.containerWatcher.destroy();
117 | }
118 | else {
119 | this.item.removeEventListener('scroll', this.DOMListener, useCapture);
120 | }
121 | window.removeEventListener('resize', this.DOMListener);
122 | };
123 | }
124 | };
125 | ScrollMonitorContainer.prototype.destroy = function () {
126 | // noop, override for your own purposes.
127 | // in listenToDOM, for example.
128 | };
129 | ScrollMonitorContainer.prototype.DOMListener = function (event) {
130 | //alert('got scroll');
131 | this.updateState();
132 | this.updateAndTriggerWatchers(event);
133 | };
134 | ScrollMonitorContainer.prototype.updateState = function () {
135 | var viewportTop = scrollTop(this.item);
136 | var viewportHeight = getViewportHeight(this.item);
137 | var contentHeight = getContentHeight(this.item);
138 | var needsRecalcuate = viewportHeight !== this.viewportHeight || contentHeight !== this.contentHeight;
139 | this.viewportTop = viewportTop;
140 | this.viewportHeight = viewportHeight;
141 | this.viewportBottom = viewportTop + viewportHeight;
142 | this.contentHeight = contentHeight;
143 | if (needsRecalcuate) {
144 | var i = this.watchers.length;
145 | while (i--) {
146 | this.watchers[i].recalculateLocation();
147 | }
148 | }
149 | };
150 | ScrollMonitorContainer.prototype.updateAndTriggerWatchers = function (event) {
151 | var i = this.watchers.length;
152 | while (i--) {
153 | this.watchers[i].update();
154 | }
155 | i = this.watchers.length;
156 | while (i--) {
157 | this.watchers[i].triggerCallbacks(event);
158 | }
159 | };
160 | ScrollMonitorContainer.prototype.createContainer = function (input) {
161 | var item;
162 | if (typeof input === 'string') {
163 | item = document.querySelector(input);
164 | }
165 | else if (Array.isArray(input) || input instanceof NodeList) {
166 | item = input[0];
167 | }
168 | else {
169 | item = input;
170 | }
171 | var container = new ScrollMonitorContainer(item, this);
172 | this.updateState();
173 | container.listenToDOM();
174 | return container;
175 | };
176 | ScrollMonitorContainer.prototype.create = function (input, offsets) {
177 | var item;
178 | if (typeof item === 'string') {
179 | item = document.querySelector(item);
180 | }
181 | else if (Array.isArray(input) || input instanceof NodeList) {
182 | item = input[0];
183 | }
184 | else {
185 | item = input;
186 | }
187 | var watcher = new Watcher(this, item, offsets);
188 | this.watchers.push(watcher);
189 | return watcher;
190 | };
191 | /**
192 | * @deprecated since version 1.1
193 | */
194 | ScrollMonitorContainer.prototype.beget = function (input, offsets) {
195 | return this.create(input, offsets);
196 | };
197 | return ScrollMonitorContainer;
198 | }());
199 | export { ScrollMonitorContainer };
200 | //# sourceMappingURL=container.js.map
--------------------------------------------------------------------------------
/dist/module/src/container.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"container.js","sourceRoot":"","sources":["../../../src/container.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/module/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import type { Watcher } from './watcher.js';
2 | export declare type EventName = 'visibilityChange' | 'enterViewport' | 'fullyEnterViewport' | 'exitViewport' | 'partiallyExitViewport' | 'locationChange' | 'stateChange';
3 | export declare type WatchItemInput = HTMLElement | {
4 | top: number;
5 | bottom: number;
6 | } | number | NodeList | HTMLElement[] | string;
7 | export declare type WatchItem = HTMLElement | {
8 | top: number;
9 | bottom: number;
10 | } | number;
11 | export declare type Offsets = number | {
12 | top: number;
13 | bottom: number;
14 | } | {
15 | top: number;
16 | } | {
17 | bottom: number;
18 | };
19 | export declare type ScrollEvent = Parameters[0];
20 | export declare type Listener = (event: ScrollEvent | undefined, watcher: Watcher) => void;
21 |
--------------------------------------------------------------------------------
/dist/module/src/types.js:
--------------------------------------------------------------------------------
1 | export {};
2 | //# sourceMappingURL=types.js.map
--------------------------------------------------------------------------------
/dist/module/src/types.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/dist/module/src/watcher.d.ts:
--------------------------------------------------------------------------------
1 | import type { ScrollMonitorContainer } from './container.js';
2 | import type { EventName, Listener, Offsets, ScrollEvent, WatchItem } from './types';
3 | export declare class Watcher {
4 | container: ScrollMonitorContainer;
5 | watchItem: WatchItem;
6 | constructor(container: ScrollMonitorContainer, watchItem: WatchItem, offsets: Offsets);
7 | height: number;
8 | top: number;
9 | bottom: number;
10 | offsets: {
11 | top: number;
12 | bottom: number;
13 | };
14 | isInViewport: boolean;
15 | isFullyInViewport: boolean;
16 | isAboveViewport: boolean;
17 | isBelowViewport: boolean;
18 | locked: boolean;
19 | callbacks: Record;
23 | triggerCallbacks: (event: ScrollEvent) => void;
24 | recalculateLocation: () => void;
25 | visibilityChange: (callback: Listener, isOne: boolean) => void;
26 | enterViewport: (callback: Listener, isOne: boolean) => void;
27 | fullyEnterViewport: (callback: Listener, isOne: boolean) => void;
28 | exitViewport: (callback: Listener, isOne: boolean) => void;
29 | partiallyExitViewport: (callback: Listener, isOne: boolean) => void;
30 | locationChange: (callback: Listener, isOne: boolean) => void;
31 | stateChange: (callback: Listener, isOne: boolean) => void;
32 | on(event: EventName, callback: Listener, isOne?: boolean): void;
33 | off(event: EventName, callback: Listener): void;
34 | one(event: EventName, callback: Listener): void;
35 | recalculateSize(): void;
36 | update(): void;
37 | destroy(): void;
38 | lock(): void;
39 | unlock(): void;
40 | }
41 |
--------------------------------------------------------------------------------
/dist/module/src/watcher.js:
--------------------------------------------------------------------------------
1 | import { VISIBILITYCHANGE, ENTERVIEWPORT, FULLYENTERVIEWPORT, EXITVIEWPORT, PARTIALLYEXITVIEWPORT, LOCATIONCHANGE, STATECHANGE, eventTypes, defaultOffsets, } from './constants.js';
2 | var Watcher = /** @class */ (function () {
3 | function Watcher(container, watchItem, offsets) {
4 | this.container = container;
5 | this.watchItem = watchItem;
6 | this.locked = false;
7 | this.callbacks = {};
8 | var self = this;
9 | if (!offsets) {
10 | this.offsets = defaultOffsets;
11 | }
12 | else if (typeof offsets === 'number') {
13 | this.offsets = { top: offsets, bottom: offsets };
14 | }
15 | else {
16 | this.offsets = {
17 | top: 'top' in offsets ? offsets.top : defaultOffsets.top,
18 | bottom: 'bottom' in offsets ? offsets.bottom : defaultOffsets.bottom,
19 | };
20 | }
21 | for (var i = 0, j = eventTypes.length; i < j; i++) {
22 | self.callbacks[eventTypes[i]] = [];
23 | }
24 | this.locked = false;
25 | var wasInViewport;
26 | var wasFullyInViewport;
27 | var wasAboveViewport;
28 | var wasBelowViewport;
29 | var listenerToTriggerListI;
30 | var listener;
31 | var needToTriggerStateChange = false;
32 | function triggerCallbackArray(listeners, event) {
33 | needToTriggerStateChange = true;
34 | if (listeners.length === 0) {
35 | return;
36 | }
37 | listenerToTriggerListI = listeners.length;
38 | while (listenerToTriggerListI--) {
39 | listener = listeners[listenerToTriggerListI];
40 | listener.callback.call(self, event, self);
41 | if (listener.isOne) {
42 | listeners.splice(listenerToTriggerListI, 1);
43 | }
44 | }
45 | }
46 | this.triggerCallbacks = function triggerCallbacks(event) {
47 | if (this.isInViewport && !wasInViewport) {
48 | triggerCallbackArray(this.callbacks[ENTERVIEWPORT], event);
49 | }
50 | if (this.isFullyInViewport && !wasFullyInViewport) {
51 | triggerCallbackArray(this.callbacks[FULLYENTERVIEWPORT], event);
52 | }
53 | if (this.isAboveViewport !== wasAboveViewport &&
54 | this.isBelowViewport !== wasBelowViewport) {
55 | triggerCallbackArray(this.callbacks[VISIBILITYCHANGE], event);
56 | // if you skip completely past this element
57 | if (!wasFullyInViewport && !this.isFullyInViewport) {
58 | triggerCallbackArray(this.callbacks[FULLYENTERVIEWPORT], event);
59 | triggerCallbackArray(this.callbacks[PARTIALLYEXITVIEWPORT], event);
60 | }
61 | if (!wasInViewport && !this.isInViewport) {
62 | triggerCallbackArray(this.callbacks[ENTERVIEWPORT], event);
63 | triggerCallbackArray(this.callbacks[EXITVIEWPORT], event);
64 | }
65 | }
66 | if (!this.isFullyInViewport && wasFullyInViewport) {
67 | triggerCallbackArray(this.callbacks[PARTIALLYEXITVIEWPORT], event);
68 | }
69 | if (!this.isInViewport && wasInViewport) {
70 | triggerCallbackArray(this.callbacks[EXITVIEWPORT], event);
71 | }
72 | if (this.isInViewport !== wasInViewport) {
73 | triggerCallbackArray(this.callbacks[VISIBILITYCHANGE], event);
74 | }
75 | if (needToTriggerStateChange) {
76 | needToTriggerStateChange = false;
77 | triggerCallbackArray(this.callbacks[STATECHANGE], event);
78 | }
79 | wasInViewport = this.isInViewport;
80 | wasFullyInViewport = this.isFullyInViewport;
81 | wasAboveViewport = this.isAboveViewport;
82 | wasBelowViewport = this.isBelowViewport;
83 | };
84 | this.recalculateLocation = function () {
85 | if (this.locked) {
86 | return;
87 | }
88 | var previousTop = this.top;
89 | var previousBottom = this.bottom;
90 | if (this.watchItem.nodeName) {
91 | // a dom element
92 | var cachedDisplay = this.watchItem.style.display;
93 | if (cachedDisplay === 'none') {
94 | this.watchItem.style.display = '';
95 | }
96 | var containerOffset = 0;
97 | var container = this.container;
98 | while (container.containerWatcher) {
99 | containerOffset +=
100 | container.containerWatcher.top -
101 | container.containerWatcher.container.viewportTop;
102 | container = container.containerWatcher.container;
103 | }
104 | var boundingRect = this.watchItem.getBoundingClientRect();
105 | this.top = boundingRect.top + this.container.viewportTop - containerOffset;
106 | this.bottom = boundingRect.bottom + this.container.viewportTop - containerOffset;
107 | if (cachedDisplay === 'none') {
108 | this.watchItem.style.display = cachedDisplay;
109 | }
110 | }
111 | else if (this.watchItem === +this.watchItem) {
112 | // number
113 | if (this.watchItem > 0) {
114 | this.top = this.bottom = this.watchItem;
115 | }
116 | else {
117 | this.top = this.bottom = this.container.documentHeight - this.watchItem;
118 | }
119 | }
120 | else {
121 | // an object with a top and bottom property
122 | this.top = this.watchItem.top;
123 | this.bottom = this.watchItem.bottom;
124 | }
125 | this.top -= this.offsets.top;
126 | this.bottom += this.offsets.bottom;
127 | this.height = this.bottom - this.top;
128 | if ((previousTop !== undefined || previousBottom !== undefined) &&
129 | (this.top !== previousTop || this.bottom !== previousBottom)) {
130 | triggerCallbackArray(this.callbacks[LOCATIONCHANGE], undefined);
131 | }
132 | };
133 | this.recalculateLocation();
134 | this.update();
135 | wasInViewport = this.isInViewport;
136 | wasFullyInViewport = this.isFullyInViewport;
137 | wasAboveViewport = this.isAboveViewport;
138 | wasBelowViewport = this.isBelowViewport;
139 | }
140 | Watcher.prototype.on = function (event, callback, isOne) {
141 | if (isOne === void 0) { isOne = false; }
142 | // trigger the event if it applies to the element right now.
143 | switch (true) {
144 | case event === VISIBILITYCHANGE && !this.isInViewport && this.isAboveViewport:
145 | case event === ENTERVIEWPORT && this.isInViewport:
146 | case event === FULLYENTERVIEWPORT && this.isFullyInViewport:
147 | case event === EXITVIEWPORT && this.isAboveViewport && !this.isInViewport:
148 | case event === PARTIALLYEXITVIEWPORT && this.isInViewport && this.isAboveViewport:
149 | callback.call(this, this);
150 | if (isOne) {
151 | return;
152 | }
153 | }
154 | if (this.callbacks[event]) {
155 | this.callbacks[event].push({ callback: callback, isOne: isOne });
156 | }
157 | else {
158 | throw new Error('Tried to add a scroll monitor listener of type ' +
159 | event +
160 | '. Your options are: ' +
161 | eventTypes.join(', '));
162 | }
163 | };
164 | Watcher.prototype.off = function (event, callback) {
165 | if (this.callbacks[event]) {
166 | for (var i = 0, item; (item = this.callbacks[event][i]); i++) {
167 | if (item.callback === callback) {
168 | this.callbacks[event].splice(i, 1);
169 | break;
170 | }
171 | }
172 | }
173 | else {
174 | throw new Error('Tried to remove a scroll monitor listener of type ' +
175 | event +
176 | '. Your options are: ' +
177 | eventTypes.join(', '));
178 | }
179 | };
180 | Watcher.prototype.one = function (event, callback) {
181 | this.on(event, callback, true);
182 | };
183 | Watcher.prototype.recalculateSize = function () {
184 | if (this.watchItem instanceof HTMLElement) {
185 | this.height = this.watchItem.offsetHeight + this.offsets.top + this.offsets.bottom;
186 | this.bottom = this.top + this.height;
187 | }
188 | };
189 | Watcher.prototype.update = function () {
190 | this.isAboveViewport = this.top < this.container.viewportTop;
191 | this.isBelowViewport = this.bottom > this.container.viewportBottom;
192 | this.isInViewport =
193 | this.top < this.container.viewportBottom && this.bottom > this.container.viewportTop;
194 | this.isFullyInViewport =
195 | (this.top >= this.container.viewportTop &&
196 | this.bottom <= this.container.viewportBottom) ||
197 | (this.isAboveViewport && this.isBelowViewport);
198 | };
199 | Watcher.prototype.destroy = function () {
200 | var index = this.container.watchers.indexOf(this), self = this;
201 | this.container.watchers.splice(index, 1);
202 | self.callbacks = {};
203 | };
204 | // prevent recalculating the element location
205 | Watcher.prototype.lock = function () {
206 | this.locked = true;
207 | };
208 | Watcher.prototype.unlock = function () {
209 | this.locked = false;
210 | };
211 | return Watcher;
212 | }());
213 | export { Watcher };
214 | var eventHandlerFactory = function (type) {
215 | return function (callback, isOne) {
216 | if (isOne === void 0) { isOne = false; }
217 | this.on.call(this, type, callback, isOne);
218 | };
219 | };
220 | for (var i = 0, j = eventTypes.length; i < j; i++) {
221 | var type = eventTypes[i];
222 | Watcher.prototype[type] = eventHandlerFactory(type);
223 | }
224 | //# sourceMappingURL=watcher.js.map
--------------------------------------------------------------------------------
/dist/module/src/watcher.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/watcher.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/umd/index.d.ts:
--------------------------------------------------------------------------------
1 | export { ScrollMonitorContainer } from './src/container.js';
2 | export { Watcher } from './src/watcher.js';
3 | export * from './src/types';
4 | import { ScrollMonitorContainer } from './src/container.js';
5 | declare const scrollMonitor: ScrollMonitorContainer;
6 | export default scrollMonitor;
7 |
--------------------------------------------------------------------------------
/dist/umd/index.js:
--------------------------------------------------------------------------------
1 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
2 | if (k2 === undefined) k2 = k;
3 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4 | }) : (function(o, m, k, k2) {
5 | if (k2 === undefined) k2 = k;
6 | o[k2] = m[k];
7 | }));
8 | var __exportStar = (this && this.__exportStar) || function(m, exports) {
9 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
10 | };
11 | (function (factory) {
12 | if (typeof module === "object" && typeof module.exports === "object") {
13 | var v = factory(require, exports);
14 | if (v !== undefined) module.exports = v;
15 | }
16 | else if (typeof define === "function" && define.amd) {
17 | define(["require", "exports", "./src/container.js", "./src/watcher.js", "./src/types", "./src/constants.js", "./src/container.js"], factory);
18 | }
19 | })(function (require, exports) {
20 | "use strict";
21 | Object.defineProperty(exports, "__esModule", { value: true });
22 | exports.Watcher = exports.ScrollMonitorContainer = void 0;
23 | var container_js_1 = require("./src/container.js");
24 | Object.defineProperty(exports, "ScrollMonitorContainer", { enumerable: true, get: function () { return container_js_1.ScrollMonitorContainer; } });
25 | var watcher_js_1 = require("./src/watcher.js");
26 | Object.defineProperty(exports, "Watcher", { enumerable: true, get: function () { return watcher_js_1.Watcher; } });
27 | __exportStar(require("./src/types"), exports);
28 | var constants_js_1 = require("./src/constants.js");
29 | var container_js_2 = require("./src/container.js");
30 | // this is needed for the type, but if we're not in a browser the only
31 | // way listenToDOM will be called is if you call scrollmonitor.createContainer
32 | // and you can't do that until you have a DOM element.
33 | var scrollMonitor = new container_js_2.ScrollMonitorContainer(constants_js_1.isInBrowser ? document.body : undefined);
34 | if (constants_js_1.isInBrowser) {
35 | scrollMonitor.updateState();
36 | scrollMonitor.listenToDOM();
37 | //@ts-ignore
38 | window.scrollMonitor = scrollMonitor;
39 | }
40 | exports.default = scrollMonitor;
41 | });
42 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/dist/umd/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;IAAA,mDAA4D;IAAnD,sHAAA,sBAAsB,OAAA;IAC/B,+CAA2C;IAAlC,qGAAA,OAAO,OAAA;IAChB,8CAA4B;IAE5B,mDAAiD;IACjD,mDAA4D;IAE5D,sEAAsE;IACtE,8EAA8E;IAC9E,sDAAsD;IACtD,IAAM,aAAa,GAAG,IAAI,qCAAsB,CAC5C,0BAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAE,SAAoC,CACtE,CAAC;IACF,IAAI,0BAAW,EAAE;QACb,aAAa,CAAC,WAAW,EAAE,CAAC;QAC5B,aAAa,CAAC,WAAW,EAAE,CAAC;QAC5B,YAAY;QACZ,MAAM,CAAC,aAAa,GAAG,aAAa,CAAC;KACxC;IAED,kBAAe,aAAa,CAAC"}
--------------------------------------------------------------------------------
/dist/umd/src/constants.d.ts:
--------------------------------------------------------------------------------
1 | export declare const VISIBILITYCHANGE = "visibilityChange";
2 | export declare const ENTERVIEWPORT = "enterViewport";
3 | export declare const FULLYENTERVIEWPORT = "fullyEnterViewport";
4 | export declare const EXITVIEWPORT = "exitViewport";
5 | export declare const PARTIALLYEXITVIEWPORT = "partiallyExitViewport";
6 | export declare const LOCATIONCHANGE = "locationChange";
7 | export declare const STATECHANGE = "stateChange";
8 | export declare const eventTypes: readonly ["visibilityChange", "enterViewport", "fullyEnterViewport", "exitViewport", "partiallyExitViewport", "locationChange", "stateChange"];
9 | export declare const isOnServer: boolean;
10 | export declare const isInBrowser: boolean;
11 | export declare const defaultOffsets: {
12 | readonly top: 0;
13 | readonly bottom: 0;
14 | };
15 |
--------------------------------------------------------------------------------
/dist/umd/src/constants.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof module === "object" && typeof module.exports === "object") {
3 | var v = factory(require, exports);
4 | if (v !== undefined) module.exports = v;
5 | }
6 | else if (typeof define === "function" && define.amd) {
7 | define(["require", "exports"], factory);
8 | }
9 | })(function (require, exports) {
10 | "use strict";
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | exports.defaultOffsets = exports.isInBrowser = exports.isOnServer = exports.eventTypes = exports.STATECHANGE = exports.LOCATIONCHANGE = exports.PARTIALLYEXITVIEWPORT = exports.EXITVIEWPORT = exports.FULLYENTERVIEWPORT = exports.ENTERVIEWPORT = exports.VISIBILITYCHANGE = void 0;
13 | exports.VISIBILITYCHANGE = 'visibilityChange';
14 | exports.ENTERVIEWPORT = 'enterViewport';
15 | exports.FULLYENTERVIEWPORT = 'fullyEnterViewport';
16 | exports.EXITVIEWPORT = 'exitViewport';
17 | exports.PARTIALLYEXITVIEWPORT = 'partiallyExitViewport';
18 | exports.LOCATIONCHANGE = 'locationChange';
19 | exports.STATECHANGE = 'stateChange';
20 | exports.eventTypes = [
21 | exports.VISIBILITYCHANGE,
22 | exports.ENTERVIEWPORT,
23 | exports.FULLYENTERVIEWPORT,
24 | exports.EXITVIEWPORT,
25 | exports.PARTIALLYEXITVIEWPORT,
26 | exports.LOCATIONCHANGE,
27 | exports.STATECHANGE,
28 | ];
29 | exports.isOnServer = typeof window === 'undefined';
30 | exports.isInBrowser = !exports.isOnServer;
31 | exports.defaultOffsets = { top: 0, bottom: 0 };
32 | });
33 | //# sourceMappingURL=constants.js.map
--------------------------------------------------------------------------------
/dist/umd/src/constants.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/constants.ts"],"names":[],"mappings":";;;;;;;;;;;;IAAa,QAAA,gBAAgB,GAAG,kBAAkB,CAAC;IACtC,QAAA,aAAa,GAAG,eAAe,CAAC;IAChC,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;IAC1C,QAAA,YAAY,GAAG,cAAc,CAAC;IAC9B,QAAA,qBAAqB,GAAG,uBAAuB,CAAC;IAChD,QAAA,cAAc,GAAG,gBAAgB,CAAC;IAClC,QAAA,WAAW,GAAG,aAAa,CAAC;IAE5B,QAAA,UAAU,GAAG;QACtB,wBAAgB;QAChB,qBAAa;QACb,0BAAkB;QAClB,oBAAY;QACZ,6BAAqB;QACrB,sBAAc;QACd,mBAAW;KACL,CAAC;IAEE,QAAA,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;IAC3C,QAAA,WAAW,GAAG,CAAC,kBAAU,CAAC;IAE1B,QAAA,cAAc,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAW,CAAC"}
--------------------------------------------------------------------------------
/dist/umd/src/container.d.ts:
--------------------------------------------------------------------------------
1 | import { Offsets, WatchItemInput, ScrollEvent } from './types.js';
2 | import { Watcher } from './watcher.js';
3 | export declare class ScrollMonitorContainer {
4 | item: HTMLElement;
5 | watchers: Watcher[];
6 | viewportTop: number;
7 | viewportBottom: number;
8 | documentHeight: number;
9 | viewportHeight: number;
10 | contentHeight: number;
11 | containerWatcher: Watcher | undefined;
12 | update: () => void;
13 | recalculateLocations: () => void;
14 | eventTypes: readonly ["visibilityChange", "enterViewport", "fullyEnterViewport", "exitViewport", "partiallyExitViewport", "locationChange", "stateChange"];
15 | constructor(item: HTMLElement, parentWatcher?: ScrollMonitorContainer);
16 | listenToDOM(): void;
17 | destroy(): void;
18 | DOMListener(event: ScrollEvent): void;
19 | updateState(): void;
20 | updateAndTriggerWatchers(event: ScrollEvent): void;
21 | createContainer(input: HTMLElement | NodeList | HTMLElement[] | string): ScrollMonitorContainer;
22 | create(input: WatchItemInput, offsets?: Offsets): Watcher;
23 | /**
24 | * @deprecated since version 1.1
25 | */
26 | beget(input: WatchItemInput, offsets?: Offsets): Watcher;
27 | }
28 |
--------------------------------------------------------------------------------
/dist/umd/src/container.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof module === "object" && typeof module.exports === "object") {
3 | var v = factory(require, exports);
4 | if (v !== undefined) module.exports = v;
5 | }
6 | else if (typeof define === "function" && define.amd) {
7 | define(["require", "exports", "./constants.js", "./watcher.js"], factory);
8 | }
9 | })(function (require, exports) {
10 | "use strict";
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | exports.ScrollMonitorContainer = void 0;
13 | var constants_js_1 = require("./constants.js");
14 | var watcher_js_1 = require("./watcher.js");
15 | function getViewportHeight(element) {
16 | if (constants_js_1.isOnServer) {
17 | return 0;
18 | }
19 | if (element === document.body) {
20 | return window.innerHeight || document.documentElement.clientHeight;
21 | }
22 | else {
23 | return element.clientHeight;
24 | }
25 | }
26 | function getContentHeight(element) {
27 | if (constants_js_1.isOnServer) {
28 | return 0;
29 | }
30 | if (element === document.body) {
31 | // jQuery approach
32 | // whichever is greatest
33 | return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight);
34 | }
35 | else {
36 | return element.scrollHeight;
37 | }
38 | }
39 | function scrollTop(element) {
40 | if (constants_js_1.isOnServer) {
41 | return 0;
42 | }
43 | if (element === document.body) {
44 | return (window.pageYOffset ||
45 | (document.documentElement && document.documentElement.scrollTop) ||
46 | document.body.scrollTop);
47 | }
48 | else {
49 | return element.scrollTop;
50 | }
51 | }
52 | var browserSupportsPassive = false;
53 | if (constants_js_1.isInBrowser) {
54 | try {
55 | var opts = Object.defineProperty({}, 'passive', {
56 | get: function () {
57 | browserSupportsPassive = true;
58 | },
59 | });
60 | window.addEventListener('test', null, opts);
61 | }
62 | catch (e) { }
63 | }
64 | var useCapture = browserSupportsPassive ? { capture: false, passive: true } : false;
65 | var ScrollMonitorContainer = /** @class */ (function () {
66 | function ScrollMonitorContainer(item, parentWatcher) {
67 | this.eventTypes = constants_js_1.eventTypes;
68 | var self = this;
69 | this.item = item;
70 | this.watchers = [];
71 | this.viewportTop = null;
72 | this.viewportBottom = null;
73 | this.documentHeight = getContentHeight(item);
74 | this.viewportHeight = getViewportHeight(item);
75 | this.DOMListener = function () {
76 | ScrollMonitorContainer.prototype.DOMListener.apply(self, arguments);
77 | };
78 | if (parentWatcher) {
79 | this.containerWatcher = parentWatcher.create(item);
80 | }
81 | var previousDocumentHeight;
82 | var calculateViewportI;
83 | function calculateViewport() {
84 | self.viewportTop = scrollTop(item);
85 | self.viewportBottom = self.viewportTop + self.viewportHeight;
86 | self.documentHeight = getContentHeight(item);
87 | if (self.documentHeight !== previousDocumentHeight) {
88 | calculateViewportI = self.watchers.length;
89 | while (calculateViewportI--) {
90 | self.watchers[calculateViewportI].recalculateLocation();
91 | }
92 | previousDocumentHeight = self.documentHeight;
93 | }
94 | }
95 | var updateAndTriggerWatchersI;
96 | function updateAndTriggerWatchers() {
97 | // update all watchers then trigger the events so one can rely on another being up to date.
98 | updateAndTriggerWatchersI = self.watchers.length;
99 | while (updateAndTriggerWatchersI--) {
100 | self.watchers[updateAndTriggerWatchersI].update();
101 | }
102 | updateAndTriggerWatchersI = self.watchers.length;
103 | while (updateAndTriggerWatchersI--) {
104 | self.watchers[updateAndTriggerWatchersI].triggerCallbacks(undefined);
105 | }
106 | }
107 | this.update = function () {
108 | calculateViewport();
109 | updateAndTriggerWatchers();
110 | };
111 | this.recalculateLocations = function () {
112 | this.documentHeight = 0;
113 | this.update();
114 | };
115 | }
116 | ScrollMonitorContainer.prototype.listenToDOM = function () {
117 | if (constants_js_1.isInBrowser) {
118 | if (this.item === document.body) {
119 | window.addEventListener('scroll', this.DOMListener, useCapture);
120 | }
121 | else {
122 | this.item.addEventListener('scroll', this.DOMListener, useCapture);
123 | }
124 | window.addEventListener('resize', this.DOMListener);
125 | this.destroy = function () {
126 | if (this.item === document.body) {
127 | window.removeEventListener('scroll', this.DOMListener, useCapture);
128 | this.containerWatcher.destroy();
129 | }
130 | else {
131 | this.item.removeEventListener('scroll', this.DOMListener, useCapture);
132 | }
133 | window.removeEventListener('resize', this.DOMListener);
134 | };
135 | }
136 | };
137 | ScrollMonitorContainer.prototype.destroy = function () {
138 | // noop, override for your own purposes.
139 | // in listenToDOM, for example.
140 | };
141 | ScrollMonitorContainer.prototype.DOMListener = function (event) {
142 | //alert('got scroll');
143 | this.updateState();
144 | this.updateAndTriggerWatchers(event);
145 | };
146 | ScrollMonitorContainer.prototype.updateState = function () {
147 | var viewportTop = scrollTop(this.item);
148 | var viewportHeight = getViewportHeight(this.item);
149 | var contentHeight = getContentHeight(this.item);
150 | var needsRecalcuate = viewportHeight !== this.viewportHeight || contentHeight !== this.contentHeight;
151 | this.viewportTop = viewportTop;
152 | this.viewportHeight = viewportHeight;
153 | this.viewportBottom = viewportTop + viewportHeight;
154 | this.contentHeight = contentHeight;
155 | if (needsRecalcuate) {
156 | var i = this.watchers.length;
157 | while (i--) {
158 | this.watchers[i].recalculateLocation();
159 | }
160 | }
161 | };
162 | ScrollMonitorContainer.prototype.updateAndTriggerWatchers = function (event) {
163 | var i = this.watchers.length;
164 | while (i--) {
165 | this.watchers[i].update();
166 | }
167 | i = this.watchers.length;
168 | while (i--) {
169 | this.watchers[i].triggerCallbacks(event);
170 | }
171 | };
172 | ScrollMonitorContainer.prototype.createContainer = function (input) {
173 | var item;
174 | if (typeof input === 'string') {
175 | item = document.querySelector(input);
176 | }
177 | else if (Array.isArray(input) || input instanceof NodeList) {
178 | item = input[0];
179 | }
180 | else {
181 | item = input;
182 | }
183 | var container = new ScrollMonitorContainer(item, this);
184 | this.updateState();
185 | container.listenToDOM();
186 | return container;
187 | };
188 | ScrollMonitorContainer.prototype.create = function (input, offsets) {
189 | var item;
190 | if (typeof item === 'string') {
191 | item = document.querySelector(item);
192 | }
193 | else if (Array.isArray(input) || input instanceof NodeList) {
194 | item = input[0];
195 | }
196 | else {
197 | item = input;
198 | }
199 | var watcher = new watcher_js_1.Watcher(this, item, offsets);
200 | this.watchers.push(watcher);
201 | return watcher;
202 | };
203 | /**
204 | * @deprecated since version 1.1
205 | */
206 | ScrollMonitorContainer.prototype.beget = function (input, offsets) {
207 | return this.create(input, offsets);
208 | };
209 | return ScrollMonitorContainer;
210 | }());
211 | exports.ScrollMonitorContainer = ScrollMonitorContainer;
212 | });
213 | //# sourceMappingURL=container.js.map
--------------------------------------------------------------------------------
/dist/umd/src/container.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"container.js","sourceRoot":"","sources":["../../../src/container.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/umd/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import type { Watcher } from './watcher.js';
2 | export declare type EventName = 'visibilityChange' | 'enterViewport' | 'fullyEnterViewport' | 'exitViewport' | 'partiallyExitViewport' | 'locationChange' | 'stateChange';
3 | export declare type WatchItemInput = HTMLElement | {
4 | top: number;
5 | bottom: number;
6 | } | number | NodeList | HTMLElement[] | string;
7 | export declare type WatchItem = HTMLElement | {
8 | top: number;
9 | bottom: number;
10 | } | number;
11 | export declare type Offsets = number | {
12 | top: number;
13 | bottom: number;
14 | } | {
15 | top: number;
16 | } | {
17 | bottom: number;
18 | };
19 | export declare type ScrollEvent = Parameters[0];
20 | export declare type Listener = (event: ScrollEvent | undefined, watcher: Watcher) => void;
21 |
--------------------------------------------------------------------------------
/dist/umd/src/types.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof module === "object" && typeof module.exports === "object") {
3 | var v = factory(require, exports);
4 | if (v !== undefined) module.exports = v;
5 | }
6 | else if (typeof define === "function" && define.amd) {
7 | define(["require", "exports"], factory);
8 | }
9 | })(function (require, exports) {
10 | "use strict";
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | });
13 | //# sourceMappingURL=types.js.map
--------------------------------------------------------------------------------
/dist/umd/src/types.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/dist/umd/src/watcher.d.ts:
--------------------------------------------------------------------------------
1 | import type { ScrollMonitorContainer } from './container.js';
2 | import type { EventName, Listener, Offsets, ScrollEvent, WatchItem } from './types';
3 | export declare class Watcher {
4 | container: ScrollMonitorContainer;
5 | watchItem: WatchItem;
6 | constructor(container: ScrollMonitorContainer, watchItem: WatchItem, offsets: Offsets);
7 | height: number;
8 | top: number;
9 | bottom: number;
10 | offsets: {
11 | top: number;
12 | bottom: number;
13 | };
14 | isInViewport: boolean;
15 | isFullyInViewport: boolean;
16 | isAboveViewport: boolean;
17 | isBelowViewport: boolean;
18 | locked: boolean;
19 | callbacks: Record;
23 | triggerCallbacks: (event: ScrollEvent) => void;
24 | recalculateLocation: () => void;
25 | visibilityChange: (callback: Listener, isOne: boolean) => void;
26 | enterViewport: (callback: Listener, isOne: boolean) => void;
27 | fullyEnterViewport: (callback: Listener, isOne: boolean) => void;
28 | exitViewport: (callback: Listener, isOne: boolean) => void;
29 | partiallyExitViewport: (callback: Listener, isOne: boolean) => void;
30 | locationChange: (callback: Listener, isOne: boolean) => void;
31 | stateChange: (callback: Listener, isOne: boolean) => void;
32 | on(event: EventName, callback: Listener, isOne?: boolean): void;
33 | off(event: EventName, callback: Listener): void;
34 | one(event: EventName, callback: Listener): void;
35 | recalculateSize(): void;
36 | update(): void;
37 | destroy(): void;
38 | lock(): void;
39 | unlock(): void;
40 | }
41 |
--------------------------------------------------------------------------------
/dist/umd/src/watcher.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | if (typeof module === "object" && typeof module.exports === "object") {
3 | var v = factory(require, exports);
4 | if (v !== undefined) module.exports = v;
5 | }
6 | else if (typeof define === "function" && define.amd) {
7 | define(["require", "exports", "./constants.js"], factory);
8 | }
9 | })(function (require, exports) {
10 | "use strict";
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | exports.Watcher = void 0;
13 | var constants_js_1 = require("./constants.js");
14 | var Watcher = /** @class */ (function () {
15 | function Watcher(container, watchItem, offsets) {
16 | this.container = container;
17 | this.watchItem = watchItem;
18 | this.locked = false;
19 | this.callbacks = {};
20 | var self = this;
21 | if (!offsets) {
22 | this.offsets = constants_js_1.defaultOffsets;
23 | }
24 | else if (typeof offsets === 'number') {
25 | this.offsets = { top: offsets, bottom: offsets };
26 | }
27 | else {
28 | this.offsets = {
29 | top: 'top' in offsets ? offsets.top : constants_js_1.defaultOffsets.top,
30 | bottom: 'bottom' in offsets ? offsets.bottom : constants_js_1.defaultOffsets.bottom,
31 | };
32 | }
33 | for (var i = 0, j = constants_js_1.eventTypes.length; i < j; i++) {
34 | self.callbacks[constants_js_1.eventTypes[i]] = [];
35 | }
36 | this.locked = false;
37 | var wasInViewport;
38 | var wasFullyInViewport;
39 | var wasAboveViewport;
40 | var wasBelowViewport;
41 | var listenerToTriggerListI;
42 | var listener;
43 | var needToTriggerStateChange = false;
44 | function triggerCallbackArray(listeners, event) {
45 | needToTriggerStateChange = true;
46 | if (listeners.length === 0) {
47 | return;
48 | }
49 | listenerToTriggerListI = listeners.length;
50 | while (listenerToTriggerListI--) {
51 | listener = listeners[listenerToTriggerListI];
52 | listener.callback.call(self, event, self);
53 | if (listener.isOne) {
54 | listeners.splice(listenerToTriggerListI, 1);
55 | }
56 | }
57 | }
58 | this.triggerCallbacks = function triggerCallbacks(event) {
59 | if (this.isInViewport && !wasInViewport) {
60 | triggerCallbackArray(this.callbacks[constants_js_1.ENTERVIEWPORT], event);
61 | }
62 | if (this.isFullyInViewport && !wasFullyInViewport) {
63 | triggerCallbackArray(this.callbacks[constants_js_1.FULLYENTERVIEWPORT], event);
64 | }
65 | if (this.isAboveViewport !== wasAboveViewport &&
66 | this.isBelowViewport !== wasBelowViewport) {
67 | triggerCallbackArray(this.callbacks[constants_js_1.VISIBILITYCHANGE], event);
68 | // if you skip completely past this element
69 | if (!wasFullyInViewport && !this.isFullyInViewport) {
70 | triggerCallbackArray(this.callbacks[constants_js_1.FULLYENTERVIEWPORT], event);
71 | triggerCallbackArray(this.callbacks[constants_js_1.PARTIALLYEXITVIEWPORT], event);
72 | }
73 | if (!wasInViewport && !this.isInViewport) {
74 | triggerCallbackArray(this.callbacks[constants_js_1.ENTERVIEWPORT], event);
75 | triggerCallbackArray(this.callbacks[constants_js_1.EXITVIEWPORT], event);
76 | }
77 | }
78 | if (!this.isFullyInViewport && wasFullyInViewport) {
79 | triggerCallbackArray(this.callbacks[constants_js_1.PARTIALLYEXITVIEWPORT], event);
80 | }
81 | if (!this.isInViewport && wasInViewport) {
82 | triggerCallbackArray(this.callbacks[constants_js_1.EXITVIEWPORT], event);
83 | }
84 | if (this.isInViewport !== wasInViewport) {
85 | triggerCallbackArray(this.callbacks[constants_js_1.VISIBILITYCHANGE], event);
86 | }
87 | if (needToTriggerStateChange) {
88 | needToTriggerStateChange = false;
89 | triggerCallbackArray(this.callbacks[constants_js_1.STATECHANGE], event);
90 | }
91 | wasInViewport = this.isInViewport;
92 | wasFullyInViewport = this.isFullyInViewport;
93 | wasAboveViewport = this.isAboveViewport;
94 | wasBelowViewport = this.isBelowViewport;
95 | };
96 | this.recalculateLocation = function () {
97 | if (this.locked) {
98 | return;
99 | }
100 | var previousTop = this.top;
101 | var previousBottom = this.bottom;
102 | if (this.watchItem.nodeName) {
103 | // a dom element
104 | var cachedDisplay = this.watchItem.style.display;
105 | if (cachedDisplay === 'none') {
106 | this.watchItem.style.display = '';
107 | }
108 | var containerOffset = 0;
109 | var container = this.container;
110 | while (container.containerWatcher) {
111 | containerOffset +=
112 | container.containerWatcher.top -
113 | container.containerWatcher.container.viewportTop;
114 | container = container.containerWatcher.container;
115 | }
116 | var boundingRect = this.watchItem.getBoundingClientRect();
117 | this.top = boundingRect.top + this.container.viewportTop - containerOffset;
118 | this.bottom = boundingRect.bottom + this.container.viewportTop - containerOffset;
119 | if (cachedDisplay === 'none') {
120 | this.watchItem.style.display = cachedDisplay;
121 | }
122 | }
123 | else if (this.watchItem === +this.watchItem) {
124 | // number
125 | if (this.watchItem > 0) {
126 | this.top = this.bottom = this.watchItem;
127 | }
128 | else {
129 | this.top = this.bottom = this.container.documentHeight - this.watchItem;
130 | }
131 | }
132 | else {
133 | // an object with a top and bottom property
134 | this.top = this.watchItem.top;
135 | this.bottom = this.watchItem.bottom;
136 | }
137 | this.top -= this.offsets.top;
138 | this.bottom += this.offsets.bottom;
139 | this.height = this.bottom - this.top;
140 | if ((previousTop !== undefined || previousBottom !== undefined) &&
141 | (this.top !== previousTop || this.bottom !== previousBottom)) {
142 | triggerCallbackArray(this.callbacks[constants_js_1.LOCATIONCHANGE], undefined);
143 | }
144 | };
145 | this.recalculateLocation();
146 | this.update();
147 | wasInViewport = this.isInViewport;
148 | wasFullyInViewport = this.isFullyInViewport;
149 | wasAboveViewport = this.isAboveViewport;
150 | wasBelowViewport = this.isBelowViewport;
151 | }
152 | Watcher.prototype.on = function (event, callback, isOne) {
153 | if (isOne === void 0) { isOne = false; }
154 | // trigger the event if it applies to the element right now.
155 | switch (true) {
156 | case event === constants_js_1.VISIBILITYCHANGE && !this.isInViewport && this.isAboveViewport:
157 | case event === constants_js_1.ENTERVIEWPORT && this.isInViewport:
158 | case event === constants_js_1.FULLYENTERVIEWPORT && this.isFullyInViewport:
159 | case event === constants_js_1.EXITVIEWPORT && this.isAboveViewport && !this.isInViewport:
160 | case event === constants_js_1.PARTIALLYEXITVIEWPORT && this.isInViewport && this.isAboveViewport:
161 | callback.call(this, this);
162 | if (isOne) {
163 | return;
164 | }
165 | }
166 | if (this.callbacks[event]) {
167 | this.callbacks[event].push({ callback: callback, isOne: isOne });
168 | }
169 | else {
170 | throw new Error('Tried to add a scroll monitor listener of type ' +
171 | event +
172 | '. Your options are: ' +
173 | constants_js_1.eventTypes.join(', '));
174 | }
175 | };
176 | Watcher.prototype.off = function (event, callback) {
177 | if (this.callbacks[event]) {
178 | for (var i = 0, item; (item = this.callbacks[event][i]); i++) {
179 | if (item.callback === callback) {
180 | this.callbacks[event].splice(i, 1);
181 | break;
182 | }
183 | }
184 | }
185 | else {
186 | throw new Error('Tried to remove a scroll monitor listener of type ' +
187 | event +
188 | '. Your options are: ' +
189 | constants_js_1.eventTypes.join(', '));
190 | }
191 | };
192 | Watcher.prototype.one = function (event, callback) {
193 | this.on(event, callback, true);
194 | };
195 | Watcher.prototype.recalculateSize = function () {
196 | if (this.watchItem instanceof HTMLElement) {
197 | this.height = this.watchItem.offsetHeight + this.offsets.top + this.offsets.bottom;
198 | this.bottom = this.top + this.height;
199 | }
200 | };
201 | Watcher.prototype.update = function () {
202 | this.isAboveViewport = this.top < this.container.viewportTop;
203 | this.isBelowViewport = this.bottom > this.container.viewportBottom;
204 | this.isInViewport =
205 | this.top < this.container.viewportBottom && this.bottom > this.container.viewportTop;
206 | this.isFullyInViewport =
207 | (this.top >= this.container.viewportTop &&
208 | this.bottom <= this.container.viewportBottom) ||
209 | (this.isAboveViewport && this.isBelowViewport);
210 | };
211 | Watcher.prototype.destroy = function () {
212 | var index = this.container.watchers.indexOf(this), self = this;
213 | this.container.watchers.splice(index, 1);
214 | self.callbacks = {};
215 | };
216 | // prevent recalculating the element location
217 | Watcher.prototype.lock = function () {
218 | this.locked = true;
219 | };
220 | Watcher.prototype.unlock = function () {
221 | this.locked = false;
222 | };
223 | return Watcher;
224 | }());
225 | exports.Watcher = Watcher;
226 | var eventHandlerFactory = function (type) {
227 | return function (callback, isOne) {
228 | if (isOne === void 0) { isOne = false; }
229 | this.on.call(this, type, callback, isOne);
230 | };
231 | };
232 | for (var i = 0, j = constants_js_1.eventTypes.length; i < j; i++) {
233 | var type = constants_js_1.eventTypes[i];
234 | Watcher.prototype[type] = eventHandlerFactory(type);
235 | }
236 | });
237 | //# sourceMappingURL=watcher.js.map
--------------------------------------------------------------------------------
/dist/umd/src/watcher.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/watcher.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export { ScrollMonitorContainer } from './src/container.js';
2 | export { Watcher } from './src/watcher.js';
3 | export * from './src/types';
4 |
5 | import { isInBrowser } from './src/constants.js';
6 | import { ScrollMonitorContainer } from './src/container.js';
7 |
8 | // this is needed for the type, but if we're not in a browser the only
9 | // way listenToDOM will be called is if you call scrollmonitor.createContainer
10 | // and you can't do that until you have a DOM element.
11 | const scrollMonitor = new ScrollMonitorContainer(
12 | isInBrowser ? document.body : (undefined as unknown as HTMLElement)
13 | );
14 | if (isInBrowser) {
15 | scrollMonitor.updateState();
16 | scrollMonitor.listenToDOM();
17 | //@ts-ignore
18 | window.scrollMonitor = scrollMonitor;
19 | }
20 |
21 | export default scrollMonitor;
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scrollmonitor",
3 | "version": "1.2.11",
4 | "author": "Stu Kabakoff ",
5 | "description": "A simple and fast API to monitor DOM elements as you scroll",
6 | "contributors": [
7 | {
8 | "name": "Stu Kabakoff",
9 | "email": "stukabakoff@gmail.com"
10 | },
11 | {
12 | "name": "Terrence Lee",
13 | "email": "mr.lee@terrenceishere.com"
14 | },
15 | {
16 | "name": "Roman Kalyakin",
17 | "email": "roman@kalyakin.com"
18 | }
19 | ],
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/stutrek/scrollMonitor.git"
23 | },
24 | "keywords": [
25 | "scroll",
26 | "dom"
27 | ],
28 | "scripts": {
29 | "test": "tsc testEntry.ts --outDir ./testOutput --module es6 && testem ci",
30 | "start": "tsc testEntry.ts --outDir ./testOutput --module es6 && testem",
31 | "build": "rm -rf dist; tsc; tsc --outDir ./dist/module --module es6"
32 | },
33 | "license": "MIT",
34 | "main": "./dist/umd/index.js",
35 | "module": "./dist/module/index.js",
36 | "dependencies": {},
37 | "devDependencies": {
38 | "prettier": "^2.4.1",
39 | "sinon": "^1.17.6",
40 | "testem": "github:stutrek/testem",
41 | "typescript": "^4.4.4"
42 | },
43 | "spm": {
44 | "main": "scrollMonitor.js"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const VISIBILITYCHANGE = 'visibilityChange';
2 | export const ENTERVIEWPORT = 'enterViewport';
3 | export const FULLYENTERVIEWPORT = 'fullyEnterViewport';
4 | export const EXITVIEWPORT = 'exitViewport';
5 | export const PARTIALLYEXITVIEWPORT = 'partiallyExitViewport';
6 | export const LOCATIONCHANGE = 'locationChange';
7 | export const STATECHANGE = 'stateChange';
8 |
9 | export const eventTypes = [
10 | VISIBILITYCHANGE,
11 | ENTERVIEWPORT,
12 | FULLYENTERVIEWPORT,
13 | EXITVIEWPORT,
14 | PARTIALLYEXITVIEWPORT,
15 | LOCATIONCHANGE,
16 | STATECHANGE,
17 | ] as const;
18 |
19 | export const isOnServer = typeof window === 'undefined';
20 | export const isInBrowser = !isOnServer;
21 |
22 | export const defaultOffsets = { top: 0, bottom: 0 } as const;
23 |
--------------------------------------------------------------------------------
/src/container.ts:
--------------------------------------------------------------------------------
1 | import { isOnServer, isInBrowser, eventTypes } from './constants.js';
2 | import { WatchItem, Offsets, WatchItemInput, ScrollEvent } from './types.js';
3 | import { Watcher } from './watcher.js';
4 |
5 | function getViewportHeight(element: HTMLElement) {
6 | if (isOnServer) {
7 | return 0;
8 | }
9 | if (element === document.body) {
10 | return window.innerHeight || document.documentElement.clientHeight;
11 | } else {
12 | return element.clientHeight;
13 | }
14 | }
15 |
16 | function getContentHeight(element: HTMLElement) {
17 | if (isOnServer) {
18 | return 0;
19 | }
20 |
21 | if (element === document.body) {
22 | // jQuery approach
23 | // whichever is greatest
24 | return Math.max(
25 | document.body.scrollHeight,
26 | document.documentElement.scrollHeight,
27 | document.body.offsetHeight,
28 | document.documentElement.offsetHeight,
29 | document.documentElement.clientHeight
30 | );
31 | } else {
32 | return element.scrollHeight;
33 | }
34 | }
35 |
36 | function scrollTop(element: HTMLElement) {
37 | if (isOnServer) {
38 | return 0;
39 | }
40 | if (element === document.body) {
41 | return (
42 | window.pageYOffset ||
43 | (document.documentElement && document.documentElement.scrollTop) ||
44 | document.body.scrollTop
45 | );
46 | } else {
47 | return element.scrollTop;
48 | }
49 | }
50 |
51 | var browserSupportsPassive = false;
52 | if (isInBrowser) {
53 | try {
54 | var opts = Object.defineProperty({}, 'passive', {
55 | get: function () {
56 | browserSupportsPassive = true;
57 | },
58 | });
59 | window.addEventListener('test', null, opts);
60 | } catch (e) {}
61 | }
62 | const useCapture = browserSupportsPassive ? { capture: false, passive: true } : false;
63 |
64 | export class ScrollMonitorContainer {
65 | item: HTMLElement;
66 | watchers: Watcher[];
67 |
68 | viewportTop: number;
69 | viewportBottom: number;
70 | documentHeight: number;
71 | viewportHeight: number;
72 | contentHeight: number;
73 |
74 | containerWatcher: Watcher | undefined;
75 |
76 | update: () => void;
77 | recalculateLocations: () => void;
78 |
79 | eventTypes = eventTypes;
80 |
81 | constructor(item: HTMLElement, parentWatcher?: ScrollMonitorContainer) {
82 | var self = this;
83 |
84 | this.item = item;
85 | this.watchers = [];
86 | this.viewportTop = null;
87 | this.viewportBottom = null;
88 | this.documentHeight = getContentHeight(item);
89 | this.viewportHeight = getViewportHeight(item);
90 | this.DOMListener = function () {
91 | ScrollMonitorContainer.prototype.DOMListener.apply(self, arguments);
92 | };
93 |
94 | if (parentWatcher) {
95 | this.containerWatcher = parentWatcher.create(item);
96 | }
97 |
98 | var previousDocumentHeight: number;
99 |
100 | var calculateViewportI;
101 | function calculateViewport() {
102 | self.viewportTop = scrollTop(item);
103 | self.viewportBottom = self.viewportTop + self.viewportHeight;
104 | self.documentHeight = getContentHeight(item);
105 | if (self.documentHeight !== previousDocumentHeight) {
106 | calculateViewportI = self.watchers.length;
107 | while (calculateViewportI--) {
108 | self.watchers[calculateViewportI].recalculateLocation();
109 | }
110 | previousDocumentHeight = self.documentHeight;
111 | }
112 | }
113 |
114 | var updateAndTriggerWatchersI;
115 | function updateAndTriggerWatchers() {
116 | // update all watchers then trigger the events so one can rely on another being up to date.
117 | updateAndTriggerWatchersI = self.watchers.length;
118 | while (updateAndTriggerWatchersI--) {
119 | self.watchers[updateAndTriggerWatchersI].update();
120 | }
121 |
122 | updateAndTriggerWatchersI = self.watchers.length;
123 | while (updateAndTriggerWatchersI--) {
124 | self.watchers[updateAndTriggerWatchersI].triggerCallbacks(undefined);
125 | }
126 | }
127 |
128 | this.update = function () {
129 | calculateViewport();
130 | updateAndTriggerWatchers();
131 | };
132 | this.recalculateLocations = function () {
133 | this.documentHeight = 0;
134 | this.update();
135 | };
136 | }
137 |
138 | listenToDOM() {
139 | if (isInBrowser) {
140 | if (this.item === document.body) {
141 | window.addEventListener('scroll', this.DOMListener, useCapture);
142 | } else {
143 | this.item.addEventListener('scroll', this.DOMListener, useCapture);
144 | }
145 | window.addEventListener('resize', this.DOMListener);
146 |
147 | this.destroy = function () {
148 | if (this.item === document.body) {
149 | window.removeEventListener('scroll', this.DOMListener, useCapture);
150 | this.containerWatcher.destroy();
151 | } else {
152 | this.item.removeEventListener('scroll', this.DOMListener, useCapture);
153 | }
154 | window.removeEventListener('resize', this.DOMListener);
155 | };
156 | }
157 | }
158 |
159 | destroy() {
160 | // noop, override for your own purposes.
161 | // in listenToDOM, for example.
162 | }
163 |
164 | DOMListener(event: ScrollEvent) {
165 | //alert('got scroll');
166 | this.updateState();
167 | this.updateAndTriggerWatchers(event);
168 | }
169 |
170 | updateState() {
171 | var viewportTop = scrollTop(this.item);
172 | var viewportHeight = getViewportHeight(this.item);
173 | var contentHeight = getContentHeight(this.item);
174 |
175 | var needsRecalcuate =
176 | viewportHeight !== this.viewportHeight || contentHeight !== this.contentHeight;
177 |
178 | this.viewportTop = viewportTop;
179 | this.viewportHeight = viewportHeight;
180 | this.viewportBottom = viewportTop + viewportHeight;
181 | this.contentHeight = contentHeight;
182 |
183 | if (needsRecalcuate) {
184 | let i = this.watchers.length;
185 | while (i--) {
186 | this.watchers[i].recalculateLocation();
187 | }
188 | }
189 | }
190 |
191 | updateAndTriggerWatchers(event: ScrollEvent) {
192 | let i = this.watchers.length;
193 | while (i--) {
194 | this.watchers[i].update();
195 | }
196 |
197 | i = this.watchers.length;
198 | while (i--) {
199 | this.watchers[i].triggerCallbacks(event);
200 | }
201 | }
202 |
203 | createContainer(input: HTMLElement | NodeList | HTMLElement[] | string) {
204 | let item: HTMLElement;
205 | if (typeof input === 'string') {
206 | item = document.querySelector(input) as HTMLElement;
207 | } else if (Array.isArray(input) || input instanceof NodeList) {
208 | item = input[0] as HTMLElement;
209 | } else {
210 | item = input;
211 | }
212 | var container = new ScrollMonitorContainer(item, this);
213 | this.updateState();
214 | container.listenToDOM();
215 | return container;
216 | }
217 |
218 | create(input: WatchItemInput, offsets?: Offsets) {
219 | let item: WatchItem;
220 | if (typeof item === 'string') {
221 | item = document.querySelector(item);
222 | } else if (Array.isArray(input) || input instanceof NodeList) {
223 | item = input[0] as HTMLElement;
224 | } else {
225 | item = input as WatchItem;
226 | }
227 | var watcher = new Watcher(this, item, offsets);
228 | this.watchers.push(watcher);
229 | return watcher;
230 | }
231 |
232 | /**
233 | * @deprecated since version 1.1
234 | */
235 | beget(input: WatchItemInput, offsets?: Offsets) {
236 | return this.create(input, offsets);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Watcher } from './watcher.js';
2 |
3 | export type EventName =
4 | | 'visibilityChange'
5 | | 'enterViewport'
6 | | 'fullyEnterViewport'
7 | | 'exitViewport'
8 | | 'partiallyExitViewport'
9 | | 'locationChange'
10 | | 'stateChange';
11 |
12 | export type WatchItemInput =
13 | | HTMLElement
14 | | { top: number; bottom: number }
15 | | number
16 | | NodeList
17 | | HTMLElement[]
18 | | string;
19 |
20 | export type WatchItem = HTMLElement | { top: number; bottom: number } | number;
21 |
22 | export type Offsets =
23 | | number
24 | | { top: number; bottom: number }
25 | | { top: number }
26 | | { bottom: number };
27 |
28 | export type ScrollEvent = Parameters[0];
29 |
30 | export type Listener = (event: ScrollEvent | undefined, watcher: Watcher) => void;
31 |
--------------------------------------------------------------------------------
/src/watcher.ts:
--------------------------------------------------------------------------------
1 | import {
2 | VISIBILITYCHANGE,
3 | ENTERVIEWPORT,
4 | FULLYENTERVIEWPORT,
5 | EXITVIEWPORT,
6 | PARTIALLYEXITVIEWPORT,
7 | LOCATIONCHANGE,
8 | STATECHANGE,
9 | eventTypes,
10 | defaultOffsets,
11 | } from './constants.js';
12 |
13 | import type { ScrollMonitorContainer } from './container.js';
14 | import type { EventName, Listener, Offsets, ScrollEvent, WatchItem, WatchItemInput } from './types';
15 |
16 | type ListenerItem = {
17 | callback: Listener;
18 | isOne: boolean;
19 | };
20 |
21 | export class Watcher {
22 | constructor(
23 | public container: ScrollMonitorContainer,
24 | public watchItem: WatchItem,
25 | offsets: Offsets
26 | ) {
27 | var self = this;
28 |
29 | if (!offsets) {
30 | this.offsets = defaultOffsets;
31 | } else if (typeof offsets === 'number') {
32 | this.offsets = { top: offsets, bottom: offsets };
33 | } else {
34 | this.offsets = {
35 | top: 'top' in offsets ? offsets.top : defaultOffsets.top,
36 | bottom: 'bottom' in offsets ? offsets.bottom : defaultOffsets.bottom,
37 | };
38 | }
39 |
40 | for (var i = 0, j = eventTypes.length; i < j; i++) {
41 | self.callbacks[eventTypes[i]] = [];
42 | }
43 |
44 | this.locked = false;
45 |
46 | var wasInViewport: boolean;
47 | var wasFullyInViewport: boolean;
48 | var wasAboveViewport: boolean;
49 | var wasBelowViewport: boolean;
50 |
51 | var listenerToTriggerListI;
52 | var listener;
53 | let needToTriggerStateChange = false;
54 | function triggerCallbackArray(listeners: ListenerItem[], event: ScrollEvent) {
55 | needToTriggerStateChange = true;
56 | if (listeners.length === 0) {
57 | return;
58 | }
59 | listenerToTriggerListI = listeners.length;
60 | while (listenerToTriggerListI--) {
61 | listener = listeners[listenerToTriggerListI];
62 | listener.callback.call(self, event, self);
63 | if (listener.isOne) {
64 | listeners.splice(listenerToTriggerListI, 1);
65 | }
66 | }
67 | }
68 | this.triggerCallbacks = function triggerCallbacks(event: ScrollEvent) {
69 | if (this.isInViewport && !wasInViewport) {
70 | triggerCallbackArray(this.callbacks[ENTERVIEWPORT], event);
71 | }
72 | if (this.isFullyInViewport && !wasFullyInViewport) {
73 | triggerCallbackArray(this.callbacks[FULLYENTERVIEWPORT], event);
74 | }
75 |
76 | if (
77 | this.isAboveViewport !== wasAboveViewport &&
78 | this.isBelowViewport !== wasBelowViewport
79 | ) {
80 | triggerCallbackArray(this.callbacks[VISIBILITYCHANGE], event);
81 |
82 | // if you skip completely past this element
83 | if (!wasFullyInViewport && !this.isFullyInViewport) {
84 | triggerCallbackArray(this.callbacks[FULLYENTERVIEWPORT], event);
85 | triggerCallbackArray(this.callbacks[PARTIALLYEXITVIEWPORT], event);
86 | }
87 | if (!wasInViewport && !this.isInViewport) {
88 | triggerCallbackArray(this.callbacks[ENTERVIEWPORT], event);
89 | triggerCallbackArray(this.callbacks[EXITVIEWPORT], event);
90 | }
91 | }
92 |
93 | if (!this.isFullyInViewport && wasFullyInViewport) {
94 | triggerCallbackArray(this.callbacks[PARTIALLYEXITVIEWPORT], event);
95 | }
96 | if (!this.isInViewport && wasInViewport) {
97 | triggerCallbackArray(this.callbacks[EXITVIEWPORT], event);
98 | }
99 | if (this.isInViewport !== wasInViewport) {
100 | triggerCallbackArray(this.callbacks[VISIBILITYCHANGE], event);
101 | }
102 | if (needToTriggerStateChange) {
103 | needToTriggerStateChange = false;
104 | triggerCallbackArray(this.callbacks[STATECHANGE], event);
105 | }
106 |
107 | wasInViewport = this.isInViewport;
108 | wasFullyInViewport = this.isFullyInViewport;
109 | wasAboveViewport = this.isAboveViewport;
110 | wasBelowViewport = this.isBelowViewport;
111 | };
112 |
113 | this.recalculateLocation = function () {
114 | if (this.locked) {
115 | return;
116 | }
117 | var previousTop = this.top;
118 | var previousBottom = this.bottom;
119 | if (this.watchItem.nodeName) {
120 | // a dom element
121 | var cachedDisplay = this.watchItem.style.display;
122 | if (cachedDisplay === 'none') {
123 | this.watchItem.style.display = '';
124 | }
125 |
126 | var containerOffset = 0;
127 | var container = this.container;
128 | while (container.containerWatcher) {
129 | containerOffset +=
130 | container.containerWatcher.top -
131 | container.containerWatcher.container.viewportTop;
132 | container = container.containerWatcher.container;
133 | }
134 |
135 | var boundingRect = this.watchItem.getBoundingClientRect();
136 | this.top = boundingRect.top + this.container.viewportTop - containerOffset;
137 | this.bottom = boundingRect.bottom + this.container.viewportTop - containerOffset;
138 |
139 | if (cachedDisplay === 'none') {
140 | this.watchItem.style.display = cachedDisplay;
141 | }
142 | } else if (this.watchItem === +this.watchItem) {
143 | // number
144 | if (this.watchItem > 0) {
145 | this.top = this.bottom = this.watchItem;
146 | } else {
147 | this.top = this.bottom = this.container.documentHeight - this.watchItem;
148 | }
149 | } else {
150 | // an object with a top and bottom property
151 | this.top = this.watchItem.top;
152 | this.bottom = this.watchItem.bottom;
153 | }
154 |
155 | this.top -= this.offsets.top;
156 | this.bottom += this.offsets.bottom;
157 | this.height = this.bottom - this.top;
158 |
159 | if (
160 | (previousTop !== undefined || previousBottom !== undefined) &&
161 | (this.top !== previousTop || this.bottom !== previousBottom)
162 | ) {
163 | triggerCallbackArray(this.callbacks[LOCATIONCHANGE], undefined);
164 | }
165 | };
166 |
167 | this.recalculateLocation();
168 | this.update();
169 |
170 | wasInViewport = this.isInViewport;
171 | wasFullyInViewport = this.isFullyInViewport;
172 | wasAboveViewport = this.isAboveViewport;
173 | wasBelowViewport = this.isBelowViewport;
174 | }
175 |
176 | height: number;
177 | top: number;
178 | bottom: number;
179 | offsets: { top: number; bottom: number };
180 |
181 | isInViewport: boolean;
182 | isFullyInViewport: boolean;
183 | isAboveViewport: boolean;
184 | isBelowViewport: boolean;
185 |
186 | locked: boolean = false;
187 |
188 | callbacks: Record = {};
189 | triggerCallbacks: (event: ScrollEvent) => void;
190 | recalculateLocation: () => void;
191 |
192 | visibilityChange: (callback: Listener, isOne: boolean) => void;
193 | enterViewport: (callback: Listener, isOne: boolean) => void;
194 | fullyEnterViewport: (callback: Listener, isOne: boolean) => void;
195 | exitViewport: (callback: Listener, isOne: boolean) => void;
196 | partiallyExitViewport: (callback: Listener, isOne: boolean) => void;
197 | locationChange: (callback: Listener, isOne: boolean) => void;
198 | stateChange: (callback: Listener, isOne: boolean) => void;
199 |
200 | on(event: EventName, callback: Listener, isOne = false) {
201 | // trigger the event if it applies to the element right now.
202 | switch (true) {
203 | case event === VISIBILITYCHANGE && !this.isInViewport && this.isAboveViewport:
204 | case event === ENTERVIEWPORT && this.isInViewport:
205 | case event === FULLYENTERVIEWPORT && this.isFullyInViewport:
206 | case event === EXITVIEWPORT && this.isAboveViewport && !this.isInViewport:
207 | case event === PARTIALLYEXITVIEWPORT && this.isInViewport && this.isAboveViewport:
208 | callback.call(this, this);
209 | if (isOne) {
210 | return;
211 | }
212 | }
213 |
214 | if (this.callbacks[event]) {
215 | this.callbacks[event].push({ callback, isOne });
216 | } else {
217 | throw new Error(
218 | 'Tried to add a scroll monitor listener of type ' +
219 | event +
220 | '. Your options are: ' +
221 | eventTypes.join(', ')
222 | );
223 | }
224 | }
225 | off(event: EventName, callback: Listener) {
226 | if (this.callbacks[event]) {
227 | for (var i = 0, item; (item = this.callbacks[event][i]); i++) {
228 | if (item.callback === callback) {
229 | this.callbacks[event].splice(i, 1);
230 | break;
231 | }
232 | }
233 | } else {
234 | throw new Error(
235 | 'Tried to remove a scroll monitor listener of type ' +
236 | event +
237 | '. Your options are: ' +
238 | eventTypes.join(', ')
239 | );
240 | }
241 | }
242 | one(event: EventName, callback: Listener) {
243 | this.on(event, callback, true);
244 | }
245 | recalculateSize() {
246 | if (this.watchItem instanceof HTMLElement) {
247 | this.height = this.watchItem.offsetHeight + this.offsets.top + this.offsets.bottom;
248 | this.bottom = this.top + this.height;
249 | }
250 | }
251 | update() {
252 | this.isAboveViewport = this.top < this.container.viewportTop;
253 | this.isBelowViewport = this.bottom > this.container.viewportBottom;
254 |
255 | this.isInViewport =
256 | this.top < this.container.viewportBottom && this.bottom > this.container.viewportTop;
257 | this.isFullyInViewport =
258 | (this.top >= this.container.viewportTop &&
259 | this.bottom <= this.container.viewportBottom) ||
260 | (this.isAboveViewport && this.isBelowViewport);
261 | }
262 | destroy() {
263 | var index = this.container.watchers.indexOf(this),
264 | self = this;
265 | this.container.watchers.splice(index, 1);
266 | self.callbacks = {};
267 | }
268 | // prevent recalculating the element location
269 | lock() {
270 | this.locked = true;
271 | }
272 | unlock() {
273 | this.locked = false;
274 | }
275 | }
276 |
277 | var eventHandlerFactory = function (type: EventName) {
278 | return function (callback: Listener, isOne = false) {
279 | this.on.call(this, type, callback, isOne);
280 | };
281 | };
282 |
283 | for (var i = 0, j = eventTypes.length; i < j; i++) {
284 | var type = eventTypes[i];
285 | Watcher.prototype[type] = eventHandlerFactory(type);
286 | }
287 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var script = document.createElement('script');
2 | script.type = 'module';
3 |
4 | script.src = '/testOutput/testEntry.js';
5 | document.body.appendChild(script);
6 |
7 | var script = document.createElement('script');
8 | script.type = 'text/javascript';
9 |
10 | script.src = '/test/tests.js';
11 | document.body.appendChild(script);
12 |
--------------------------------------------------------------------------------
/test/polyfills.js:
--------------------------------------------------------------------------------
1 | if (!Function.prototype.bind) {
2 | Function.prototype.bind = function(oThis) {
3 | if (typeof this !== 'function') {
4 | // closest thing possible to the ECMAScript 5
5 | // internal IsCallable function
6 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
7 | }
8 |
9 | var aArgs = Array.prototype.slice.call(arguments, 1),
10 | fToBind = this,
11 | fNOP = function() {},
12 | fBound = function() {
13 | return fToBind.apply(this instanceof fNOP
14 | ? this
15 | : oThis,
16 | aArgs.concat(Array.prototype.slice.call(arguments)));
17 | };
18 |
19 | if (this.prototype) {
20 | // Function.prototype doesn't have a prototype property
21 | fNOP.prototype = this.prototype;
22 | }
23 | fBound.prototype = new fNOP();
24 |
25 | return fBound;
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/test/tests.js:
--------------------------------------------------------------------------------
1 | /* global require: false, describe: false, it: false, sinon: false, beforeEach: false, afterEach: false, scrollMonitor: false, chai: false */
2 |
3 | var expect = chai.expect;
4 | //mocha.allowUncaught();
5 |
6 | var VISIBILITYCHANGE = 'visibilityChange';
7 | var ENTERVIEWPORT = 'enterViewport';
8 | var FULLYENTERVIEWPORT = 'fullyEnterViewport';
9 | var EXITVIEWPORT = 'exitViewport';
10 | var PARTIALLYEXITVIEWPORT = 'partiallyExitViewport';
11 | var LOCATIONCHANGE = 'locationChange';
12 | var STATECHANGE = 'stateChange';
13 |
14 | var eventTypes = [
15 | VISIBILITYCHANGE,
16 | ENTERVIEWPORT,
17 | FULLYENTERVIEWPORT,
18 | EXITVIEWPORT,
19 | PARTIALLYEXITVIEWPORT,
20 | LOCATIONCHANGE,
21 | STATECHANGE,
22 | ];
23 |
24 | var requestAnimationFrameDoer =
25 | window.requestAnimationFrame ||
26 | window.mozRequestAnimationFrame ||
27 | window.webkitRequestAnimationFrame ||
28 | window.msRequestAnimationFrame ||
29 | function (cb) {
30 | setTimeout(cb, 20);
31 | };
32 |
33 | var getViewportHeight = function () {
34 | return window.innerHeight || document.documentElement.clientHeight;
35 | };
36 | var requestAnimationFrame = function (cb) {
37 | setTimeout(function () {
38 | requestAnimationFrameDoer(cb);
39 | }, 4);
40 | };
41 |
42 | var fixture = document.getElementById('fixture');
43 | if (!fixture) {
44 | fixture = document.createElement('div');
45 | document.body.appendChild(fixture);
46 | }
47 | var div;
48 | div = document.createElement('div');
49 | div.style.position = 'absolute';
50 | div.style.top = '0px';
51 | div.style.left = '0px';
52 | div.style.width = '10px';
53 | div.style.backgroundColor = 'silver';
54 | div.style.height = '' + getViewportHeight() * 2 + 'px';
55 | fixture.appendChild(div);
56 |
57 | var setup = function (done) {
58 | // the browser doesn't trigger the scroll event synchronously.
59 | window.scrollTo(0, 0);
60 | requestAnimationFrame(function () {
61 | done();
62 | });
63 | };
64 |
65 | var destroy = function (done) {
66 | window.scrollTo(0, 0);
67 | requestAnimationFrame(function () {
68 | done();
69 | });
70 | };
71 |
72 | describe('API', function () {
73 | it('module should have correct API.', function () {
74 | expect(scrollMonitor).to.respondTo('create');
75 | expect(scrollMonitor).to.respondTo('update');
76 | expect(scrollMonitor).to.respondTo('recalculateLocations');
77 |
78 | expect(scrollMonitor.viewportTop).to.be.a('number');
79 | expect(scrollMonitor.viewportBottom).to.be.a('number');
80 | expect(scrollMonitor.viewportHeight).to.be.a('number');
81 | expect(scrollMonitor.documentHeight).to.be.a('number');
82 |
83 | expect(scrollMonitor.eventTypes).to.eql([
84 | 'visibilityChange',
85 | 'enterViewport',
86 | 'fullyEnterViewport',
87 | 'exitViewport',
88 | 'partiallyExitViewport',
89 | 'locationChange',
90 | 'stateChange',
91 | ]);
92 | });
93 | it('watcher should have correct API.', function () {
94 | var watcher = scrollMonitor.create(10);
95 |
96 | expect(watcher).to.respondTo('on');
97 | expect(watcher).to.respondTo('off');
98 | expect(watcher).to.respondTo('one');
99 | expect(watcher).to.respondTo('lock');
100 | expect(watcher).to.respondTo('unlock');
101 | expect(watcher).to.respondTo('destroy');
102 | expect(watcher).to.respondTo('update');
103 | expect(watcher).to.respondTo('triggerCallbacks');
104 |
105 | eventTypes.forEach(function (type) {
106 | expect(watcher).to.respondTo(type);
107 | });
108 |
109 | expect(watcher.isInViewport).to.be.a('boolean');
110 | expect(watcher.isFullyInViewport).to.be.a('boolean');
111 | expect(watcher.isAboveViewport).to.be.a('boolean');
112 | expect(watcher.isBelowViewport).to.be.a('boolean');
113 | expect(watcher.top).to.be.a('number');
114 | expect(watcher.bottom).to.be.a('number');
115 | expect(watcher.height).to.be.a('number');
116 | expect(watcher.watchItem).to.be.a('number');
117 | expect(watcher.offsets).to.be.an('object');
118 |
119 | watcher.destroy();
120 | });
121 | });
122 |
123 | describe('calculating locations', function () {
124 | it('should calculate numbers correctly', function () {
125 | var watcher10 = scrollMonitor.create(10);
126 | var watcher15 = scrollMonitor.create(15);
127 |
128 | expect(watcher10.top).to.equal(10);
129 | expect(watcher15.top).to.equal(15);
130 |
131 | expect(watcher10.bottom).to.equal(10);
132 | expect(watcher15.bottom).to.equal(15);
133 |
134 | expect(watcher10.height).to.equal(0);
135 | expect(watcher15.height).to.equal(0);
136 |
137 | expect(watcher10.offsets).to.eql({ top: 0, bottom: 0 });
138 | expect(watcher15.offsets).to.eql({ top: 0, bottom: 0 });
139 |
140 | watcher10.destroy();
141 | watcher15.destroy();
142 | });
143 |
144 | it('should calculate objects correctly', function () {
145 | var watcher = scrollMonitor.create({ top: 100, bottom: 102 });
146 |
147 | expect(watcher.top).to.equal(100);
148 | expect(watcher.bottom).to.equal(102);
149 | expect(watcher.height).to.equal(2);
150 |
151 | watcher.destroy();
152 | });
153 |
154 | it('should calculate things with a property of "0" correctly', function () {
155 | var watcher = scrollMonitor.create([10]);
156 |
157 | expect(watcher.top).to.equal(10);
158 | expect(watcher.bottom).to.equal(10);
159 | expect(watcher.height).to.equal(0);
160 |
161 | watcher.destroy();
162 | });
163 |
164 | it('should calculate DOM elements correctly', function () {
165 | var div = document.createElement('div');
166 |
167 | div.style.position = 'relative';
168 | div.style.top = '10px';
169 | div.style.width = '100px';
170 | div.style.height = '15px';
171 | div.style.backgroundColor = 'gray';
172 |
173 | fixture.appendChild(div);
174 | var watcher = scrollMonitor.create(div);
175 |
176 | var offset = fixture.offsetTop;
177 | expect(watcher.top).to.equal(offset + 10);
178 | expect(watcher.bottom).to.equal(offset + 25);
179 | expect(watcher.height).to.equal(15);
180 |
181 | watcher.destroy();
182 | fixture.removeChild(div);
183 | });
184 | });
185 |
186 | describe('location booleans', function () {
187 | beforeEach(setup);
188 | afterEach(destroy);
189 |
190 | it('should calculate fully in viewport correctly', function () {
191 | var watcher = scrollMonitor.create(10);
192 |
193 | expect(watcher.isInViewport).to.equal(true);
194 | expect(watcher.isFullyInViewport).to.equal(true);
195 | expect(watcher.isAboveViewport).to.equal(false);
196 | expect(watcher.isBelowViewport).to.equal(false);
197 |
198 | watcher.destroy();
199 | });
200 |
201 | it('should calculate partially below viewport correctly', function () {
202 | var windowHeight = getViewportHeight();
203 | var watcher = scrollMonitor.create({ top: windowHeight - 10, bottom: windowHeight + 10 });
204 |
205 | expect(watcher.isInViewport).to.equal(true);
206 | expect(watcher.isFullyInViewport).to.equal(false);
207 | expect(watcher.isAboveViewport).to.equal(false);
208 | expect(watcher.isBelowViewport).to.equal(true);
209 |
210 | watcher.destroy();
211 | });
212 |
213 | it('should calculate partially above viewport correctly', function (done) {
214 | window.scrollTo(0, 100);
215 | requestAnimationFrame(function () {
216 | var watcher = scrollMonitor.create({ top: 0, bottom: 200 });
217 |
218 | expect(watcher.isInViewport).to.equal(true);
219 | expect(watcher.isFullyInViewport).to.equal(false);
220 | expect(watcher.isAboveViewport).to.equal(true);
221 | expect(watcher.isBelowViewport).to.equal(false);
222 |
223 | watcher.destroy();
224 | done();
225 | });
226 | });
227 |
228 | it('should calculate below viewport correctly', function () {
229 | var windowHeight = getViewportHeight();
230 | var watcher = scrollMonitor.create({ top: windowHeight + 10, bottom: windowHeight + 20 });
231 |
232 | expect(scrollMonitor.viewportTop).to.equal(0);
233 | expect(watcher.isInViewport).to.equal(false);
234 | expect(watcher.isFullyInViewport).to.equal(false);
235 | expect(watcher.isAboveViewport).to.equal(false);
236 | expect(watcher.isBelowViewport).to.equal(true);
237 |
238 | watcher.destroy();
239 | });
240 |
241 | it('should calculate above viewport correctly', function (done) {
242 | window.scrollTo(0, 100);
243 | requestAnimationFrame(function () {
244 | var watcher = scrollMonitor.create({ top: 0, bottom: 20 });
245 |
246 | expect(watcher.isInViewport).to.equal(false);
247 | expect(watcher.isFullyInViewport).to.equal(false);
248 | expect(watcher.isAboveViewport).to.equal(true);
249 | expect(watcher.isBelowViewport).to.equal(false);
250 |
251 | watcher.destroy();
252 | done();
253 | });
254 | });
255 |
256 | it('should calculate larger than and fully in viewport correctly', function (done) {
257 | window.scrollTo(0, 100);
258 | requestAnimationFrame(function () {
259 | var windowHeight = getViewportHeight();
260 | var watcher = scrollMonitor.create({ top: 0, bottom: windowHeight + 200 });
261 |
262 | expect(watcher.isInViewport).to.equal(true);
263 | expect(watcher.isFullyInViewport).to.equal(true);
264 | expect(watcher.isAboveViewport).to.equal(true);
265 | expect(watcher.isBelowViewport).to.equal(true);
266 |
267 | watcher.destroy();
268 | done();
269 | });
270 | });
271 | });
272 |
273 | describe('setting offsets', function () {
274 | it('should not have offsets by default', function () {
275 | var watcher = scrollMonitor.create(10);
276 | expect(watcher.offsets).to.eql({ top: 0, bottom: 0 });
277 | watcher.destroy();
278 | });
279 | it('should add offsets as a number', function () {
280 | var watcher = scrollMonitor.create(10, 10);
281 | expect(watcher.offsets).to.eql({ top: 10, bottom: 10 });
282 | watcher.destroy();
283 | });
284 | it('should add offsets as a with just the top in an object', function () {
285 | var watcher = scrollMonitor.create(10, { top: 10 });
286 | expect(watcher.offsets).to.eql({ top: 10, bottom: 0 });
287 | watcher.destroy();
288 | });
289 | it('should add offsets as a with just the bottom in an object', function () {
290 | var watcher = scrollMonitor.create(10, { bottom: 10 });
291 | expect(watcher.offsets).to.eql({ top: 0, bottom: 10 });
292 | watcher.destroy();
293 | });
294 | it('should add offsets as a with both properties in an object', function () {
295 | var watcher = scrollMonitor.create(10, { top: 5, bottom: 10 });
296 | expect(watcher.offsets).to.eql({ top: 5, bottom: 10 });
297 | watcher.destroy();
298 | });
299 | });
300 |
301 | describe('callback application', function () {
302 | function noop() {}
303 | it('should create arrays to hold callbacks.', function () {
304 | var watcher = scrollMonitor.create(10);
305 | eventTypes.forEach(function (type) {
306 | expect(watcher.callbacks[type]).to.be.an('array');
307 | expect(watcher.callbacks[type].length).to.equal(0);
308 | });
309 | watcher.destroy();
310 | });
311 | it('should add event listeners.', function () {
312 | var watcher = scrollMonitor.create(10);
313 | eventTypes.forEach(function (type) {
314 | watcher.on(type, noop);
315 | expect(watcher.callbacks[type].length).to.equal(1);
316 | expect(watcher.callbacks[type][0].callback).to.equal(noop);
317 | expect(watcher.callbacks[type][0].isOne).to.equal(false);
318 | });
319 | watcher.destroy();
320 | });
321 | it('should remove event listeners.', function () {
322 | var watcher = scrollMonitor.create(10);
323 | eventTypes.forEach(function (type) {
324 | watcher.on(type, noop);
325 | watcher.off(type, noop);
326 | expect(watcher.callbacks[type].length).to.equal(0);
327 | });
328 | watcher.destroy();
329 | });
330 | it('should add "one" event listeners as "one" event listeners.', function () {
331 | var watcher = scrollMonitor.create(10);
332 | eventTypes.forEach(function (type) {
333 | var spy = sinon.spy();
334 | watcher.one(type, spy);
335 | if (type === 'enterViewport' || type === 'fullyEnterViewport') {
336 | expect(watcher.callbacks[type].length).to.equal(0);
337 | expect(spy.called).to.equal(true);
338 | } else {
339 | expect(watcher.callbacks[type].length).to.equal(1);
340 | expect(spy.called).to.equal(false);
341 | expect(watcher.callbacks[type][0].callback).to.equal(spy);
342 | expect(watcher.callbacks[type][0].isOne).to.equal(true);
343 | }
344 | });
345 | watcher.destroy();
346 | });
347 | it('should automatically call listeners when they are already in the viewport.', function () {
348 | var watcher = scrollMonitor.create(10);
349 | eventTypes.forEach(function (type) {
350 | var spy = sinon.spy();
351 | watcher.on(type, spy);
352 | if (type === 'enterViewport' || type === 'fullyEnterViewport') {
353 | expect(spy.called).to.equal(true);
354 | } else {
355 | expect(spy.called).to.equal(false);
356 | }
357 | });
358 | watcher.destroy();
359 | });
360 |
361 | it('should automatically call listeners when they are already in the viewport.', function () {
362 | var watcher = scrollMonitor.create(10);
363 | eventTypes.forEach(function (type) {
364 | var spy = sinon.spy();
365 | watcher.on(type, spy);
366 | if (type === 'enterViewport' || type === 'fullyEnterViewport') {
367 | expect(spy.called).to.equal(true);
368 | } else {
369 | expect(spy.called).to.equal(false);
370 | }
371 | });
372 | watcher.destroy();
373 | });
374 | });
375 |
376 | describe('events as the user scrolls', function () {
377 | beforeEach(setup);
378 | afterEach(destroy);
379 |
380 | it('should call enterViewport immediately if the element is already in the viewport.', function () {
381 | var watcher = scrollMonitor.create(div);
382 | var spy = sinon.spy();
383 |
384 | watcher.enterViewport(spy);
385 | expect(spy.called).to.equal(true);
386 | watcher.destroy();
387 | });
388 |
389 | it('should call enterViewport and fullyEnterViewport callbacks immediately if the element is fully in the viewport', function () {
390 | div.style.top = '0px';
391 |
392 | var watcher = scrollMonitor.create(10);
393 | var spy1 = sinon.spy();
394 | var spy2 = sinon.spy();
395 |
396 | watcher.enterViewport(spy1);
397 | watcher.fullyEnterViewport(spy2);
398 | expect(spy1.called).to.equal(true);
399 | expect(spy2.called).to.equal(true);
400 |
401 | watcher.destroy();
402 | });
403 |
404 | it('should call exitViewport immediately if the element is already above the viewport.', function (done) {
405 | window.scrollTo(0, 100);
406 | requestAnimationFrame(function () {
407 | var watcher = scrollMonitor.create(10);
408 | var spy = sinon.spy();
409 |
410 | watcher.exitViewport(spy);
411 | expect(spy.called).to.equal(true);
412 | done();
413 | });
414 | });
415 |
416 | it('should not call partiallyExitViewport immediately if the element is already above the viewport.', function (done) {
417 | window.scrollTo(0, 100);
418 | requestAnimationFrame(function () {
419 | var watcher = scrollMonitor.create(10);
420 | var spy = sinon.spy();
421 |
422 | watcher.partiallyExitViewport(spy);
423 | expect(spy.called).to.equal(false);
424 | watcher.destroy();
425 | done();
426 | });
427 | });
428 |
429 | it('should call exitViewport and partiallyExitViewport when the element exits the viewport.', function (done) {
430 | var watcher = scrollMonitor.create(10);
431 | var spy1 = sinon.spy();
432 | var spy2 = sinon.spy();
433 |
434 | watcher.exitViewport(spy1);
435 | watcher.partiallyExitViewport(spy2);
436 | expect(spy1.called).to.equal(false);
437 | expect(spy2.called).to.equal(false);
438 |
439 | window.scrollTo(0, 100);
440 | requestAnimationFrame(function () {
441 | expect(spy1.called).to.equal(true);
442 | expect(spy2.called).to.equal(true);
443 | done();
444 | });
445 | });
446 |
447 | it('should call enterViewport and fullyEnterViewport when the element enters the viewport.', function (done) {
448 | var watcher = scrollMonitor.create(getViewportHeight() + 10);
449 | var spy1 = sinon.spy();
450 | var spy2 = sinon.spy();
451 |
452 | watcher.enterViewport(spy1);
453 | watcher.fullyEnterViewport(spy2);
454 | expect(spy1.called).to.equal(false);
455 | expect(spy2.called).to.equal(false);
456 |
457 | window.scrollTo(0, 100);
458 | requestAnimationFrame(function () {
459 | expect(spy1.called).to.equal(true);
460 | expect(spy2.called).to.equal(true);
461 | done();
462 | });
463 | });
464 |
465 | it('should only call partiallyExitViewport when the element partially exits the viewport.', function (done) {
466 | var watcher = scrollMonitor.create({ top: 10, bottom: 200 });
467 | var spy1 = sinon.spy();
468 | var spy2 = sinon.spy();
469 |
470 | watcher.exitViewport(spy1);
471 | watcher.partiallyExitViewport(spy2);
472 | expect(spy1.called).to.equal(false);
473 | expect(spy2.called).to.equal(false);
474 |
475 | window.scrollTo(0, 100);
476 | requestAnimationFrame(function () {
477 | expect(spy1.called).to.equal(false);
478 | expect(spy2.called).to.equal(true);
479 | done();
480 | });
481 | });
482 |
483 | it('should only call enterViewport when the element halfway enters the viewport.', function (done) {
484 | var watcher = scrollMonitor.create({
485 | top: getViewportHeight() + 10,
486 | bottom: getViewportHeight() * 2 + 100,
487 | });
488 | var spy1 = sinon.spy();
489 | var spy2 = sinon.spy();
490 |
491 | watcher.enterViewport(spy1);
492 | watcher.fullyEnterViewport(spy2);
493 | expect(spy1.called).to.equal(false);
494 | expect(spy2.called).to.equal(false);
495 |
496 | window.scrollTo(0, getViewportHeight() + 20);
497 | requestAnimationFrame(function () {
498 | expect(spy1.called).to.equal(true);
499 | expect(spy2.called).to.equal(false);
500 | done();
501 | });
502 | });
503 | });
504 |
505 | describe('arguments and context', function () {
506 | it('should use the watcher as the context.', function () {
507 | var watcher = scrollMonitor.create(10);
508 | watcher.enterViewport(function () {
509 | expect(this).to.equal(watcher);
510 | });
511 | watcher.destroy();
512 | });
513 |
514 | it('should use the scroll event as the only argument.', function (done) {
515 | var watcher = scrollMonitor.create({
516 | top: getViewportHeight() + 10,
517 | bottom: getViewportHeight() * 2 + 100,
518 | });
519 | var spy = sinon.spy();
520 |
521 | watcher.enterViewport(spy);
522 |
523 | window.scrollTo(0, getViewportHeight() + 20);
524 | requestAnimationFrame(function () {
525 | expect(spy.args[0][0].type).to.equal('scroll');
526 | done();
527 | });
528 | });
529 | });
530 |
531 | document.getElementById('mocha').innerHTML = '';
532 | setTimeout(function () {
533 | mocha.run();
534 | }, 1000);
535 |
--------------------------------------------------------------------------------
/testEntry.ts:
--------------------------------------------------------------------------------
1 | import { isInBrowser } from './src/constants.js';
2 | import { ScrollMonitorContainer } from './src/container.js';
3 |
4 | if (isInBrowser) {
5 | var scrollMonitor = new ScrollMonitorContainer(document.body);
6 | scrollMonitor.updateState();
7 | scrollMonitor.listenToDOM();
8 | //@ts-ignore
9 | window.scrollMonitor = scrollMonitor;
10 | }
11 |
12 | export default scrollMonitor;
13 |
--------------------------------------------------------------------------------
/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "mocha+chai",
3 | "src_files": [],
4 | "footer_scripts": ["test/polyfills.js", "node_modules/sinon/pkg/sinon.js", "test/index.js"],
5 |
6 | "launch_in_dev": ["phantomjs"]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "umd",
4 | "noImplicitAny": true,
5 | "preserveConstEnums": true,
6 | "sourceMap": true,
7 | "moduleResolution": "node",
8 | "outDir": "./dist/umd",
9 | "target": "es5",
10 | "lib": ["es6", "dom"],
11 | "declaration": true
12 | },
13 | "files": ["index.ts"]
14 | }
15 |
--------------------------------------------------------------------------------