Files: d1627bb325d9d1acbb1ba98a75ceb37a6b537c85 / lib / scroller.js
3108 bytesRaw
1 | const pull = require('pull-stream') |
2 | const Pause = require('pull-pause') |
3 | const { h, computed, Value } = require('mutant') |
4 | |
5 | module.exports = Scroller |
6 | |
7 | function Scroller (scroller, content, render, opts) { |
8 | if (typeof opts === 'function') { |
9 | opts = { onDone: opts } |
10 | } else if (!opts) { |
11 | opts = {} |
12 | } |
13 | const toRenderCount = Value(0) |
14 | const toAppendCount = Value(0) |
15 | const waitingForItems = Value(false) |
16 | const pauser = Pause(function () {}) |
17 | |
18 | const endMarker = h('div.endMarker') |
19 | let intersecting = false |
20 | content.appendChild(endMarker) |
21 | |
22 | const endlessObserver = new window.IntersectionObserver((entries) => { |
23 | if (entries[0].isIntersecting) { |
24 | // end marker is inside margin, request more items |
25 | intersecting = true |
26 | if (running) { |
27 | waitingForItems.set(true) |
28 | pauser.resume() |
29 | } |
30 | } else { |
31 | // end marker is no longer inside margin, stop appending items |
32 | intersecting = false |
33 | waitingForItems.set(false) |
34 | } |
35 | }, { |
36 | root: scroller, |
37 | // preload at least 4 screens ahead |
38 | rootMargin: '0px 0px 400% 0px' |
39 | }) |
40 | |
41 | const visibleObserver = new window.IntersectionObserver((entries) => { |
42 | entries.forEach(entry => { |
43 | if (entry.isIntersecting) { |
44 | visibleObserver.unobserve(entry.target) |
45 | process.nextTick(() => opts.onItemVisible && opts.onItemVisible(entry.target)) |
46 | } |
47 | }) |
48 | }, { |
49 | root: scroller |
50 | }) |
51 | |
52 | endlessObserver.observe(endMarker) |
53 | |
54 | const queueLength = computed([toRenderCount, toAppendCount], (a, b) => a + b) |
55 | |
56 | const appendQueue = [] |
57 | let flushing = false |
58 | let running = true |
59 | |
60 | function queueAppend (element) { |
61 | // only call appendNow a maximum of once per frame |
62 | appendQueue.push(element) |
63 | toAppendCount.set(appendQueue.length) |
64 | |
65 | if (!flushing) { |
66 | flushing = true |
67 | window.requestAnimationFrame(appendNow) |
68 | } |
69 | } |
70 | |
71 | function appendNow () { |
72 | flushing = false |
73 | while (appendQueue.length) { |
74 | const element = appendQueue.shift() |
75 | content.insertBefore(element, endMarker) |
76 | |
77 | // notify when this element comes into view (for mark as read) |
78 | visibleObserver.observe(element) |
79 | } |
80 | |
81 | toAppendCount.set(appendQueue.length) |
82 | |
83 | // oops, looks like we need more items! |
84 | if (queueLength() < 5 && intersecting) { |
85 | pauser.resume() |
86 | } |
87 | |
88 | if (running === false && !queueLength()) { |
89 | // we're done already! |
90 | waitingForItems.set(false) |
91 | } |
92 | } |
93 | |
94 | const stream = pull( |
95 | pauser, |
96 | pull.drain(function (msg) { |
97 | toRenderCount.set(toRenderCount() + 1) |
98 | |
99 | process.nextTick(() => { |
100 | // render post when idle |
101 | const element = render(msg) |
102 | queueAppend(element) |
103 | toRenderCount.set(toRenderCount() - 1) |
104 | }) |
105 | |
106 | if (queueLength() > 5 || !intersecting) { |
107 | // stop feeding the queue if greater than 5 |
108 | pauser.pause() |
109 | } |
110 | }, function (err) { |
111 | running = false |
112 | if (!queueLength()) waitingForItems.set(false) |
113 | opts.onDone ? opts.onDone(err) : console.error(err) |
114 | }) |
115 | ) |
116 | |
117 | stream.waiting = waitingForItems |
118 | stream.queue = queueLength |
119 | |
120 | return stream |
121 | } |
122 |
Built with git-ssb-web