├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── examples ├── element.html ├── horizontal.html ├── pinterest.html └── window.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.0.6 / 2015-03-16 3 | ================== 4 | 5 | * package: add "browser" mapping for browserify 6 | * component: sort deps 7 | 8 | 0.0.5 / 2013-09-19 9 | ================== 10 | 11 | * only load if bounding box has dimensions 12 | 13 | 0.0.4 / 2013-08-28 14 | ================== 15 | 16 | * added events: loading, load, unloading, unload 17 | 18 | 0.0.3 / 2013-08-08 19 | ================== 20 | 21 | * added infinity#margin(n) 22 | 23 | 0.0.2 / 2013-08-07 24 | ================== 25 | 26 | * automatic refresh => manual refresh. more annoying, but so much faster 27 | 28 | 0.0.1 / 2013-08-07 29 | ================== 30 | 31 | * Initial commit 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: components index.js 3 | @component build --dev 4 | 5 | components: component.json 6 | @component install --dev 7 | 8 | clean: 9 | rm -fr build components template.js 10 | 11 | .PHONY: clean 12 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # infinity 3 | 4 | Unload and reload panes while scrolling. Inspired by [airbnb/infinity](http://github.com/airbnb/infinity). 5 | 6 | ## Installation 7 | 8 | Install with [component(1)](http://component.io): 9 | 10 | $ component install component/infinity 11 | 12 | ## Example 13 | 14 | ```js 15 | var infinity = require('infinity')(window); 16 | var panes = document.querySelectorAll('.pane'); 17 | 18 | for(var i = 0, len = panes.length; i < len; i++) { 19 | infinity.add(panes[i]); 20 | } 21 | 22 | infinity.refresh(); 23 | ``` 24 | 25 | ## Events 26 | 27 | * `loading`: called once before each visible `el` is loaded. useful for batch operations. 28 | * `load`: called when a `el` is to be loaded. 29 | * `unloading`: called once before each `el` is unloaded. useful for batch operations. 30 | * `unload`: called when an `el` is to be unloaded. 31 | 32 | ## API 33 | 34 | ### infinity(el) 35 | 36 | Initialize `infinity` with `el`. `el` can be either the `window` or an element with overflow. 37 | 38 | ### infinity.add(el, ...) 39 | 40 | Add `el` to `infinity`. You may pass any number of arguments 41 | to be called with the `load` and `unload` functions. The 42 | first argument must be the element node. 43 | 44 | ```js 45 | infinity.add(view.el, view) 46 | ``` 47 | 48 | ### infinity.remove(el) 49 | 50 | Remove an element from `infinity`. 51 | 52 | ```js 53 | infinity.remove(el) 54 | ``` 55 | 56 | ### infinity.load(fn) 57 | 58 | Add a load function. Defaults to a `noop`. 59 | The arguments passed to add will be passed 60 | through `load`. 61 | 62 | ```js 63 | infinity.load(function(el, view) { 64 | // ... 65 | }); 66 | ``` 67 | 68 | ### infinity.unload(fn) 69 | 70 | Add an unload function. Defaults to a `noop`. 71 | The arguments passed to add will be passed 72 | through `unload`. 73 | 74 | ```js 75 | infinity.unload(function(el, view) { 76 | // ... 77 | }); 78 | ``` 79 | 80 | ### infinity.refresh() 81 | 82 | Refresh, loading and unloading elements. Call this 83 | after adding elements, removing elements, or moving 84 | elements programmatically. 85 | 86 | ```js 87 | infinity.refresh(); 88 | ``` 89 | 90 | ### infinity.margin(n) 91 | 92 | Add "preload margin" to each side of the container. 93 | This will allow you to start loading elements before 94 | they appear in viewport. `n` defaults to `0`. 95 | 96 | For example, for `infinity.margin(200)`, the `load` 97 | function would trigger when the element is within 98 | 200px from being in view. 99 | 100 | ### infinity.unbind() 101 | 102 | Unbind all infinity events 103 | 104 | ## License 105 | 106 | MIT 107 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "infinity", 3 | "repo": "component/infinity", 4 | "description": "unload and reload panes while scrolling.", 5 | "version": "0.0.6", 6 | "keywords": [ 7 | "infinity", 8 | "scrolling" 9 | ], 10 | "dependencies": { 11 | "component/debounce": "*", 12 | "component/emitter": "*", 13 | "component/event": "*", 14 | "component/query": "*", 15 | "component/throttle": "*" 16 | }, 17 | "development": { 18 | "danzajdband/vanilla-masonry": "*" 19 | }, 20 | "license": "MIT", 21 | "main": "index.js", 22 | "scripts": [ 23 | "index.js" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | infinity component 4 | 5 | 47 | 48 | 49 |

infinity component

50 | 51 |
52 |
1
53 |
2
54 |
3
55 |
4
56 |
5
57 |
6
58 |
7
59 |
8
60 |
9
61 |
10
62 |
63 | 64 |
65 | 66 | 67 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/horizontal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | infinity component 4 | 5 | 49 | 50 | 51 |

infinity component

52 | 53 |
54 |
1
55 |
2
56 |
3
57 |
4
58 |
5
59 |
6
60 |
7
61 |
8
62 |
9
63 |
10
64 |
65 | 66 |
67 | 68 | 69 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/pinterest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | infinity component 4 | 5 | 66 | 67 | 68 |

infinity component

69 | 70 |
71 |
1
72 |
2
73 |
3
74 |
4
75 |
5
76 |
6
77 |
7
78 |
8
79 |
9
80 |
10
81 |
11
82 |
12
83 |
13
84 |
14
85 |
15
86 |
16
87 |
17
88 |
18
89 |
19
90 |
20
91 |
21
92 |
22
93 |
23
94 |
24
95 |
25
96 |
26
97 |
27
98 |
28
99 |
29
100 |
30
101 |
31
102 |
32
103 |
33
104 |
34
105 |
35
106 |
36
107 |
37
108 |
38
109 |
39
110 |
40
111 |
1
112 |
2
113 |
3
114 |
4
115 |
5
116 |
6
117 |
7
118 |
8
119 |
9
120 |
10
121 |
11
122 |
12
123 |
13
124 |
14
125 |
15
126 |
16
127 |
17
128 |
18
129 |
19
130 |
20
131 |
21
132 |
22
133 |
23
134 |
24
135 |
25
136 |
26
137 |
27
138 |
28
139 |
29
140 |
30
141 |
31
142 |
32
143 |
33
144 |
34
145 |
35
146 |
36
147 |
37
148 |
38
149 |
39
150 |
40
151 |
152 | 153 |
154 | 155 | 156 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /examples/window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | infinity component 4 | 5 | 42 | 43 | 44 |

infinity component

45 | 46 |
1
47 |
2
48 |
3
49 |
4
50 |
5
51 |
6
52 |
7
53 |
8
54 |
9
55 |
10
56 | 57 |
58 | 59 | 60 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var event = require('event'); 6 | var query = require('query'); 7 | var throttle = require('throttle'); 8 | var debounce = require('debounce'); 9 | var Emitter = require('emitter'); 10 | 11 | /** 12 | * Export `infinity` 13 | */ 14 | 15 | module.exports = infinity; 16 | 17 | /** 18 | * Initialize `infinity` 19 | * 20 | * @param {Element|Window} container el 21 | * @return {infinity} 22 | * @api public 23 | */ 24 | 25 | function infinity(el) { 26 | if (!(this instanceof infinity)) return new infinity(el); 27 | this.el = el; 28 | this.isWindow = (el.self == el); 29 | this.views = []; 30 | this._margin = 0; 31 | this.throttle = throttle(this.refresh.bind(this), 200); 32 | this.debounce = debounce(this.refresh.bind(this), 100); 33 | event.bind(el, 'scroll', this.throttle); 34 | event.bind(el, 'scroll', this.debounce); 35 | event.bind(el, 'resize', this.debounce); 36 | } 37 | 38 | /** 39 | * Mixin `Emitter` 40 | */ 41 | 42 | Emitter(infinity.prototype); 43 | 44 | /** 45 | * Add an element. You may pass any number of arguments 46 | * to be called on the load and unload functions 47 | * 48 | * ex. infinity.add(view.el, view) 49 | * 50 | * @param {Element} el 51 | * @param {Mixed, ...} args 52 | * @return {infinity} 53 | * @api public 54 | */ 55 | 56 | infinity.prototype.add = function(el) { 57 | var view = {}; 58 | view.el = el; 59 | view.args = [].slice.call(arguments) || []; 60 | view.loaded = false; 61 | this.views.push(view); 62 | return this; 63 | }; 64 | 65 | /** 66 | * Remove an element. 67 | * 68 | * ex. infinity.remove(el) 69 | * 70 | * @param {Element} el 71 | * @return {infinity} 72 | * @api public 73 | */ 74 | 75 | infinity.prototype.remove = function(el) { 76 | for (var i = 0, view; view = this.views[i]; i++) { 77 | if (el == view.el) { 78 | this.views.splice(i, 1); 79 | break; 80 | } 81 | } 82 | 83 | return this; 84 | }; 85 | 86 | 87 | /** 88 | * Get the coordinated of the box 89 | * 90 | * @return {Object} coords 91 | * @api private 92 | */ 93 | 94 | infinity.prototype.box = function() { 95 | var el = this.el; 96 | var margin = this._margin; 97 | var box = {}; 98 | 99 | if (!this.isWindow) { 100 | var rect = el.getBoundingClientRect(); 101 | box.top = rect.top; 102 | box.left = rect.left; 103 | box.width = rect.width; 104 | box.height = rect.height; 105 | } else { 106 | box.top = 0; 107 | box.left = 0; 108 | box.height = el.innerHeight || document.documentElement.clientHeight; 109 | box.width = el.innerWidth || document.documentElement.clientWidth; 110 | } 111 | 112 | box.top -= margin; 113 | box.left -= margin; 114 | box.width += (margin * 2); 115 | box.height += (margin * 2); 116 | 117 | return box; 118 | }; 119 | 120 | /** 121 | * Add margin to the box 122 | * 123 | * @param {Number} margin 124 | * @return {infinity} 125 | * @api public 126 | */ 127 | 128 | infinity.prototype.margin = function(margin) { 129 | this._margin = margin; 130 | return this; 131 | }; 132 | 133 | /** 134 | * Is the element in view? 135 | * 136 | * @param {Object} view 137 | * @return {Boolean} 138 | * @api private 139 | */ 140 | 141 | infinity.prototype.visible = function(view) { 142 | var box = this._box; 143 | var pos = view.el.getBoundingClientRect(); 144 | 145 | // only load if our view has dimensions 146 | if (0 === pos.width || 0 === pos.height) return false; 147 | 148 | // check viewport if window otherwise view 149 | return (this.isWindow) ? this.inViewport(pos, box) 150 | : this.inView(pos, box); 151 | }; 152 | 153 | /** 154 | * Is the element in view? 155 | * 156 | * @param {Object} pos 157 | * @param {Object} box 158 | * @return {Boolean} 159 | * @api private 160 | */ 161 | 162 | infinity.prototype.inView = function(pos, box) { 163 | return ( 164 | pos.top < (box.top + box.height) && 165 | pos.left < (box.left + box.width) && 166 | (pos.top + pos.height) > box.top && 167 | (pos.left + pos.width) > box.left 168 | ); 169 | }; 170 | 171 | /** 172 | * Is the element in the viewport? 173 | * 174 | * TODO: inViewport and inView could probably be consolidated 175 | * with some better math 176 | * 177 | * @param {Object} pos 178 | * @param {Object} box 179 | * @return {Boolean} 180 | * @api private 181 | */ 182 | 183 | infinity.prototype.inViewport = function(pos, box) { 184 | return ( 185 | pos.bottom >= box.top && 186 | pos.right >= box.left && 187 | pos.top <= box.height && 188 | pos.left <= box.width 189 | ); 190 | }; 191 | 192 | /** 193 | * Add a load function 194 | * 195 | * @param {Function} fn 196 | * @return {infinity} 197 | * @api public 198 | */ 199 | 200 | infinity.prototype.load = function(fn) { 201 | this.on('load', fn); 202 | return this; 203 | }; 204 | 205 | /** 206 | * Add an unload function 207 | * 208 | * @param {Function} fn 209 | * @return {infinity} 210 | * @api public 211 | */ 212 | 213 | infinity.prototype.unload = function(fn) { 214 | this.on('unload', fn); 215 | return this; 216 | }; 217 | 218 | /** 219 | * Refresh, loading and unloading elements. 220 | * 221 | * Used internally but may need to be called 222 | * manually if you are programmatically adjusting 223 | * elements. 224 | * 225 | * @return {infinity} 226 | * @api public 227 | */ 228 | 229 | infinity.prototype.refresh = function() { 230 | var visibles = []; 231 | var invisibles = []; 232 | 233 | this._box = this.box(); 234 | 235 | // load / unload panes 236 | // 237 | // TODO: figure out a smarter way to not loop 238 | // through all the elements time but maintain 239 | // flexibility 240 | for (var i = 0, view; view = this.views[i]; i++) { 241 | var visible = this.visible(view); 242 | if (visible && !view.loaded) { 243 | visibles.push(view); 244 | } else if (!visible && view.loaded) { 245 | invisibles.push(view); 246 | } 247 | } 248 | 249 | if (visibles.length) { 250 | this.emit('loading'); 251 | for (var i = 0, view; view = visibles[i]; i++) { 252 | this.emit.apply(this, ['load'].concat(view.args)); 253 | view.loaded = true; 254 | } 255 | } 256 | 257 | if (invisibles.length) { 258 | this.emit('unloading'); 259 | for (var i = 0, view; view = invisibles[i]; i++) { 260 | this.emit.apply(this, ['unload'].concat(view.args)); 261 | view.loaded = false; 262 | } 263 | } 264 | 265 | return this; 266 | }; 267 | 268 | /** 269 | * Unbind events 270 | * 271 | * @return {infinity} 272 | * @api public 273 | */ 274 | 275 | infinity.prototype.unbind = function() { 276 | event.unbind(this.el, 'scroll', this.throttle); 277 | event.unbind(this.el, 'scroll', this.debounce); 278 | event.unbind(this.el, 'resize', this.debounce); 279 | return this; 280 | }; 281 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-infinity", 3 | "version": "0.0.6", 4 | "description": "infinite scrolling with loading and unloading", 5 | "main": "index.js", 6 | "keywords": [ 7 | "scrolling", 8 | "loading", 9 | "unload" 10 | ], 11 | "dependencies": { 12 | "debounce": "*", 13 | "component-emitter": "*", 14 | "component-event": "*", 15 | "component-query": "*", 16 | "throttleit": "*" 17 | }, 18 | "browser": { 19 | "emitter": "component-emitter", 20 | "event": "component-event", 21 | "query": "component-query", 22 | "throttle": "throttleit" 23 | }, 24 | "author": "Matthew Mueller", 25 | "license": "MIT", 26 | "component": { 27 | "scripts": { 28 | "infinity/index.js": "index.js" 29 | } 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/component/infinity.git" 34 | } 35 | } 36 | --------------------------------------------------------------------------------