some more work on virtual scroll which is hard

This commit is contained in:
Nolan Lawson 2018-01-15 12:23:28 -08:00
parent 3ef701fd57
commit 3f9ca66e38
6 changed files with 2510 additions and 61 deletions

View File

@ -9,7 +9,7 @@
{{then constructor}} {{then constructor}}
<:Component {constructor} :target /> <:Component {constructor} :target />
{{catch error}} {{catch error}}
<div>Component failed to load. Please try refreshing!</div> <div>Component failed to load. Please try refreshing! {{error}}</div>
{{/await}} {{/await}}
<style> <style>
.loading-page { .loading-page {

View File

@ -1,5 +1,6 @@
<div class="timeline"> <div class="timeline">
<VirtualList component="{{StatusListItem}}" items="{{statuses}}" /> <VirtualList component="{{StatusListItem}}" items="{{statuses}}" />
<button type="button" on:click="addMoreItems()">Add more items</button>
</div> </div>
<style> <style>
.timeline { .timeline {
@ -12,16 +13,30 @@
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'
let i = -1
const createData = () => fixture.slice(0, 5).map(_ => ({
key: `${++i}`,
props: _
}))
export default { export default {
data: () => ({ data: () => ({
target: 'home', target: 'home',
statuses: fixture, statuses: createData(),
StatusListItem: StatusListItem StatusListItem: StatusListItem
}), }),
store: () => store, store: () => store,
components: { components: {
VirtualList VirtualList
},
methods: {
splice: splice,
addMoreItems() {
this.splice('statuses', this.get('statuses').length, 0, ...createData())
}
} }
} }
</script> </script>

View File

@ -1,10 +1,12 @@
<div class="virtual-list" ref:node style="height: {{height}}px;"> <:Window bind:scrollY='scrollY'/>
{{#each visibleItems as visibleItem, index}} <div class="virtual-list" ref:node style="height: {{$height}}px;">
<!-- <div class="virtual-list-viewport" ref:viewport></div> -->
{{#each $virtualItems as virtualItem, virtualIndex}}
<VirtualListItem :component <VirtualListItem :component
:intersectionObserver props="{{virtualItem.props}}"
virtualOffset="{{visibleItem.offset}}" index="{{virtualItem.index}}"
virtualProps="{{visibleItem.props}}" key="{{virtualItem.key}}"
virtualIndex="{{index}}" /> />
{{/each}} {{/each}}
</div> </div>
<style> <style>
@ -14,55 +16,22 @@
</style> </style>
<script> <script>
import VirtualListItem from './VirtualListItem' import VirtualListItem from './VirtualListItem'
import { virtualListStore } from '../_utils/virtualListStore'
function sum(arr) {
return arr.reduce((a, b) => a + b, 0)
}
export default { export default {
oncreate() { oncreate() {
let intersectionObserver = new IntersectionObserver((entries) => { this.observe('items', (items) => {
let totalHeight = sum(entries.map(entry => entry.boundingClientRect.height)) this.store.set({'items': items})
let offset = 0
let offsets = []
entries.forEach(entry => {
offsets.push(offset)
offset += entry.boundingClientRect.height
})
this.set({
height: totalHeight,
offsets: offsets
})
console.log('entries', entries.map(entry => entry.target.getAttribute('data-virtual-index')))
}, {
root: this.refs.node
}) })
this.set({ this.observe('scrollY', (scrollY) => {
intersectionObserver: intersectionObserver console.log('scrollY', scrollY)
this.store.set({scrollTop: scrollY})
}) })
}, },
ondestroy() {
let intersectionObserver = this.get('intersectionObserver')
if (intersectionObserver) {
intersectionObserver.disconnect()
}
},
computed: {
visibleItems: (items, offsets) => {
return items.map((item, idx) => ({
props: item,
offset: offsets[idx]
}))
}
},
data: () => ({ data: () => ({
scrollHeight: 0, component: null
component: null,
intersectionObserver: null,
items: [],
offsets: [],
height: 400
}), }),
store: () => virtualListStore,
components: { components: {
VirtualListItem VirtualListItem
} }

View File

@ -1,8 +1,10 @@
<div class="virtual-list-item" <div class="virtual-list-item"
ref:node ref:node
style="transform: translate3d(0, {{virtualOffset}}px, 0);" style="transform: translate3d(0, {{itemOffset}}px, 0);"
data-virtual-index="{{virtualIndex}}"> data-virtual-index="{{index}}"
<:Component {component} :virtualProps :virtualIndex :intersectionObserver /> data-virtual-key="{{key}}"
>
<:Component {component} virtualProps="{{props}}" />
</div> </div>
<style> <style>
.virtual-list-item { .virtual-list-item {
@ -12,18 +14,19 @@
} }
</style> </style>
<script> <script>
import { virtualListStore } from '../_utils/virtualListStore'
export default { export default {
oncreate() { oncreate() {
this.observe('intersectionObserver', (intersectionObserver) => { let itemHeights = this.store.get('itemHeights')
if (intersectionObserver) { itemHeights[this.get('key')] = this.refs.node.offsetHeight
intersectionObserver.observe(this.refs.node) this.store.set({itemHeights: itemHeights})
}
})
}, },
methods: { computed: {
showRefs () { itemOffset: ($itemOffsets, key) => {
console.log(this.refs) return $itemOffsets[key] || 0
} }
} },
store: () => virtualListStore
} }
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
import { Store } from 'svelte/store.js'
import { splice } from 'svelte-extras'
class VirtualListStore extends Store {
}
VirtualListStore.prototype.splice = splice
const virtualListStore = new VirtualListStore({
items: [],
itemHeights: {},
scrollTop: 0
})
virtualListStore.compute('virtualItems', ['items'], (items) => {
return items.map((item, idx) => ({
props: item.props,
key: item.key,
index: idx
}))
})
virtualListStore.compute('itemOffsets', ['virtualItems', 'itemHeights'], (virtualItems, itemHeights) => {
let itemOffsets = {}
let totalHeight = 0
virtualItems.forEach(item => {
let height = itemHeights[item.key] || 0
itemOffsets[item.key] = totalHeight
totalHeight += height
})
return itemOffsets
})
virtualListStore.compute('height', ['virtualItems', 'itemHeights'], (virtualItems, itemHeights) => {
let sum = 0
virtualItems.forEach(item => {
sum += itemHeights[item.key] || 0
})
return sum
})
if (process.browser && process.env.NODE_ENV !== 'production') {
window.virtualListStore = virtualListStore
}
export {
virtualListStore
}