滚动隐藏顶栏

向下滚动阅读时顶栏滑走,一往上滚想找它,它就立刻滑回来。

  • JS
  • 布局
  • 滚动
  • 收录于
田野笔记
先往下滚,再往上滚

AI 提示词

用 React 制作向下滚动时隐藏、向上滚动时重新出现的顶栏。用 ref 保存上一次的滚动位置。每次 scroll 事件比较新的 scrollTop:如果向下移动超过 4px 且已超过顶栏高度,就给顶栏设置 data-hidden 属性;如果向上移动超过 4px,就移除它。顶栏使用 position: sticky 固定在顶部,隐藏时为 transform: translateY(-100%),transform 过渡 280ms,缓动 cubic-bezier(0.16, 1, 0.3, 1)。设置一个小阈值,避免触控板的细微抖动让它闪烁。焦点进入顶栏内部时始终显示。在 prefers-reduced-motion 下隐藏和显示不做滑动。品牌名为“田野笔记”。

源代码

Demo.tsx
'use client'

import { useRef } from 'react'
import s from './demo.module.css'

export default function HideHeader({ labels }: { labels: Record<string, string> }) {
  const header = useRef<HTMLElement>(null)
  const last = useRef(0)

  const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
    const y = e.currentTarget.scrollTop
    const dy = y - last.current
    if (Math.abs(dy) < 4) return
    header.current?.toggleAttribute('data-hidden', dy > 0 && y > 48)
    last.current = y
  }

  return (
    <div className={s.frame} tabIndex={0} onScroll={onScroll}>
      <header ref={header} className={s.header}>
        <span className={s.logo} aria-hidden="true" />
        <strong className={s.brand}>{labels.brand}</strong>
        <span className={s.menu} aria-hidden="true" />
      </header>
      <span className={s.hint}>{labels.hint}</span>
      <div className={s.feed}>
        {Array.from({ length: 6 }, (_, i) => (
          <div key={i} className={s.post}>
            <span className={s.thumb} />
            <span className={s.lines}>
              <span />
              <span />
            </span>
          </div>
        ))}
      </div>
    </div>
  )
}
demo.module.css
.frame {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.frame:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -4px; }

.hint {
  display: block;
  font: 500 var(--text-xs) / 1 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--stage-muted);
}

.header {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 16px;
  border-bottom: 1px solid var(--stage-rule);
  background: var(--stage-surface);
  transition: transform 280ms var(--ease-out);
}

.header[data-hidden]:not(:focus-within) { transform: translateY(-100%); }

.logo { width: 20px; height: 20px; border-radius: 6px; background: var(--stage-accent); }
.brand { flex: 1; font: 600 var(--text-sm) / 1 var(--font-display); color: var(--stage-ink); }

.menu {
  width: 18px;
  height: 10px;
  border-block: 2px solid var(--stage-muted);
}

.frame > .hint { margin: 16px 0 4px; }

.feed {
  display: grid;
  gap: 10px;
  max-width: 300px;
  margin: 0 auto;
  padding: 8px 16px 24px;
}

.post {
  display: flex;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--stage-rule);
  border-radius: 12px;
}

.thumb { flex: none; width: 44px; height: 44px; border-radius: 8px; background: var(--stage-surface); }

.lines { display: grid; flex: 1; align-content: center; gap: 8px; }
.lines span { height: 8px; border-radius: 4px; background: var(--stage-surface); }
.lines span:last-child { width: 60%; }

@media (prefers-reduced-motion: reduce) {
  .header { transition: none; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。