some more work on virtual scroll which is hard
This commit is contained in:
parent
3ef701fd57
commit
3f9ca66e38
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
|
@ -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({
|
this.observe('scrollY', (scrollY) => {
|
||||||
height: totalHeight,
|
console.log('scrollY', scrollY)
|
||||||
offsets: offsets
|
this.store.set({scrollTop: scrollY})
|
||||||
})
|
})
|
||||||
console.log('entries', entries.map(entry => entry.target.getAttribute('data-virtual-index')))
|
|
||||||
}, {
|
|
||||||
root: this.refs.node
|
|
||||||
})
|
|
||||||
this.set({
|
|
||||||
intersectionObserver: intersectionObserver
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
48
routes/_utils/virtualListStore.js
Normal file
48
routes/_utils/virtualListStore.js
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue