From 9e111bfc5a5c08bc3bb93c97a671f235ed5ea6f3 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Wed, 17 Jan 2018 00:06:24 -0800 Subject: [PATCH] fine-tune infinite scrolling list --- package-lock.json | 5 +++++ package.json | 1 + routes/_components/Layout.html | 14 ++++++++++---- routes/_components/Timeline.html | 25 +++++++++++++++++++++++-- routes/_components/VirtualList.html | 12 ++++++++---- routes/_components/VirtualListItem.html | 1 + routes/_utils/asyncModules.js | 7 ++++++- routes/_utils/virtualListStore.js | 2 +- templates/main.js | 10 +++++++--- 9 files changed, 62 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3330b4..069b510 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7172076..402fb5e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/routes/_components/Layout.html b/routes/_components/Layout.html index 5449142..0beecf7 100644 --- a/routes/_components/Layout.html +++ b/routes/_components/Layout.html @@ -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 { diff --git a/routes/_components/Timeline.html b/routes/_components/Timeline.html index 7533eb9..42cc862 100644 --- a/routes/_components/Timeline.html +++ b/routes/_components/Timeline.html @@ -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) + }) } } } diff --git a/routes/_components/VirtualList.html b/routes/_components/VirtualList.html index 6a90547..f7d0610 100644 --- a/routes/_components/VirtualList.html +++ b/routes/_components/VirtualList.html @@ -1,5 +1,4 @@ -
- +
{{#each $visibleItems as item @key}} { - //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') } diff --git a/routes/_components/VirtualListItem.html b/routes/_components/VirtualListItem.html index ce65e08..286bd0b 100644 --- a/routes/_components/VirtualListItem.html +++ b/routes/_components/VirtualListItem.html @@ -11,6 +11,7 @@ top: 0; opacity: 0; pointer-events: none; + /* will-change: transform; */ /* causes jank in mobile Firefox */ } .shown { opacity: 1; diff --git a/routes/_utils/asyncModules.js b/routes/_utils/asyncModules.js index f3248ad..a665f40 100644 --- a/routes/_utils/asyncModules.js +++ b/routes/_utils/asyncModules.js @@ -17,8 +17,13 @@ const importIntersectionObserver = () => import( /* webpackChunkname: 'intersection-observer' */ 'intersection-observer' ) +const importRequestIdleCallback = () => import( + /* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback' + ) + export { importURLSearchParams, importTimeline, - importIntersectionObserver + importIntersectionObserver, + importRequestIdleCallback } \ No newline at end of file diff --git a/routes/_utils/virtualListStore.js b/routes/_utils/virtualListStore.js index ed9121c..d1cdfdf 100644 --- a/routes/_utils/virtualListStore.js +++ b/routes/_utils/virtualListStore.js @@ -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 diff --git a/templates/main.js b/templates/main.js index 924155b..7c0e6fb 100644 --- a/templates/main.js +++ b/templates/main.js @@ -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__)