pinafore/routes/_utils/virtualListStore.js

140 lines
3.1 KiB
JavaScript
Raw Normal View History

import { Store } from 'svelte/store.js'
2018-01-17 09:59:15 +01:00
import { mark, stop } from '../_utils/marks'
2018-01-21 00:37:40 +01:00
const VIEWPORT_RENDER_FACTOR = 4
2018-01-24 18:47:31 +01:00
const cloneKeys = [
'items',
'itemHeights',
'scrollTop',
'scrollHeight',
'offsetHeight'
]
class VirtualListStore extends Store {
2018-01-18 04:53:12 +01:00
constructor(state) {
super(state)
2018-01-18 04:41:37 +01:00
this._batches = {}
}
2018-01-24 18:47:31 +01:00
cloneState() {
let res = {}
for (let key of cloneKeys) {
res[key] = this.get(key)
}
return res
}
2018-01-18 04:41:37 +01:00
batchUpdate(key, subKey, value) {
let batch = this._batches[key]
if (!batch) {
batch = this._batches[key] = {}
}
batch[subKey] = value
requestAnimationFrame(() => {
2018-01-18 04:52:18 +01:00
let batch = this._batches[key]
if (!batch) {
return
}
2018-01-18 04:41:37 +01:00
let updatedKeys = Object.keys(batch)
if (!updatedKeys.length) {
return
}
mark('batchUpdate()')
let obj = this.get(key)
for (let otherKey of updatedKeys) {
obj[otherKey] = batch[otherKey]
}
delete this._batches[key]
let toSet = {}
toSet[key] = obj
this.set(toSet)
stop('batchUpdate()')
})
}
}
const virtualListStore = new VirtualListStore({
items: [],
itemHeights: {},
2018-01-22 01:07:11 +01:00
showFooter: false,
footerHeight: 0
})
2018-01-16 01:12:07 +01:00
virtualListStore.compute('visibleItems',
2018-01-17 06:43:31 +01:00
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
(items, scrollTop, itemHeights, offsetHeight) => {
2018-01-17 09:59:15 +01:00
mark('compute visibleItems')
2018-01-21 00:37:40 +01:00
let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight
2018-01-16 01:12:07 +01:00
let visibleItems = []
2018-01-16 02:25:32 +01:00
let totalOffset = 0
let len = items.length
let i = -1
while (++i < len) {
2018-01-24 03:19:03 +01:00
let key = items[i]
2018-01-16 01:35:08 +01:00
let height = itemHeights[key] || 0
2018-01-16 02:25:32 +01:00
let currentOffset = totalOffset
totalOffset += height
let isBelowViewport = (currentOffset < scrollTop)
if (isBelowViewport) {
2018-01-16 03:29:28 +01:00
if (scrollTop - renderBuffer > currentOffset) {
2018-01-16 02:25:32 +01:00
continue // below the area we want to render
}
2018-01-16 01:12:07 +01:00
} else {
2018-01-17 08:16:15 +01:00
if (currentOffset > (scrollTop + height + renderBuffer)) {
2018-01-16 02:25:32 +01:00
break // above the area we want to render
}
2018-01-16 01:12:07 +01:00
}
2018-01-16 02:25:32 +01:00
visibleItems.push({
offset: currentOffset,
key: key,
2018-01-17 08:16:15 +01:00
index: i
2018-01-16 02:25:32 +01:00
})
}
2018-01-17 09:59:15 +01:00
stop('compute visibleItems')
2018-01-16 01:12:07 +01:00
return visibleItems
})
2018-01-22 01:07:11 +01:00
virtualListStore.compute('heightWithoutFooter',
['items', 'itemHeights'],
(items, itemHeights) => {
let sum = 0
2018-01-16 02:25:32 +01:00
let i = -1
let len = items.length
while (++i < len) {
2018-01-24 03:19:03 +01:00
sum += itemHeights[items[i]] || 0
2018-01-16 02:25:32 +01:00
}
return sum
})
2018-01-22 01:07:11 +01:00
virtualListStore.compute('height',
['heightWithoutFooter', 'showFooter', 'footerHeight'],
(heightWithoutFooter, showFooter, footerHeight) => {
return showFooter ? (heightWithoutFooter + footerHeight) : heightWithoutFooter
})
2018-01-18 08:00:33 +01:00
virtualListStore.compute('numItems', ['items'], (items) => items.length)
2018-01-25 03:04:25 +01:00
virtualListStore.compute('allVisibleItemsHaveHeight',
['visibleItems', 'itemHeights'],
(visibleItems, itemHeights) => {
2018-01-25 03:58:10 +01:00
if (!visibleItems.length) {
return false
}
2018-01-25 03:04:25 +01:00
for (let visibleItem of visibleItems) {
if (!itemHeights[visibleItem.key]) {
return false
}
}
return true
})
if (process.browser && process.env.NODE_ENV !== 'production') {
window.virtualListStore = virtualListStore
}
export {
virtualListStore
}