use IntersectionObserver for virtual scroll
This commit is contained in:
		
							parent
							
								
									94cab7aaf7
								
							
						
					
					
						commit
						5e3e56d454
					
				
					 8 changed files with 63 additions and 23 deletions
				
			
		
							
								
								
									
										5
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -3344,6 +3344,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ="
 | 
			
		||||
    },
 | 
			
		||||
    "intersection-observer": {
 | 
			
		||||
      "version": "0.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-8Zgt4ijlyvIrQVTA7MPb2W9+KhoetrAbxlh0RmTGxpx0+ZsAXvy7IsbNnZIrqZ6TddAdWeQj49x7Ph7Ir6KRkA=="
 | 
			
		||||
    },
 | 
			
		||||
    "intl-messageformat": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@
 | 
			
		|||
    "font-awesome-svg-png": "^1.2.2",
 | 
			
		||||
    "glob": "^7.1.2",
 | 
			
		||||
    "idb": "^2.0.4",
 | 
			
		||||
    "intersection-observer": "^0.5.0",
 | 
			
		||||
    "intl-relativeformat": "^2.1.0",
 | 
			
		||||
    "lodash": "^4.17.4",
 | 
			
		||||
    "node-fetch": "^1.7.3",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,7 +61,7 @@
 | 
			
		|||
        ".............. status-toolbar";
 | 
			
		||||
    grid-template-columns: 58px 1fr;
 | 
			
		||||
    border-bottom: 1px solid var(--main-border);
 | 
			
		||||
    will-change: transform; /* TODO: is this necessary? */
 | 
			
		||||
    /* will-change: transform; */ /* TODO: is this necessary? */
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :global(.status-sidebar) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,11 @@
 | 
			
		|||
  <VirtualList component="{{StatusListItem}}" items="{{statuses}}" />
 | 
			
		||||
  <button type="button" on:click="addMoreItems()">Add more items</button>
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .timeline {
 | 
			
		||||
    min-height: 50vh;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import { store } from '../_utils/store'
 | 
			
		||||
  import { getHomeTimeline } from '../_utils/mastodon/oauth'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,8 +42,6 @@
 | 
			
		|||
        })
 | 
			
		||||
      }, THROTTLE_TIME))
 | 
			
		||||
    },
 | 
			
		||||
    ondestroy () {
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      component: null
 | 
			
		||||
    }),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
<div class="virtual-list-item"
 | 
			
		||||
<div class="virtual-list-item {{shown ? 'shown' : ''}}"
 | 
			
		||||
     virtual-list-key="{{key}}"
 | 
			
		||||
     ref:node
 | 
			
		||||
     style="transform: translate3d(0, {{offset}}px, 0);"
 | 
			
		||||
>
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +9,12 @@
 | 
			
		|||
  .virtual-list-item {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
  }
 | 
			
		||||
  .shown {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    pointer-events: auto;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,28 +22,45 @@
 | 
			
		|||
 | 
			
		||||
  let updateItemHeights = {}
 | 
			
		||||
  let promise = Promise.resolve()
 | 
			
		||||
  let onIntersectionCallbacks = {}
 | 
			
		||||
 | 
			
		||||
  let intersectionObserver = new IntersectionObserver(entries => {
 | 
			
		||||
    entries.forEach(entry => {
 | 
			
		||||
      let key = entry.target.getAttribute('virtual-list-key')
 | 
			
		||||
      onIntersectionCallbacks[key](entry)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      let key = this.get('key')
 | 
			
		||||
      updateItemHeights[key] = this.refs.node.offsetHeight
 | 
			
		||||
      promise.then(() => {
 | 
			
		||||
        // update all item heights in one microtask batch for better perf
 | 
			
		||||
        let updatedKeys = Object.keys(updateItemHeights)
 | 
			
		||||
        if (!updatedKeys.length) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        // batch all updates to itemHeights for better perf
 | 
			
		||||
        let itemHeights = this.store.get('itemHeights')
 | 
			
		||||
        for (key of updatedKeys) {
 | 
			
		||||
          itemHeights[key] = updateItemHeights[key]
 | 
			
		||||
        }
 | 
			
		||||
        this.store.set({
 | 
			
		||||
          itemHeights: itemHeights
 | 
			
		||||
      onIntersectionCallbacks[key] = entry => {
 | 
			
		||||
        updateItemHeights[key] = entry.boundingClientRect.height
 | 
			
		||||
        promise.then(() => {
 | 
			
		||||
          // update all item heights in one microtask batch for better perf
 | 
			
		||||
          let updatedKeys = Object.keys(updateItemHeights)
 | 
			
		||||
          if (!updatedKeys.length) {
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
          // batch all updates to itemHeights for better perf
 | 
			
		||||
          let itemHeights = this.store.get('itemHeights')
 | 
			
		||||
          for (key of updatedKeys) {
 | 
			
		||||
            itemHeights[key] = updateItemHeights[key]
 | 
			
		||||
          }
 | 
			
		||||
          this.store.set({
 | 
			
		||||
            itemHeights: itemHeights
 | 
			
		||||
          })
 | 
			
		||||
          updateItemHeights = {}
 | 
			
		||||
        })
 | 
			
		||||
        updateItemHeights = {}
 | 
			
		||||
      })
 | 
			
		||||
      }
 | 
			
		||||
      intersectionObserver.observe(this.refs.node)
 | 
			
		||||
    },
 | 
			
		||||
    store: () => virtualListStore
 | 
			
		||||
    ondestroy() {
 | 
			
		||||
      intersectionObserver.unobserve(this.refs.node)
 | 
			
		||||
    },
 | 
			
		||||
    store: () => virtualListStore,
 | 
			
		||||
    computed: {
 | 
			
		||||
      'shown': ($itemHeights, key) => $itemHeights[key] > 0
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +13,12 @@ const importTimeline = () => import(
 | 
			
		|||
  /* webpackChunkName: 'Timeline' */ '../_components/Timeline.html'
 | 
			
		||||
  ).then(mod => mod.default)
 | 
			
		||||
 | 
			
		||||
const importIntersectionObserver = () => import(
 | 
			
		||||
  /* webpackChunkname: 'intersection-observer' */ 'intersection-observer'
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  importURLSearchParams,
 | 
			
		||||
  importTimeline
 | 
			
		||||
  importTimeline,
 | 
			
		||||
  importIntersectionObserver
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
import { init } from 'sapper/runtime.js'
 | 
			
		||||
import { importURLSearchParams } from '../routes/_utils/asyncModules'
 | 
			
		||||
import { importIntersectionObserver } from '../routes/_utils/asyncModules'
 | 
			
		||||
 | 
			
		||||
// polyfills
 | 
			
		||||
Promise.all([
 | 
			
		||||
  typeof URLSearchParams === 'undefined' && importURLSearchParams()
 | 
			
		||||
  typeof URLSearchParams === 'undefined' && importURLSearchParams(),
 | 
			
		||||
  typeof IntersectionObserver === 'undefined' && importIntersectionObserver()
 | 
			
		||||
]).then(() => {
 | 
			
		||||
  // `routes` is an array of route objects injected by Sapper
 | 
			
		||||
  init(document.querySelector('#sapper'), __routes__)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue