<div class="pseudo-virtual-list" on:initialized ref:node> {{#each wrappedItems as wrappedItem, i @item}} <PseudoVirtualListLazyItem component="{{component}}" index="{{i}}" length="{{wrappedItems.length}}" makeProps="{{makeProps}}" key="{{wrappedItem.item}}" intersectionObserver="{{intersectionObserver}}" isIntersecting="{{isIntersecting(wrappedItem.item, $intersectionStates)}}" isCached="{{isCached(wrappedItem.item, $intersectionStates)}}" height="{{getHeight(wrappedItem.item, $intersectionStates)}}" /> {{/each}} </div> <style> .pseudo-virtual-list { position: relative; } </style> <script> import PseudoVirtualListLazyItem from './PseudoVirtualListLazyItem.html' import { getRectFromEntry } from '../../_utils/getRectFromEntry' import { mark, stop } from '../../_utils/marks' import { pseudoVirtualListStore } from './pseudoVirtualListStore' export default { oncreate() { mark('PseudoVirtualList oncreate()') this.store.setCurrentRealm(this.get('realm')) // When re-rendering, assume all items are non-intersecting until told otherwise. // We already have the heights cached. let intersectionStates = this.store.get('intersectionStates') let keys = Object.keys(intersectionStates) for (let key of keys) { intersectionStates[key].isCached = true } this.store.setForRealm({intersectionStates: intersectionStates}) let intersectionObserver = new IntersectionObserver(this.onIntersection.bind(this), { root: document.querySelector(this.get('containerQuery')), rootMargin: '300% 0px' }) this.set({intersectionObserver}) this.observe('allItemsHaveHeight', allItemsHaveHeight => { console.log('allItemsHaveHeight', allItemsHaveHeight) if (allItemsHaveHeight && !this.get('initialized')) { this.set({initialized: true}) console.log('initialized PseudoVirtualList') this.fire('initialized') } }) stop('PseudoVirtualList oncreate()') }, ondestroy() { let intersectionObserver = this.get('intersectionObserver') if (intersectionObserver) { intersectionObserver.disconnect() } this.store.setCurrentRealm(null) }, helpers: { isIntersecting(key, $intersectionStates) { return !!($intersectionStates[key] && $intersectionStates[key].isIntersecting) }, isCached(key, $intersectionStates) { return !!($intersectionStates[key] && $intersectionStates[key].isCached) }, getHeight(key, $intersectionStates) { return $intersectionStates[key] && $intersectionStates[key].height } }, methods: { scrollToPosition(element) { if (this.get('scrolledToPosition')) { return } this.set({scrolledToPosition: true}) requestAnimationFrame(() => { console.log('scrolling element into view') element.scrollIntoView(true) }) }, onIntersection(entries) { mark('onIntersection') let newIntersectionStates = {} let scrollToItem = this.get('scrollToItem') let intersectionStates = this.store.get('intersectionStates') for (let entry of entries) { let key = entry.target.getAttribute('pseudo-virtual-list-key') let rect = getRectFromEntry(entry) newIntersectionStates[key] = { isIntersecting: entry.isIntersecting, height: rect.height } if (scrollToItem === key) { this.scrollToPosition(entry.target) } } intersectionStates = Object.assign(intersectionStates, newIntersectionStates) this.store.setForRealm({intersectionStates: intersectionStates}) stop('onIntersection') } }, computed: { wrappedItems: (items) => items ? items.map(item => ({item})) : [], allItemsHaveHeight: (items, $intersectionStates) => { if (!items) { return false } for (let item of items) { if (!$intersectionStates[item]) { return false } } return true } }, components: { PseudoVirtualListLazyItem }, store: () => pseudoVirtualListStore } </script>