fine-tune infinite scrolling list

This commit is contained in:
Nolan Lawson 2018-01-17 00:06:24 -08:00
parent eacf28317e
commit 9e111bfc5a
9 changed files with 62 additions and 15 deletions

5
package-lock.json generated
View file

@ -5848,6 +5848,11 @@
}
}
},
"requestidlecallback": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/requestidlecallback/-/requestidlecallback-0.3.0.tgz",
"integrity": "sha1-b7dOBzP5DfP6pIOPn2oqX5t0KsU="
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",

View file

@ -32,6 +32,7 @@
"npm-run-all": "^4.1.2",
"performance-now": "^2.1.0",
"pify": "^3.0.0",
"requestidlecallback": "^0.3.0",
"sapper": "^0.3.2",
"serve-static": "^1.13.1",
"style-loader": "^0.19.1",

View file

@ -10,17 +10,20 @@
import Nav from './Nav.html';
import { virtualListStore } from '../_utils/virtualListStore'
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
const THROTTLE_DELAY = 500
const SCROLL_EVENT_DELAY = 300
const RESIZE_EVENT_DELAY = 700
export default {
oncreate() {
this.observe('innerHeight', throttle(() => {
this.observe('innerHeight', debounce(() => {
// respond to window resize events
this.store.set({
offsetHeight: this.refs.node.offsetHeight
})
}, THROTTLE_DELAY))
}, RESIZE_EVENT_DELAY))
this.store.set({
scrollTop: this.refs.node.scrollTop,
scrollHeight: this.refs.node.scrollHeight,
@ -33,7 +36,10 @@
store: () => virtualListStore,
events: {
scroll(node, callback) {
const onScroll = throttle(callback, THROTTLE_DELAY)
const onScroll = throttle(callback, SCROLL_EVENT_DELAY, {
leading: true,
trailing: true
})
node.addEventListener('scroll', onScroll);
return {

View file

@ -14,7 +14,7 @@
import fixture from '../_utils/fixture.json'
import StatusListItem from './StatusListItem.html'
import VirtualList from './VirtualList.html'
import { splice } from 'svelte-extras'
import { splice, push } from 'svelte-extras'
let i = -1
@ -35,12 +35,33 @@
},
methods: {
splice: splice,
push: push,
addMoreItems() {
console.log('addMoreItems')
let statuses = this.get('statuses')
if (statuses) {
this.splice('statuses', statuses.length, 0, ...createData())
let itemsToAdd = createData()
if (itemsToAdd.length) {
}
let importantFirstItem = itemsToAdd
this.splice('statuses', statuses.length, 0, ...itemsToAdd)
}
},
addTheseItems(items) {
if (!items.length) {
return
}
this.push(items.pop())
while (items.length) {
this.addItemLazily(items.pop())
}
},
addItemLazily(item) {
requestIdleCallback(() => {
this.push(item)
})
}
}
}

View file

@ -1,5 +1,4 @@
<div class="virtual-list">
<!-- <div class="virtual-list-viewport" ref:viewport></div> -->
<div class="virtual-list" style="height: {{$height}}px;">
{{#each $visibleItems as item @key}}
<VirtualListItem :component
offset="{{item.offset}}"
@ -27,9 +26,14 @@
})
})
let observedOnce = false
this.observe('distanceFromBottom', (distanceFromBottom) => {
//console.log('distanceFromBottom', distanceFromBottom)
if (distanceFromBottom > 0 && // hack: the first it's reported, it's always 0
if (!observedOnce) {
observedOnce = true // TODO: the first time is always 0... need better way to handle this
return
}
if (distanceFromBottom >= 0 &&
distanceFromBottom <= DISTANCE_FROM_BOTTOM_TO_FIRE) {
this.fire('scrollToBottom')
}

View file

@ -11,6 +11,7 @@
top: 0;
opacity: 0;
pointer-events: none;
/* will-change: transform; */ /* causes jank in mobile Firefox */
}
.shown {
opacity: 1;

View file

@ -17,8 +17,13 @@ const importIntersectionObserver = () => import(
/* webpackChunkname: 'intersection-observer' */ 'intersection-observer'
)
const importRequestIdleCallback = () => import(
/* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback'
)
export {
importURLSearchParams,
importTimeline,
importIntersectionObserver
importIntersectionObserver,
importRequestIdleCallback
}

View file

@ -11,7 +11,7 @@ const virtualListStore = new VirtualListStore({
virtualListStore.compute('visibleItems',
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
(items, scrollTop, itemHeights, offsetHeight) => {
let renderBuffer = 1.5 * offsetHeight
let renderBuffer = 3 * offsetHeight
let visibleItems = []
let totalOffset = 0
let len = items.length

View file

@ -1,11 +1,15 @@
import { init } from 'sapper/runtime.js'
import { importURLSearchParams } from '../routes/_utils/asyncModules'
import { importIntersectionObserver } from '../routes/_utils/asyncModules'
import {
importURLSearchParams,
importIntersectionObserver,
importRequestIdleCallback
} from '../routes/_utils/asyncModules'
// polyfills
Promise.all([
typeof URLSearchParams === 'undefined' && importURLSearchParams(),
typeof IntersectionObserver === 'undefined' && importIntersectionObserver()
typeof IntersectionObserver === 'undefined' && importIntersectionObserver(),
typeof requestIdleCallback === 'undefined' && importRequestIdleCallback()
]).then(() => {
// `routes` is an array of route objects injected by Sapper
init(document.querySelector('#sapper'), __routes__)