fine-tune infinite scrolling list
This commit is contained in:
parent
eacf28317e
commit
9e111bfc5a
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -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": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"performance-now": "^2.1.0",
|
"performance-now": "^2.1.0",
|
||||||
"pify": "^3.0.0",
|
"pify": "^3.0.0",
|
||||||
|
"requestidlecallback": "^0.3.0",
|
||||||
"sapper": "^0.3.2",
|
"sapper": "^0.3.2",
|
||||||
"serve-static": "^1.13.1",
|
"serve-static": "^1.13.1",
|
||||||
"style-loader": "^0.19.1",
|
"style-loader": "^0.19.1",
|
||||||
|
|
|
@ -10,17 +10,20 @@
|
||||||
import Nav from './Nav.html';
|
import Nav from './Nav.html';
|
||||||
import { virtualListStore } from '../_utils/virtualListStore'
|
import { virtualListStore } from '../_utils/virtualListStore'
|
||||||
|
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
import throttle from 'lodash/throttle'
|
import throttle from 'lodash/throttle'
|
||||||
const THROTTLE_DELAY = 500
|
|
||||||
|
const SCROLL_EVENT_DELAY = 300
|
||||||
|
const RESIZE_EVENT_DELAY = 700
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
this.observe('innerHeight', throttle(() => {
|
this.observe('innerHeight', debounce(() => {
|
||||||
// respond to window resize events
|
// respond to window resize events
|
||||||
this.store.set({
|
this.store.set({
|
||||||
offsetHeight: this.refs.node.offsetHeight
|
offsetHeight: this.refs.node.offsetHeight
|
||||||
})
|
})
|
||||||
}, THROTTLE_DELAY))
|
}, RESIZE_EVENT_DELAY))
|
||||||
this.store.set({
|
this.store.set({
|
||||||
scrollTop: this.refs.node.scrollTop,
|
scrollTop: this.refs.node.scrollTop,
|
||||||
scrollHeight: this.refs.node.scrollHeight,
|
scrollHeight: this.refs.node.scrollHeight,
|
||||||
|
@ -33,7 +36,10 @@
|
||||||
store: () => virtualListStore,
|
store: () => virtualListStore,
|
||||||
events: {
|
events: {
|
||||||
scroll(node, callback) {
|
scroll(node, callback) {
|
||||||
const onScroll = throttle(callback, THROTTLE_DELAY)
|
const onScroll = throttle(callback, SCROLL_EVENT_DELAY, {
|
||||||
|
leading: true,
|
||||||
|
trailing: true
|
||||||
|
})
|
||||||
node.addEventListener('scroll', onScroll);
|
node.addEventListener('scroll', onScroll);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
import fixture from '../_utils/fixture.json'
|
import fixture from '../_utils/fixture.json'
|
||||||
import StatusListItem from './StatusListItem.html'
|
import StatusListItem from './StatusListItem.html'
|
||||||
import VirtualList from './VirtualList.html'
|
import VirtualList from './VirtualList.html'
|
||||||
import { splice } from 'svelte-extras'
|
import { splice, push } from 'svelte-extras'
|
||||||
|
|
||||||
let i = -1
|
let i = -1
|
||||||
|
|
||||||
|
@ -35,12 +35,33 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
splice: splice,
|
splice: splice,
|
||||||
|
push: push,
|
||||||
addMoreItems() {
|
addMoreItems() {
|
||||||
console.log('addMoreItems')
|
console.log('addMoreItems')
|
||||||
let statuses = this.get('statuses')
|
let statuses = this.get('statuses')
|
||||||
if (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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<div class="virtual-list">
|
<div class="virtual-list" style="height: {{$height}}px;">
|
||||||
<!-- <div class="virtual-list-viewport" ref:viewport></div> -->
|
|
||||||
{{#each $visibleItems as item @key}}
|
{{#each $visibleItems as item @key}}
|
||||||
<VirtualListItem :component
|
<VirtualListItem :component
|
||||||
offset="{{item.offset}}"
|
offset="{{item.offset}}"
|
||||||
|
@ -27,9 +26,14 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let observedOnce = false
|
||||||
|
|
||||||
this.observe('distanceFromBottom', (distanceFromBottom) => {
|
this.observe('distanceFromBottom', (distanceFromBottom) => {
|
||||||
//console.log('distanceFromBottom', distanceFromBottom)
|
if (!observedOnce) {
|
||||||
if (distanceFromBottom > 0 && // hack: the first it's reported, it's always 0
|
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) {
|
distanceFromBottom <= DISTANCE_FROM_BOTTOM_TO_FIRE) {
|
||||||
this.fire('scrollToBottom')
|
this.fire('scrollToBottom')
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
/* will-change: transform; */ /* causes jank in mobile Firefox */
|
||||||
}
|
}
|
||||||
.shown {
|
.shown {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -17,8 +17,13 @@ const importIntersectionObserver = () => import(
|
||||||
/* webpackChunkname: 'intersection-observer' */ 'intersection-observer'
|
/* webpackChunkname: 'intersection-observer' */ 'intersection-observer'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const importRequestIdleCallback = () => import(
|
||||||
|
/* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback'
|
||||||
|
)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
importURLSearchParams,
|
importURLSearchParams,
|
||||||
importTimeline,
|
importTimeline,
|
||||||
importIntersectionObserver
|
importIntersectionObserver,
|
||||||
|
importRequestIdleCallback
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ const virtualListStore = new VirtualListStore({
|
||||||
virtualListStore.compute('visibleItems',
|
virtualListStore.compute('visibleItems',
|
||||||
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
|
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
|
||||||
(items, scrollTop, itemHeights, offsetHeight) => {
|
(items, scrollTop, itemHeights, offsetHeight) => {
|
||||||
let renderBuffer = 1.5 * offsetHeight
|
let renderBuffer = 3 * offsetHeight
|
||||||
let visibleItems = []
|
let visibleItems = []
|
||||||
let totalOffset = 0
|
let totalOffset = 0
|
||||||
let len = items.length
|
let len = items.length
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { init } from 'sapper/runtime.js'
|
import { init } from 'sapper/runtime.js'
|
||||||
import { importURLSearchParams } from '../routes/_utils/asyncModules'
|
import {
|
||||||
import { importIntersectionObserver } from '../routes/_utils/asyncModules'
|
importURLSearchParams,
|
||||||
|
importIntersectionObserver,
|
||||||
|
importRequestIdleCallback
|
||||||
|
} from '../routes/_utils/asyncModules'
|
||||||
|
|
||||||
// polyfills
|
// polyfills
|
||||||
Promise.all([
|
Promise.all([
|
||||||
typeof URLSearchParams === 'undefined' && importURLSearchParams(),
|
typeof URLSearchParams === 'undefined' && importURLSearchParams(),
|
||||||
typeof IntersectionObserver === 'undefined' && importIntersectionObserver()
|
typeof IntersectionObserver === 'undefined' && importIntersectionObserver(),
|
||||||
|
typeof requestIdleCallback === 'undefined' && importRequestIdleCallback()
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
// `routes` is an array of route objects injected by Sapper
|
// `routes` is an array of route objects injected by Sapper
|
||||||
init(document.querySelector('#sapper'), __routes__)
|
init(document.querySelector('#sapper'), __routes__)
|
||||||
|
|
Loading…
Reference in a new issue