npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@whynotsnow/scroll-head

v0.1.0

Published

Headless scroll-aware header controller for content sites.

Downloads

129

Readme

@whynotsnow/scroll-head

面向博客、文档站和内容型页面的无头滚动头部控制器。

scroll-head 不渲染 header,也不绑定任何框架。它只监听滚动状态,并把一组稳定的 DOM 契约写到你的 header 上:data-* 属性、CSS 变量,以及可选 class。视觉样式、布局和动画都由你的 CSS 控制。

安装

npm install @whynotsnow/scroll-head

这个包是 ESM-only,适合现代打包器、Vite、Rollup、Webpack、Next.js / Nuxt / Astro 等客户端入口使用。因为核心默认会访问 window,如果在 SSR 环境中使用,请只在浏览器端初始化,或显式传入浏览器端可用的 root

快速开始

import { createScrollHead } from "@whynotsnow/scroll-head";

const header = document.querySelector<HTMLElement>(".site-header");

if (header) {
  const controller = createScrollHead(header, {
    mode: "hide-compact",
    at: 96,
    on: {
      scrollDown: {
        after: 320,
        distance: 24
      },
      scrollUp: {
        distance: 8
      }
    },
    heights: {
      full: 104,
      compact: 70
    }
  });

  // 在页面卸载或组件销毁时调用。
  // controller.destroy();
}

默认情况下,控制器会写入类似这样的状态:

<header
  data-scroll-head-state="visible"
  data-scroll-head-size="compact"
  data-scroll-head-edge="away"
  data-scroll-direction="down"
  style="--scroll-head-height: 70px; --scroll-head-progress: 1;"
>
</header>

然后你可以直接用属性和 CSS 变量写样式:

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--scroll-head-height, 104px);
  transform: translate3d(0, 0, 0);
  transition:
    height 220ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
    background-color 180ms ease,
    box-shadow 180ms ease;
}

.site-header[data-scroll-head-state="hidden"] {
  transform: translate3d(0, -100%, 0);
}

.site-header[data-scroll-head-edge="away"] {
  background: rgb(255 255 255 / 0.9);
  box-shadow: 0 1px 0 rgb(0 0 0 / 0.08);
}

预设样式

如果想更快启动,可以导入内置 CSS:

import { createScrollHead } from "@whynotsnow/scroll-head";
import "@whynotsnow/scroll-head/styles/presets/blog.css";

createScrollHead(document.querySelector(".site-header")!, {
  output: {
    classes: true
  }
});
<header class="site-header scroll-head scroll-head-blog">
  ...
</header>

也可以只导入基础过渡层:

import "@whynotsnow/scroll-head/styles/base.css";

内置 CSS 很克制,只处理高度、隐藏、顶部/离顶状态、背景过渡和 reduced-motion。更具体的导航、品牌、菜单、响应式布局仍然交给你的项目样式。

滚动模式

mode 描述内置行为,at 是主要滚动触发点,on 可以覆盖具体阈值。

createScrollHead(header, {
  mode: "hide-compact",
  at: 128,
  hysteresis: 48,
  on: {
    scrollDown: {
      after: 360
    }
  },
  heights: {
    full: 112,
    compact: 72
  }
});

上面的配置表示:

  • 0px128px 使用完整 header
  • 128px360px 使用紧凑 header
  • 超过 360px 后,继续向下滚动会自动隐藏
  • 回滚到 80px 前恢复完整 header

可用模式:

  • auto / hide-compact:隐藏、紧凑、离顶高亮
  • hide:隐藏、离顶高亮
  • compact:紧凑、离顶高亮
  • elevate:只输出顶部/离顶状态
  • none:关闭内置行为状态变化

如果只想保留紧凑和离顶效果,不想自动隐藏,可以用 mode: "compact"。如果想使用 hide-compact 但禁用隐藏阈值,可以设置 on.scrollDown.after: false

输出契约

默认输出属性:

  • data-scroll-head-state: visible / hidden
  • data-scroll-head-size: full / compact
  • data-scroll-head-edge: top / away
  • data-scroll-direction: idle / down / up

默认输出 CSS 变量:

  • --scroll-head-y
  • --scroll-head-progress
  • --scroll-head-height
  • --scroll-head-visible

如果更偏好 class,可以打开 class 同步:

createScrollHead(header, {
  output: {
    classes: true
  }
});

默认 class:

<header
  class="
    scroll-head
    scroll-head--visible
    scroll-head--compact
    scroll-head--away
    scroll-head--direction-down
  "
>
</header>

也可以自定义 class 名:

createScrollHead(header, {
  output: {
    classes: {
      root: "site-head",
      hidden: "is-hidden",
      compact: "is-compact",
      away: "is-elevated"
    }
  }
});

边界规则

滚动距离会被规范化:

  • 负数会变成 0
  • NaNInfinity-Infinity 会回退到默认值
  • 很大的有限数字会被保留,所以 on.scrollDown.after: 100000 可以用来推迟隐藏
  • on.scrollDown.after: false 是明确禁用隐藏的方式
  • on.returnBefore.y 会被限制在不大于紧凑触发点

Change Events

onChange 会收到当前状态、上一次状态、变更字段和被控制的元素。

createScrollHead(header, {
  onChange({ state, previousState, changed, element }) {
    if (changed.visibility) {
      element.classList.toggle("just-hid", state.visibility === "hidden");
    }

    console.log(previousState.visibility, "->", state.visibility);
  }
});

changed 的结构:

{
  visibility: boolean;
  size: boolean;
  edge: boolean;
  direction: boolean;
  y: boolean;
  progress: boolean;
  height: boolean;
}

API

const controller = createScrollHead(element, options);

controller.getState();
controller.update();
controller.disable();
controller.enable();
controller.destroy();

destroy() 会移除滚动和 resize 监听,并恢复初始化前由 scroll-head 管理的属性、CSS 变量和 class 状态。

Options

interface ScrollHeadOptions {
  mode?: "auto" | "hide" | "compact" | "elevate" | "hide-compact" | "none";
  at?: number;
  top?: number;
  hysteresis?: number;
  on?: {
    scrollDown?: {
      after?: number | false;
      distance?: number;
    };
    scrollUp?: {
      distance?: number;
    };
    pass?: {
      y?: number;
    };
    returnBefore?: {
      y?: number;
    };
    progress?: {
      from?: number;
      to?: number;
    };
  };
  output?: {
    attributePrefix?: string;
    cssVarPrefix?: string;
    attributes?: boolean;
    cssVars?: boolean;
    classes?: boolean | ScrollHeadClasses;
  };
  heights?: {
    full?: number | string;
    compact?: number | string;
  };
  root?: Window | HTMLElement;
  disabled?: boolean;
  onChange?: (event: ScrollHeadChangeEvent) => void;
}

调试与 source map

npm 包会同时发布 distsrcdist/*.js.map 指向 ../src/*.ts,所以在支持 source map 的调试器里可以回到 TypeScript 源文件。

本地开发

npm install
npm run dev

示例项目位于 examples/vanilla

常用检查:

npm run typecheck
npm test
npm pack --dry-run

English

@whynotsnow/scroll-head is a headless scroll-aware header controller for blogs, docs, and content-heavy sites.

It does not render a header. Instead, it observes scroll state and writes a small DOM contract to your header element: data-* attributes, CSS variables, and optional classes. Your CSS owns the layout, visuals, and transitions.

Install

npm install @whynotsnow/scroll-head

The package is ESM-only. In SSR environments, initialize it only on the client, or pass a browser-side root.

Basic Usage

import { createScrollHead } from "@whynotsnow/scroll-head";

const header = document.querySelector<HTMLElement>(".site-header");

if (header) {
  createScrollHead(header, {
    mode: "hide-compact",
    at: 96,
    heights: {
      full: 104,
      compact: 70
    }
  });
}

Preset CSS

import "@whynotsnow/scroll-head/styles/presets/blog.css";

Or import only the base transition layer:

import "@whynotsnow/scroll-head/styles/base.css";

API

const controller = createScrollHead(element, options);

controller.getState();
controller.update();
controller.disable();
controller.enable();
controller.destroy();

destroy() removes listeners and restores the attributes, CSS variables, and classes managed by scroll-head.