diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 95270055dd..6a2c456883 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -14,7 +14,7 @@ import { onNuxtReady } from '../composables/ready' import { navigateTo, useRouter } from '../composables/router' import { useNuxtApp, useRuntimeConfig } from '../nuxt' import { cancelIdleCallback, requestIdleCallback } from '../compat/idle-callback' -import { useObserver } from '../utils' +import type { ObserveFn, CallbackFn } from '../utils' // @ts-expect-error virtual file import { nuxtLinkDefaults } from '#build/nuxt.config.mjs' @@ -437,6 +437,47 @@ function applyTrailingSlashBehavior (to: string, trailingSlash: NuxtLinkOptions[ // --- Prefetching utils --- +function useObserver (): { observe: ObserveFn } | undefined { + if (import.meta.server) { return } + + const nuxtApp = useNuxtApp() + if (nuxtApp._observer) { + return nuxtApp._observer + } + + let observer: IntersectionObserver | null = null + + const callbacks = new Map() + + const observe: ObserveFn = (element, callback) => { + if (!observer) { + observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + const callback = callbacks.get(entry.target) + const isVisible = entry.isIntersecting || entry.intersectionRatio > 0 + if (isVisible && callback) { callback() } + } + }) + } + callbacks.set(element, callback) + observer.observe(element) + return () => { + callbacks.delete(element) + observer!.unobserve(element) + if (callbacks.size === 0) { + observer!.disconnect() + observer = null + } + } + } + + const _observer = nuxtApp._observer = { + observe + } + + return _observer +} + function isSlowConnection () { if (import.meta.server) { return } diff --git a/packages/nuxt/src/app/utils.ts b/packages/nuxt/src/app/utils.ts index a46715cb21..237f51e6ce 100644 --- a/packages/nuxt/src/app/utils.ts +++ b/packages/nuxt/src/app/utils.ts @@ -1,49 +1,6 @@ -import { useNuxtApp } from './nuxt' - export function toArray (value: T | T[]): T[] { return Array.isArray(value) ? value : [value] } -type CallbackFn = () => void -type ObserveFn = (element: Element, callback: CallbackFn) => () => void - -export function useObserver (): { observe: ObserveFn } | undefined { - if (import.meta.server) { return } - - const nuxtApp = useNuxtApp() - if (nuxtApp._observer) { - return nuxtApp._observer - } - - let observer: IntersectionObserver | null = null - - const callbacks = new Map() - - const observe: ObserveFn = (element, callback) => { - if (!observer) { - observer = new IntersectionObserver((entries) => { - for (const entry of entries) { - const callback = callbacks.get(entry.target) - const isVisible = entry.isIntersecting || entry.intersectionRatio > 0 - if (isVisible && callback) { callback() } - } - }) - } - callbacks.set(element, callback) - observer.observe(element) - return () => { - callbacks.delete(element) - observer!.unobserve(element) - if (callbacks.size === 0) { - observer!.disconnect() - observer = null - } - } - } - - const _observer = nuxtApp._observer = { - observe, - } - - return _observer -} +export type CallbackFn = () => void +export type ObserveFn = (element: Element, callback: CallbackFn) => () => void diff --git a/packages/nuxt/src/components/runtime/client-delayed-component.ts b/packages/nuxt/src/components/runtime/client-delayed-component.ts index 5386172dd2..c4dba7d4fa 100644 --- a/packages/nuxt/src/components/runtime/client-delayed-component.ts +++ b/packages/nuxt/src/components/runtime/client-delayed-component.ts @@ -1,9 +1,40 @@ import { createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, onBeforeUnmount, onMounted, ref } from 'vue' import type { Component, Ref } from 'vue' // import ClientOnly from '#app/components/client-only' -import { useObserver } from '#app/utils' +import type { ObserveFn } from '#app/utils' import { getFragmentHTML } from '#app/components/utils' import { useNuxtApp } from '#app/nuxt' +import { requestIdleCallback, cancelIdleCallback } from '#app/compat/idle-callback' +import { onNuxtReady } from '#app' + +function useIntersectionObserver(options: IntersectionObserverInit) : {observe: ObserveFn} { + if (import.meta.server) { return {observe: () => () => {}}} + + let observer: IntersectionObserver | null = null + + const observe: ObserveFn = (element, callback) => { + if (!observer) { + observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + const isVisible = entry.isIntersecting || entry.intersectionRatio > 0 + if (isVisible && callback) { callback() } + } + }, options) + } + observer.observe(element) + return () => { + observer!.unobserve(element) + observer!.disconnect() + observer = null + } + } + + const _observer = { + observe + } + + return _observer +} function elementIsVisibleInViewport (el: Element) { const { top, left, bottom, right } = el.getBoundingClientRect() @@ -35,7 +66,7 @@ export const createLazyIOClientPage = (componentLoader: Component) => { if (!isIntersecting.value) { onMounted(() => { - const observer = useObserver() + const observer = useIntersectionObserver(attrs.loader ?? {}) unobserve = observer!.observe(el.value as Element, () => { isIntersecting.value = true unobserve?.() @@ -69,10 +100,12 @@ export const createLazyNetworkClientPage = (componentLoader: Component) => { const isIdle = ref(false) let idleHandle: number | null = null onMounted(() => { - idleHandle = requestIdleCallback(() => { - isIdle.value = true - cancelIdleCallback(idleHandle as unknown as number) - idleHandle = null + onNuxtReady(()=>{ + idleHandle = requestIdleCallback(() => { + isIdle.value = true + cancelIdleCallback(idleHandle as unknown as number) + idleHandle = null + }, attrs.loader ?? {timeout: 10000}) }) }) onBeforeUnmount(() => {