导航栏滚动收缩功能迭代代码 2026年1月6日

导航栏滚动收缩第一版

CSS 代码:

:root {
  /* 导航栏滚动缩放 */
  --header-height-expanded: 135px;
  --header-height-shrunk: 80px;
  --transition-duration: 0.2s;
  --transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
}
/* ==========================================
   导航栏滚动收缩样式
   ========================================== */
.header-container {
  position: sticky;
  top: 12px;
  height: var(--header-height-expanded);
  min-height: unset;
  transition: height var(--transition-duration) var(--transition-easing);
  z-index: 1000;
}

.header-container.shrunk {
  height: var(--header-height-shrunk);
}

/* 收缩态遮罩 */
.header-container.shrunk::before {
  content: '';
  position: absolute;
  inset: -15px 0;
  pointer-events: auto;
  z-index: -1;
}

header {
  padding: 1em 1.6em;
}

/* ===== 行结构 ===== */
.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;

  transform: translateY(0) translateZ(0);
  transition: transform var(--transition-duration) var(--transition-easing);
  will-change: transform;
  pointer-events: auto;
}

.header-container.shrunk .header-bottom {
  transform: translateY(-55px) translateZ(0);
  pointer-events: none;
}

/* ===== 占位元素 ===== */
.placeholder-top,
.placeholder-bottom {
  opacity: 1;
  transform: translateY(0) translateZ(0);
  transition:
    opacity calc(var(--transition-duration) * 0.75) var(--transition-easing),
    transform var(--transition-duration) var(--transition-easing);
  will-change: transform, opacity;
}

.header-container.shrunk .placeholder-top {
  opacity: 0;
  transform: translateY(-12px) translateZ(0);
  pointer-events: none;
}

.header-container.shrunk .placeholder-bottom {
  opacity: 0;
  transform: translateY(-20px) translateZ(0);
}

/* 导航按钮不动画 */
.header-bottom nav {
  transition: none;
}

/* ===== 交互修正 ===== */
.header-container.shrunk {
  pointer-events: none;
}

.header-container.shrunk nav,
.header-container.shrunk .user-menu-container,
.header-container.shrunk .user-menu-trigger {
  pointer-events: auto;
}

JS代码:

// =============================
// 14. 导航栏滚动收缩功能
// =============================
function initHeaderShrink() {
    
    const runInit = () => {
        const header = document.querySelector('.header-container');
        if (!header) return;
        // 差值需大于 导航栏展开高度135-收缩后高度80=55
        const SHRINK_AT = 65; //80
        const EXPAND_AT = 5; //20

        let isShrunk = false;
        let lastScrollY = window.scrollY;
        let ticking = false;

        const updateHeaderState = () => {
            const scrollY = window.scrollY;
            const isScrollingDown = scrollY > lastScrollY;
            lastScrollY = scrollY;

            if (isScrollingDown && !isShrunk && scrollY > SHRINK_AT) {
                header.classList.add('shrunk');
                isShrunk = true;
            } else if (!isScrollingDown && isShrunk && scrollY < EXPAND_AT) {
                header.classList.remove('shrunk');
                isShrunk = false;
            }
            ticking = false;
        };

        const onScroll = () => {
            if (!ticking) {
                requestAnimationFrame(updateHeaderState);
                ticking = true;
            }
        };

        window.addEventListener('scroll', onScroll, { passive: true });
        
        console.log('✅ 导航栏收缩功能已就绪');
    };

    if ('requestIdleCallback' in window) {
        requestIdleCallback(runInit, { timeout: 100 });
    } else {
        setTimeout(runInit, 100);
    }
}

导航栏滚动收缩第二版 - 优化掉帧

CSS 代码:

:root {
  /* 导航栏滚动缩放 */
  --header-height-expanded: 135px;
  --header-height-shrunk: 80px;
  --transition-duration: 0.2s;
  --transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
}
/* ==========================================
   导航栏滚动收缩样式
   ========================================== */
.header-container {
  transform: translateZ(0);
  will-change: height;

  position: sticky;
  top: 12px;
  height: var(--header-height-expanded);
  min-height: unset;
  transition: height var(--transition-duration) var(--transition-easing);
  z-index: 1000;
}

.header-container.shrunk {
  height: var(--header-height-shrunk);
}

/* 标签页恢复同步禁止动画一帧 */
.header-container.is-resyncing {
  transition: none;
}

/* 收缩态遮罩 */
.header-container.shrunk::before {
  content: '';
  position: absolute;
  inset: -15px 0;
  pointer-events: auto;
  z-index: -1;
}

header {
  padding: 1em 1.6em;
}

/* ===== 行结构 ===== */
.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;

  transform: translateY(0) translateZ(0);
  transition: transform var(--transition-duration) var(--transition-easing);
  will-change: transform;
  pointer-events: auto;
}

.header-container.shrunk .header-bottom {
  transform: translateY(-55px) translateZ(0);
  pointer-events: none;
}

/* ===== 占位元素 ===== */
.placeholder-top,
.placeholder-bottom {
  opacity: 1;
  transform: translateY(0) translateZ(0);
  transition:
    opacity calc(var(--transition-duration) * 0.75) var(--transition-easing),
    transform var(--transition-duration) var(--transition-easing);
  will-change: transform, opacity;
}

.header-container.shrunk .placeholder-top {
  opacity: 0;
  transform: translateY(-12px) translateZ(0);
  pointer-events: none;
}

.header-container.shrunk .placeholder-bottom {
  opacity: 0;
  transform: translateY(-20px) translateZ(0);
}

/* 导航按钮不动画 */
.header-bottom nav {
  transition: none;
}

/* ===== 交互修正 ===== */
.header-container.shrunk {
  pointer-events: none;
}

.header-container.shrunk nav,
.header-container.shrunk .user-menu-container,
.header-container.shrunk .user-menu-trigger {
  pointer-events: auto;
}

JS代码:

// =============================
// 14. 导航栏滚动收缩功能
// =============================
function initHeaderShrink() {

    const runInit = () => {
        const header = document.querySelector('.header-container');
        if (!header) return;
        // 差值需大于 导航栏展开高度135-收缩后高度80=55
        const SHRINK_AT = 65;
        const EXPAND_AT = 5;

        let isShrunk = false;
        let lastScrollY = window.scrollY;
        let ticking = false;

        // 与 scrollY 强制同步导航栏状态不动画
        const syncHeaderState = () => {
            const scrollY = window.scrollY;

            header.classList.add('is-resyncing');

            if (scrollY > SHRINK_AT) {
                header.classList.add('shrunk');
                isShrunk = true;
            } else {
                header.classList.remove('shrunk');
                isShrunk = false;
            }

            lastScrollY = scrollY;
            ticking = false;

            // 下一帧恢复动画能力
            requestAnimationFrame(() => {
                header.classList.remove('is-resyncing');
            });
        };

        const updateHeaderState = () => {
            const scrollY = window.scrollY;
            const isScrollingDown = scrollY > lastScrollY;
            lastScrollY = scrollY;

            if (isScrollingDown && !isShrunk && scrollY > SHRINK_AT) {
                header.classList.add('shrunk');
                isShrunk = true;
            } else if (!isScrollingDown && isShrunk && scrollY < EXPAND_AT) {
                header.classList.remove('shrunk');
                isShrunk = false;
            }

            ticking = false;
        };

        const onScroll = () => {
            if (!ticking) {
                requestAnimationFrame(updateHeaderState);
                ticking = true;
            }
        };

        // 页面恢复可见时重新对齐状态
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible') {
                syncHeaderState();
            }
        });

        // 初始同步一次
        syncHeaderState();

        window.addEventListener('scroll', onScroll, { passive: true });

        console.log('✅ 导航栏收缩功能已就绪');
    };

    if ('requestIdleCallback' in window) {
        requestIdleCallback(runInit, { timeout: 100 });
    } else {
        setTimeout(runInit, 100);
    }
}