diff --git a/packages/interactivity/src/init.ts b/packages/interactivity/src/init.ts index ddf6785d4dfdf..43450539cb121 100644 --- a/packages/interactivity/src/init.ts +++ b/packages/interactivity/src/init.ts @@ -29,18 +29,44 @@ export const initialVdom = new WeakMap< Element, ComponentChild[] >(); // Initialize the router with the initial DOM. export const init = async () => { + const pendingNodes = new Set(); + + const intersectionObserver = new window.IntersectionObserver( + async ( entries ) => { + for ( const entry of entries ) { + if ( ! entry.isIntersecting ) { + continue; + } + + const node = entry.target; + intersectionObserver.unobserve( node ); + pendingNodes.delete( node ); + if ( pendingNodes.size === 0 ) { + intersectionObserver.disconnect(); + } + + if ( ! hydratedIslands.has( node ) ) { + const fragment = getRegionRootFragment( node ); + const vdom = toVdom( node ); + await splitTask(); + hydrate( vdom, fragment ); + await splitTask(); + } + } + }, + { + root: null, // To watch for intersection relative to the device's viewport. + rootMargin: '100% 0% 100% 0%', // Intersect when within 1 viewport approaching from top or bottom. + threshold: 0.0, // As soon as even one pixel is visible. + } + ); + const nodes = document.querySelectorAll( `[data-${ directivePrefix }-interactive]` ); for ( const node of nodes ) { - if ( ! hydratedIslands.has( node ) ) { - await splitTask(); - const fragment = getRegionRootFragment( node ); - const vdom = toVdom( node ); - initialVdom.set( node, vdom ); - await splitTask(); - hydrate( vdom, fragment ); - } + pendingNodes.add( node ); + intersectionObserver.observe( node ); } };