├── .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 |
--------------------------------------------------------------------------------