git ssb

10+

Matt McKegg / patchwork



Commit e108357490d8ff17a62b052f173b16344531ecbc

rewrite infinity scroll to use idle rendering with animation frame append loop

Matt McKegg committed on 6/28/2017, 6:03:42 AM
Parent: d14a37cca1e80d539fdba2a00bed84a118e0892f

Files changed

lib/pull-scroll.jsdeleted
lib/scroller.jsadded
modules/feed/html/rollup.jschanged
modules/page/html/render/search.jschanged
package.jsonchanged
lib/pull-scroll.jsView
@@ -1,119 +1,0 @@
1-// forked because of weird non-filling and over-resuming problems
2-// should really PR this, but Dominic might not accept it :D
3-
4-var pull = require('pull-stream')
5-var Pause = require('pull-pause')
6-var Value = require('mutant/value')
7-
8-var next = 'undefined' === typeof setImmediate ? setTimeout : setImmediate
9-var buffer = Math.max(window.innerHeight * 2, 1000)
10-
11-var u = require('pull-scroll/utils'),
12- assertScrollable = u.assertScrollable,
13- isEnd = u.isEnd,
14- isVisible = u.isVisible
15-
16-module.exports = Scroller
17-
18-
19-function Scroller(scroller, content, render, isPrepend, isSticky, cb) {
20- assertScrollable(scroller)
21- var obs = Value(0)
22-
23- //if second argument is a function,
24- //it means the scroller and content elements are the same.
25- if('function' === typeof content) {
26- cb = isSticky
27- isPrepend = render
28- render = content
29- content = scroller
30- }
31-
32- if(!cb) cb = function (err) { if(err) throw err }
33-
34- scroller.addEventListener('scroll', scroll)
35- var pause = Pause(function () {})
36- var queue = []
37-
38- //apply some changes to the dom, but ensure that
39- //`element` is at the same place on screen afterwards.
40-
41- function add () {
42- if(queue.length) {
43- var m = queue.shift()
44- var r = render(m)
45- append(scroller, content, r, isPrepend, isSticky)
46- obs.set(queue.length)
47- }
48- }
49-
50- function scroll (ev) {
51- if (isEnd(scroller, buffer, isPrepend)) {
52- add()
53- pause.resume()
54- }
55- }
56-
57- pause.pause()
58-
59- //wait until the scroller has been added to the document
60- next(function next () {
61- if(scroller.parentElement) pause.resume()
62- else setTimeout(next, 100)
63- })
64-
65- var stream = pull(
66- pause,
67- pull.drain(function (e) {
68- queue.push(e)
69- obs.set(queue.length)
70-
71- if(content.clientHeight < window.innerHeight)
72- add()
73-
74- if (isVisible(content)) {
75- if (isEnd(scroller, buffer, isPrepend))
76- add()
77- }
78-
79- if(queue.length > 5) {
80- pause.pause()
81- }
82-
83- }, function (err) {
84- if(err) console.error(err)
85- cb ? cb(err) : console.error(err)
86- })
87- )
88-
89- stream.visible = add
90- stream.queue = obs
91- return stream
92-}
93-
94-
95-function append(scroller, list, el, isPrepend, isSticky) {
96- if(!el) return
97- var s = scroller.scrollHeight
98- var st = scroller.scrollTop
99- if(isPrepend && list.firstChild)
100- list.insertBefore(el, list.firstChild)
101- else
102- list.appendChild(el)
103-
104- //scroll down by the height of the thing added.
105- //if it added to the top (in non-sticky mode)
106- //or added it to the bottom (in sticky mode)
107- if(isPrepend !== isSticky) {
108- var d = (scroller.scrollHeight - s)
109- var before = scroller.scrollTop
110- //check whether the browser has moved the scrollTop for us.
111- //if you add an element that is not scrolled into view
112- //it no longer bumps the view down! but this check is still needed
113- //for firefox.
114- //this seems to be the behavior in recent chrome (also electron)
115- if(st === scroller.scrollTop) {
116- scroller.scrollTop = scroller.scrollTop + d
117- }
118- }
119-}
lib/scroller.jsView
@@ -1,0 +1,60 @@
1+var pull = require('pull-stream')
2+var Pause = require('pull-pause')
3+var Value = require('mutant/value')
4+var onceIdle = require('mutant/once-idle')
5+var computed = require('mutant/computed')
6+
7+module.exports = Scroller
8+
9+function Scroller (scroller, content, render, cb) {
10+ var toRenderCount = Value(0)
11+ var toAppendCount = Value(0)
12+
13+ var queueLength = computed([toRenderCount, toAppendCount], (a, b) => a + b)
14+
15+ var pause = Pause(function () {})
16+ var running = true
17+ var appendQueue = []
18+
19+ function appendLoop () {
20+ var distanceFromBottom = scroller.scrollHeight - (scroller.scrollTop + scroller.clientHeight)
21+ while (appendQueue.length && distanceFromBottom < scroller.clientHeight) {
22+ content.appendChild(appendQueue.shift())
23+ }
24+
25+ toAppendCount.set(appendQueue.length)
26+ if (queueLength() < 5) {
27+ // queue running low, resume stream
28+ pause.resume()
29+ }
30+
31+ if (running || queueLength()) {
32+ window.requestAnimationFrame(appendLoop)
33+ }
34+ }
35+
36+ var stream = pull(
37+ pause,
38+ pull.drain(function (msg) {
39+ toRenderCount.set(toRenderCount() + 1)
40+
41+ onceIdle(() => {
42+ var element = render(msg)
43+ appendQueue.push(element)
44+ toRenderCount.set(toRenderCount() - 1)
45+ })
46+
47+ if (queueLength() > 5) {
48+ pause.pause()
49+ }
50+ }, function (err) {
51+ running = false
52+ cb ? cb(err) : console.error(err)
53+ })
54+ )
55+
56+ stream.queue = queueLength
57+
58+ appendLoop()
59+ return stream
60+}
modules/feed/html/rollup.jsView
@@ -1,9 +1,9 @@
11 var nest = require('depnest')
22 var {Value, Proxy, Array: MutantArray, h, computed, map, when, onceTrue, throttle} = require('mutant')
33 var pull = require('pull-stream')
44 var Abortable = require('pull-abortable')
5-var Scroller = require('../../../lib/pull-scroll')
5+var Scroller = require('../../../lib/scroller')
66 var nextStepper = require('../../../lib/next-stepper')
77 var extend = require('xtend')
88 var paramap = require('pull-paramap')
99
@@ -56,9 +56,21 @@
5656 var newSinceRefresh = new Set()
5757 var highlightItems = new Set()
5858
5959 var container = h('Scroller', {
60- style: { overflow: 'auto' }
60+ style: { overflow: 'auto' },
61+ hooks: [(element) => {
62+ // don't activate until added to DOM
63+ refresh()
64+
65+ // deactivate when removed from DOM
66+ return () => {
67+ if (abortLastFeed) {
68+ abortLastFeed()
69+ abortLastFeed = null
70+ }
71+ }
72+ }]
6173 }, [
6274 h('div.wrapper', [
6375 h('section.prepend', prepend),
6476 content,
@@ -66,10 +78,8 @@
6678 ])
6779 ])
6880
6981 onceTrue(waitFor, () => {
70- refresh()
71-
7282 // display pending updates
7383 pull(
7484 updateStream || pull(
7585 getStream({old: false}),
@@ -104,34 +114,36 @@
104114
105115 return result
106116
107117 function refresh () {
108- if (abortLastFeed) abortLastFeed()
109- updates.set(0)
110- content.set(h('section.content'))
118+ onceTrue(waitFor, () => {
119+ if (abortLastFeed) abortLastFeed()
120+ updates.set(0)
121+ content.set(h('section.content'))
111122
112- var abortable = Abortable()
113- abortLastFeed = abortable.abort
123+ var abortable = Abortable()
124+ abortLastFeed = abortable.abort
114125
115- highlightItems = newSinceRefresh
116- newSinceRefresh = new Set()
126+ highlightItems = newSinceRefresh
127+ newSinceRefresh = new Set()
117128
118- var done = Value(false)
119- var stream = nextStepper(getStream, {reverse: true, limit: 50})
120- var scroller = Scroller(container, content(), renderItem, false, false, () => done.set(true))
129+ var done = Value(false)
130+ var stream = nextStepper(getStream, {reverse: true, limit: 50})
131+ var scroller = Scroller(container, content(), renderItem, () => done.set(true))
121132
122- // track loading state
123- loading.set(computed([done, scroller.queue], (done, queue) => {
124- return !done && queue < 5
125- }))
133+ // track loading state
134+ loading.set(computed([done, scroller.queue], (done, queue) => {
135+ return !done && queue < 5
136+ }))
126137
127- pull(
128- stream,
129- pull.filter(bumpFilter),
130- abortable,
131- api.feed.pull.rollup(rootFilter),
132- scroller
133- )
138+ pull(
139+ stream,
140+ pull.filter(bumpFilter),
141+ abortable,
142+ api.feed.pull.rollup(rootFilter),
143+ scroller
144+ )
145+ })
134146 }
135147
136148 function renderItem (item, opts) {
137149 var partial = opts && opts.partial
modules/page/html/render/search.jsView
@@ -1,7 +1,7 @@
11 const { h, Struct, Value, when, computed } = require('mutant')
22 const pull = require('pull-stream')
3-const Scroller = require('../../../../lib/pull-scroll')
3+const Scroller = require('../../../../lib/scroller')
44 const TextNodeSearcher = require('text-node-searcher')
55 const whitespace = /\s+/
66 const pullAbortable = require('pull-abortable')
77 var nest = require('depnest')
@@ -69,9 +69,9 @@
6969 pull(
7070 api.sbot.pull.log({old: false}),
7171 pull.filter(matchesQuery),
7272 realtimeAborter,
73- Scroller(container, content, renderMsg, true, false)
73+ Scroller(container, content, renderMsg)
7474 )
7575
7676 // pull(
7777 // nextStepper(api.sbot.pull.search, {query: queryStr, reverse: true, limit: 500, live: false}),
package.jsonView
@@ -44,9 +44,8 @@
4444 "pull-notify": "^0.1.1",
4545 "pull-pause": "~0.0.1",
4646 "pull-ping": "^2.0.2",
4747 "pull-pushable": "^2.0.1",
48- "pull-scroll": "^1.0.4",
4948 "pull-stream": "~3.6.0",
5049 "scuttlebot": "^10.0.7",
5150 "secure-scuttlebutt": "^16.3.4",
5251 "sorted-array-functions": "~1.0.0",

Built with git-ssb-web