diff --git a/packages/vue-use-fixed-header/src/useFixedHeader.ts b/packages/vue-use-fixed-header/src/useFixedHeader.ts index 216af8b..6d0e3de 100644 --- a/packages/vue-use-fixed-header/src/useFixedHeader.ts +++ b/packages/vue-use-fixed-header/src/useFixedHeader.ts @@ -1,6 +1,6 @@ import { shallowRef, ref, unref, watch, computed, readonly, type CSSProperties as CSS } from 'vue' -import { isSSR, useReducedMotion } from './utils' +import { useReducedMotion, isMetaFramework, isBrowser } from './utils' import { TRANSITION_STYLES } from './constants' import type { UseFixedHeaderOptions, MaybeTemplateRef } from './types' @@ -32,7 +32,7 @@ export function useFixedHeader( skipInitialObserverCb: true, isListeningScroll: false, isHovering: false, - isMount: true, + isScrollRestoration: true, } const styles = shallowRef({}) @@ -83,16 +83,18 @@ export function useFixedHeader( /** * Hides the header on page load before it has a chance to paint * if scroll restoration is instant. If not, applies the enter - * styles immediately (without transitions). + * styles immediately (without transition). + * + * This is not possible if the header is server-rendered. + * In that case, the header will be visible and start transitioning + * when manually scrolled. */ function onScrollRestoration() { window.requestAnimationFrame(() => { - if (!internal.isMount) return + if (!internal.isScrollRestoration) return + internal.isScrollRestoration = false - if (!isFixed()) { - internal.isMount = false - return - } + if (!isFixed() || isMetaFramework) return const isInstant = getScrollTop() > getHeaderHeight() * 1.2 // Resolves to false if scroll is smooth @@ -108,8 +110,6 @@ export function useFixedHeader( ...(transitionOpacity() ? { opacity: 1 } : {}), }) } - - internal.isMount = false }) } @@ -189,7 +189,7 @@ export function useFixedHeader( // Scroll Events function createScrollHandler() { - let prevTop = 0 + let prevTop = isBrowser ? getScrollTop() : 0 return () => { const scrollTop = getScrollTop() @@ -276,7 +276,7 @@ export function useFixedHeader( removePointerListener() } - !isSSR && + isBrowser && watch( () => [unref(target), getRoot(), isReduced.value, unref(options.watch)], ([headerEl, rootEl, isReduced], _, onCleanup) => { @@ -290,8 +290,9 @@ export function useFixedHeader( onCleanup(() => { removeListeners() - internal.resizeObserver?.disconnect() removeStyles() + internal.resizeObserver?.disconnect() + internal.skipInitialObserverCb = true }) }, { immediate: true, flush: 'post' }, diff --git a/packages/vue-use-fixed-header/src/utils.ts b/packages/vue-use-fixed-header/src/utils.ts index 577ea57..62ae7c6 100644 --- a/packages/vue-use-fixed-header/src/utils.ts +++ b/packages/vue-use-fixed-header/src/utils.ts @@ -1,11 +1,17 @@ import { onBeforeUnmount, onMounted, ref } from 'vue' -export const isSSR = typeof window === 'undefined' +export const isBrowser = typeof window !== 'undefined' + +const isNuxt = isBrowser && '__NUXT__' in window +const isAstro = isBrowser && 'Astro' in window +const isQuasar = isBrowser && '__Q_META__' in window + +export const isMetaFramework = isNuxt || isAstro || isQuasar export function useReducedMotion() { const isReduced = ref(false) - if (isSSR) return isReduced + if (!isBrowser) return isReduced const query = window.matchMedia('(prefers-reduced-motion: reduce)')