<div class="virtual-list-item {{shown ? 'shown' : ''}}" virtual-list-key="{{key}}" ref:node style="transform: translate3d(0, {{offset}}px, 0);" > <:Component {component} virtualProps="{{props}}" /> </div> <style> .virtual-list-item { position: absolute; top: 0; opacity: 0; pointer-events: none; /* will-change: transform; */ /* causes jank in mobile Firefox */ } .shown { opacity: 1; pointer-events: auto; } </style> <script> import { virtualListStore } from '../_utils/virtualListStore' 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') onIntersectionCallbacks[key] = entry => { let rect = entry.boundingClientRect updateItemHeights[key] = rect.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 = {} }) } intersectionObserver.observe(this.refs.node) }, ondestroy() { intersectionObserver.unobserve(this.refs.node) }, store: () => virtualListStore, computed: { 'shown': ($itemHeights, key) => $itemHeights[key] > 0 } } </script>