跳转到内容
关系图谱

scroll-navigator:回顶部与到底部浮钮

1,728 字 6 分钟

属性 1
series
VaultKit 包指南

@vaultkit/vitepress-plugin-scroll-navigator 是右下角「回顶部 / 到底部」浮钮组 Vue 组件。它解决的是长文导航的两个高频动作:读完想回顶部看目录、想直接跳到文末的评论区。往下滚动本页,右下角出现的那对圆形浮钮就是它的现场演示——本站开箱内置。

零依赖实现:passive 滚动监听 + rAF 节流,显隐语义经过仔细取舍(短文档两钮都不显示、到底后到底钮自动消失),移动端只保留回顶钮并加大到 44px 触摸目标。本文逐项拆解全部 props、CSS 变量与这些设计取舍。

为什么需要它

「回到顶部」几乎是所有博客的标配,但细节做好并不容易:

  • 常驻按钮遮内容——刚打开页面就杵在角上的按钮是视觉噪音,应当滚过一段距离才现身
  • 滚动监听拖性能——不节流的 scroll 回调在低端机上肉眼可见掉帧
  • SSR 水合闪烁——服务端不知道滚动位置,渲染出错误的初始态
  • 移动端触摸目标过小——40px 以下的圆钮在手机上容易误触旁边内容

本包把这四件事都处理掉了,且不引任何滚动库,peer 依赖只有 vue ^3.5.0

安装

sh
pnpm add -D @vaultkit/vitepress-plugin-scroll-navigator

使用 @vaultkit/vitepress-theme-vault 主题则开箱即带(features.scrollNavigator 开关控制)。

快速上手

推荐挂载在 VitePress 布局的 #layout-bottom 插槽:

.vitepress/theme/Layout.vue
vue
<script setup lang="ts">
import { ScrollNavigator } from '@vaultkit/vitepress-plugin-scroll-navigator'
import DefaultTheme from 'vitepress/theme'
</script>

<template>
  <DefaultTheme.Layout>
    <template #layout-bottom>
      <ScrollNavigator :labels="{ top: '回到顶部', bottom: '滚动到底部' }" />
    </template>
  </DefaultTheme.Layout>
</template>
Important

本包发布的是含 .vue 源文件的产物,消费方 VitePress 项目必须把它加进 vite.ssr.noExternal,否则 SSR 构建会报错:

ts
// .vitepress/config.ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  vite: { ssr: { noExternal: ['@vaultkit/vitepress-plugin-scroll-navigator'] } },
})

公开导出一览

导出形态说明
ScrollNavigatorVue 组件(具名导出)浮钮组本体
默认导出同上import ScrollNavigator from '...' 亦可

Props 逐项详解

只有两个 props,全部可选。

threshold

  • 类型:number,默认 200
  • 语义:回顶钮出现的滚动距离阈值(px)。滚过这个距离才现身——刚打开页面时角落是干净的。默认 200px 约等于「滚过首屏标题区」;想更保守(滚过一整屏才出现)可以调大:
vue
<ScrollNavigator :threshold="600" />

注意该阈值只管回顶钮。到底钮的显隐是另一套独立语义:已滚动(scrollY > 0未到达底部才显示,所以:

  • 页面刚打开:两钮都不显示
  • 滚了一点但没过阈值:只有到底钮
  • 滚过阈值:两钮都在
  • 滚到底:到底钮消失,只剩回顶钮
  • 短文档(不可滚):两钮永不出现

这套组合逻辑保证按钮只在「真的有用」的时刻出现。

labels

  • 类型:{ top?: string, bottom?: string },默认 {}
  • 语义:两个按钮的 titlearia-label 文案注入,逐字段说明:
字段默认值对应控件
top'Back to top'回顶钮(向上箭头)
bottom'Scroll to bottom'到底钮(向下箭头)

中文站点的注入示例:

vue
<ScrollNavigator
  :labels="{
    top: '回到顶部',
    bottom: '滚动到底部',
  }"
/>

文案同时写入 title(鼠标悬停提示)与 aria-label(屏幕阅读器播报),一处注入两处生效。按钮内部只有装饰性 SVG 箭头(aria-hidden="true"),可访问名完全由 aria-label 提供——不注入中文的话,读屏用户听到的是英文。

CSS 变量

变量默认值说明
--scroll-nav-right24px按钮组距视口右缘距离
--scroll-nav-bottom24px按钮组距视口底缘距离

按钮组是 position: fixed 的纵向 flex 列(间距 8px),位置经这两个变量覆盖。典型场景:站点右下角已有客服浮窗,把按钮组往上挪让位:

css
/* .vitepress/theme/custom.css */
:root {
  --scroll-nav-bottom: 96px;
}

按钮本身的配色全部走 VitePress 语义变量(--vp-c-bg-soft 底、--vp-c-divider 边、悬停变 --vp-c-brand-1),明暗两态免配置;还带 backdrop-filter: blur(8px) 毛玻璃,压在代码块上也不突兀。

设计机制

rAF 节流与 SSR 安全

和姊妹组件 reading-progress:顶部阅读进度条 同款方案:scroll / resize 回调只负责 requestAnimationFrame(update) 排程,每帧至多读一次 scrollY / scrollHeight,滚动零卡顿。

SSR 处理则更进一步:组件在 mounted整体不渲染v-if="mounted")。因为服务端不知道滚动位置,任何初始态都可能错;干脆首帧空渲染,等客户端挂载后再按真实位置显示。这等效于 VitePress 的 <ClientOnly>,但自实现后不依赖 VitePress 全局组件——组件因此框架无关,任何 Vue 3 SSR 站点可用。

到底钮的消失时机

判断「未到底」用了 y + innerHeight < scrollHeight - 1,留 1px 容差——高 DPI 屏幕上滚动位置常有亚像素误差,不留容差按钮会在底部反复闪。

平滑滚动交给浏览器

点击行为就是一行 window.scrollTo({ top, behavior: 'smooth' })。不自己写缓动动画有个关键收益:浏览器在 prefers-reduced-motion: reduce 下会自动把平滑滚动退化为瞬时跳转,无障碍偏好免费获得。按钮出现 / 消失的 Vue Transition(淡入 + 10px 上移)也在同一媒体查询下关闭。

移动端 44px 触摸目标的取舍

≤768px 时组件做两件事:

css
@media (max-width: 768px) {
  .scroll-nav-btn {
    width: 44px;
    height: 44px;
  }

  .scroll-nav-btn.is-bottom {
    display: none;
  }
}

为什么加大到 44px:Apple HIG 建议触摸目标最小 44×44pt,桌面端的 40px 圆钮在手机上处于误触边缘。

为什么砍掉到底钮:这是一个刻意的取舍——回顶是移动端最痛的操作(iOS 有点状态栏回顶手势,Android 没有原生等价物),而「到底部」价值低得多;两钮一起显示会翻倍遮挡面积,在小屏上遮的还是正文。保回顶、砍到底,是主流博客的共同折衷。

高级与边界

  • z-index 为 40:低于 VitePress 的模态层,高于正文。有冲突时用 :deep(.scroll-navigator) { z-index: ... } 或全局样式覆盖
  • 嵌套滚动容器:显隐与滚动都基于 window,正文在自定义滚动容器内的布局不适用
  • 与进度条搭配#layout-topreading-progress:顶部阅读进度条#layout-bottom 放本组件,是「滚动体验二件套」的推荐布线(本站即如此)
  • appear 过渡:Transition 带 appear,从页面中部刷新恢复滚动位置时,按钮也是淡入而非瞬现

常见问题

Q:按钮一直不出现? 页面可能太短(不可滚),或者滚动发生在自定义容器而非 window 上。

Q:想只要回顶钮、不要到底钮? 组件没有单独开关,但一行全局 CSS 即可:.scroll-nav-btn.is-bottom { display: none }(组件在移动端就是这么做的)。

Q:点击回顶后焦点在哪里? 仍在按钮上。键盘用户随后 Tab 会从按钮继续,如需「回顶 + 焦点复位」的完整无障碍链路,可在使用侧监听点击后手动 focus 页面主标题。

Q:SSR 构建报 .vue 无法解析? 没配 vite.ssr.noExternal,见上文安装一节。

相关链接