some more work on virtual scroll which is hard
This commit is contained in:
		
							parent
							
								
									3ef701fd57
								
							
						
					
					
						commit
						3f9ca66e38
					
				
					 6 changed files with 2510 additions and 61 deletions
				
			
		| 
						 | 
				
			
			@ -9,7 +9,7 @@
 | 
			
		|||
{{then constructor}}
 | 
			
		||||
<:Component {constructor} :target />
 | 
			
		||||
{{catch error}}
 | 
			
		||||
<div>Component failed to load. Please try refreshing!</div>
 | 
			
		||||
<div>Component failed to load. Please try refreshing! {{error}}</div>
 | 
			
		||||
{{/await}}
 | 
			
		||||
<style>
 | 
			
		||||
  .loading-page {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
<div class="timeline">
 | 
			
		||||
  <VirtualList component="{{StatusListItem}}" items="{{statuses}}" />
 | 
			
		||||
  <button type="button" on:click="addMoreItems()">Add more items</button>
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .timeline {
 | 
			
		||||
| 
						 | 
				
			
			@ -12,16 +13,30 @@
 | 
			
		|||
  import fixture from '../_utils/fixture.json'
 | 
			
		||||
  import StatusListItem from './StatusListItem.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 {
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      target: 'home',
 | 
			
		||||
      statuses: fixture,
 | 
			
		||||
      statuses: createData(),
 | 
			
		||||
      StatusListItem: StatusListItem
 | 
			
		||||
    }),
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    components: {
 | 
			
		||||
      VirtualList
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      splice: splice,
 | 
			
		||||
      addMoreItems() {
 | 
			
		||||
        this.splice('statuses', this.get('statuses').length, 0, ...createData())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +1,12 @@
 | 
			
		|||
<div class="virtual-list" ref:node style="height: {{height}}px;">
 | 
			
		||||
  {{#each visibleItems as visibleItem, index}}
 | 
			
		||||
<:Window bind:scrollY='scrollY'/>
 | 
			
		||||
<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
 | 
			
		||||
                     :intersectionObserver
 | 
			
		||||
                     virtualOffset="{{visibleItem.offset}}"
 | 
			
		||||
                     virtualProps="{{visibleItem.props}}"
 | 
			
		||||
                     virtualIndex="{{index}}" />
 | 
			
		||||
                     props="{{virtualItem.props}}"
 | 
			
		||||
                     index="{{virtualItem.index}}"
 | 
			
		||||
                     key="{{virtualItem.key}}"
 | 
			
		||||
    />
 | 
			
		||||
  {{/each}}
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,55 +16,22 @@
 | 
			
		|||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import VirtualListItem from './VirtualListItem'
 | 
			
		||||
 | 
			
		||||
  function sum(arr) {
 | 
			
		||||
    return arr.reduce((a, b) => a + b, 0)
 | 
			
		||||
  }
 | 
			
		||||
  import { virtualListStore } from '../_utils/virtualListStore'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      let intersectionObserver = new IntersectionObserver((entries) => {
 | 
			
		||||
        let totalHeight = sum(entries.map(entry => entry.boundingClientRect.height))
 | 
			
		||||
        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.observe('items', (items) => {
 | 
			
		||||
        this.store.set({'items': items})
 | 
			
		||||
      })
 | 
			
		||||
      this.set({
 | 
			
		||||
        intersectionObserver: intersectionObserver
 | 
			
		||||
      this.observe('scrollY', (scrollY) => {
 | 
			
		||||
        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: () => ({
 | 
			
		||||
      scrollHeight: 0,
 | 
			
		||||
      component: null,
 | 
			
		||||
      intersectionObserver: null,
 | 
			
		||||
      items: [],
 | 
			
		||||
      offsets: [],
 | 
			
		||||
      height: 400
 | 
			
		||||
      component: null
 | 
			
		||||
    }),
 | 
			
		||||
    store: () => virtualListStore,
 | 
			
		||||
    components: {
 | 
			
		||||
      VirtualListItem
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
<div class="virtual-list-item"
 | 
			
		||||
     ref:node
 | 
			
		||||
     style="transform: translate3d(0, {{virtualOffset}}px, 0);"
 | 
			
		||||
     data-virtual-index="{{virtualIndex}}">
 | 
			
		||||
  <:Component {component} :virtualProps :virtualIndex :intersectionObserver />
 | 
			
		||||
     style="transform: translate3d(0, {{itemOffset}}px, 0);"
 | 
			
		||||
     data-virtual-index="{{index}}"
 | 
			
		||||
     data-virtual-key="{{key}}"
 | 
			
		||||
>
 | 
			
		||||
  <:Component {component} virtualProps="{{props}}" />
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .virtual-list-item {
 | 
			
		||||
| 
						 | 
				
			
			@ -12,18 +14,19 @@
 | 
			
		|||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import { virtualListStore } from '../_utils/virtualListStore'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      this.observe('intersectionObserver', (intersectionObserver) => {
 | 
			
		||||
        if (intersectionObserver) {
 | 
			
		||||
          intersectionObserver.observe(this.refs.node)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      let itemHeights = this.store.get('itemHeights')
 | 
			
		||||
      itemHeights[this.get('key')] = this.refs.node.offsetHeight
 | 
			
		||||
      this.store.set({itemHeights: itemHeights})
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      showRefs () {
 | 
			
		||||
        console.log(this.refs)
 | 
			
		||||
    computed: {
 | 
			
		||||
      itemOffset: ($itemOffsets, key) => {
 | 
			
		||||
        return $itemOffsets[key] || 0
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
    store: () => virtualListStore
 | 
			
		||||
  }
 | 
			
		||||
</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…
	
	Add table
		
		Reference in a new issue