Vant 4 IndexBar 索引栏组件完全指南:列表索引分类与锚点吸顶实战
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
本文是一份面向 Vue 3 移动端开发者的 Vant IndexBar(索引栏)组件实战指南。IndexBar 用于列表的索引分类显示与快速定位,是通讯录、城市选择、商品分类等长列表场景的标配交互组件。读完本文,你将掌握 IndexBar 与 IndexAnchor 的完整 API、自定义索引列表、锚点吸顶原理、滚动定位方法,以及通过 CSS 变量进行主题定制的全部技巧。
本文所有源码引用均来自当前仓库
packages/vant/src/index-bar/与packages/vant/src/index-anchor/目录,读者可对照 IndexBar 源码、IndexAnchor 源码 与 官方英文文档 深入学习。
组件定位:什么是 IndexBar
IndexBar 组件用于列表的索引分类显示和快速定位:页面右侧会渲染一列索引字符(默认A-Z),点击或滑动索引字符时,页面自动滚动到对应的IndexAnchor锚点位置,同时锚点在滚动过程中支持吸顶(sticky)效果,让用户始终清楚当前所处分类。
组件由两部分协作完成:
van-index-bar:索引栏容器,负责渲染右侧索引列表、监听滚动、计算当前激活索引;van-index-anchor:索引锚点,配合index属性标记分组的起始位置,作为滚动定位的目标。
安装与注册
IndexBar 是 Vant 的常规组件,通过app.use全局注册即可。注意IndexAnchor 需要单独注册,二者缺一不可:
import { createApp } from 'vue'; import { IndexBar, IndexAnchor } from 'vant'; const app = createApp(); app.use(IndexBar); app.use(IndexAnchor);除全局注册外,也可使用局部注册或按需引入的方式,更多注册方式可参考 Vant 的 高级用法-组件注册 文档。从源码看,两个组件均通过withInstall包装后导出(见 index.ts),并在vue模块声明了全局组件类型VanIndexBar,因此在模板中直接使用<van-index-bar>即可获得完整的 TypeScript 类型提示。
基础用法
最基本的用法是在van-index-bar内部放置多个van-index-anchor,每个锚点后跟若干列表项(通常配合van-cell单元格):
<van-index-bar> <van-index-anchor index="A" /> <van-cell title="Text" /> <van-cell title="Text" /> <van-cell title="Text" /> <van-index-anchor index="B" /> <van-cell title="Text" /> <van-cell title="Text" /> <van-cell title="Text" /> ... </van-index-bar>点击右侧索引栏的字符时,页面会自动跳转到对应的锚点位置。锚点默认显示index属性传入的索引字符,也可以使用插槽自定义内容。
在仓库的 demo/index.vue 中可以看到完整的官方演示:它用循环生成了 26 个字母索引,并为每个索引渲染 3 个van-cell,且与van-tabs结合展示了"基础用法"与"自定义索引列表"两个标签页。
自定义索引列表
默认索引列表为A-Z共 26 个字母。通过index-list属性,可以传入任意string | number组成的数组,实现数字索引、中文索引等自定义场景:
<van-index-bar :index-list="indexList"> <van-index-anchor index="1">Title 1</van-index-anchor> <van-cell title="Text" /> <van-cell title="Text" /> <van-cell title="Text" /> <van-index-anchor index="2">Title 2</van-index-anchor> <van-cell title="Text" /> <van-cell title="Text" /> <van-cell title="Text" /> ... </van-index-bar>export default { setup() { return { indexList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], }; }, };从源码实现看,默认的A-Z索引正是由genAlphabet()函数动态生成的——它从字符'A'的码点出发,用String.fromCharCode连续生成 26 个大写字母,并作为indexListprop 的默认值(见 IndexBar.tsx):
function genAlphabet() { const charCodeOfA = 'A'.charCodeAt(0); const indexList = Array(26) .fill('') .map((_, i) => String.fromCharCode(charCodeOfA + i)); return indexList; }因此当你传入自定义indexList时,index-list会通过watch(() => props.indexList, init)触发重新初始化,保证滚动计算与索引渲染同步更新。Demo 中自定义列表还刻意跳过了数字 7,验证了非连续索引同样可用。
API 详解
IndexBar Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| index-list | 索引字符列表 | (string | number)[] | A-Z |
| z-index | z-index 层级 | number | string | 1 |
| sticky | 是否开启锚点自动吸顶 | boolean | true |
| sticky-offset-top | 锚点自动吸顶时与顶部的距离 | number | 0 |
| highlight-color | 索引字符高亮颜色 | string | #1989fa |
| teleport | 指定索引栏挂载的节点 | string | Element | - |
几个关键 prop 的源码细节:
- sticky 与 sticky-offset-top:
sticky使用truthProp声明,默认即为true;sticky-offset-top使用makeNumberProp(0)声明,默认0。当锚点吸顶时,sticky-offset-top控制锚点与视口顶部的距离,常用于吸顶到自定义导航栏下方的场景。 - z-index:源码中侧边栏的实际 z-index 是
zIndex + 1(见sidebarStyle计算属性),这样侧边栏层级始终高于吸顶锚点,避免被遮挡。 - teleport:类型为
TeleportProps['to'],支持字符串选择器或 DOM 元素。传入后侧边栏索引列表会通过 Vue 的<Teleport>挂载到指定节点(见 IndexBar.tsx),这在需要将索引栏渲染到弹层等特殊容器内时非常有用。
IndexAnchor Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| index | 索引字符 | number | string | - |
index是锚点的唯一标识,必须与index-list中的某个值对应,点击侧边栏时组件通过该值找到匹配锚点。
IndexBar Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| select | 点击索引栏的字符时触发 | index: number | string |
| change | 当前高亮的索引字符变化时触发 | index: number | string |
两者触发时机不同:select在用户点击/滑动侧边栏定位时触发;change则通过watch(activeAnchor, ...)监听激活索引变化触发(见 IndexBar.tsx),在页面滚动导致激活锚点切换时同样会触发。
IndexBar 方法
通过 ref 获取 IndexBar 实例后可调用实例方法,具体可参考 Vue 的 template refs 文档。
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| scrollTo | 滚动到指定锚点 | index: number | string | - |
<van-index-bar ref="indexBarRef" />import { ref } from 'vue'; const indexBarRef = ref(); indexBarRef.value?.scrollTo('B');scrollTo内部逻辑:先记录selectActiveIndex用于滚动后的激活状态计算,再通过match.$el.scrollIntoView()滚动到目标锚点,随后根据sticky与sticky-offset-top配置微调页面滚动位置,并触发select事件(见 IndexBar.tsx)。
类型定义
组件导出了以下 TypeScript 类型:
import type { IndexBarProps, IndexAnchorProps, IndexBarInstance } from 'vant';IndexBarInstance是组件实例的类型,可与 ref 结合获得完整的类型安全:
import { ref } from 'vue'; import type { IndexBarInstance } from 'vant'; const indexBarRef = ref<IndexBarInstance>(); indexBarRef.value?.scrollTo('B');这些类型定义在 types.ts 中:IndexBarExpose声明了scrollTo方法签名,IndexBarInstance则通过ComponentPublicInstance<IndexBarProps, IndexBarExpose>组合而成;组件内部通过useExpose({ scrollTo })将方法暴露给外部实例。
IndexAnchor 插槽
| 名称 | 说明 |
|---|---|
| default | 锚点位置显示内容,默认为索引字符 |
从 IndexAnchor.tsx 的渲染逻辑可以看到:slots.default ? slots.default() : props.index,即不传插槽时默认渲染索引字符本身。
主题定制:CSS 变量
组件提供了以下 CSS 变量用于自定义样式,可通过 ConfigProvider 组件 全局配置或直接覆盖实现定制。
索引栏变量
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-index-bar-sidebar-z-index | 2 | 侧边栏 z-index |
| --van-index-bar-index-font-size | var(--van-font-size-xs) | 索引字符字号 |
| --van-index-bar-index-line-height | var(--van-line-height-xs) | 索引字符行高 |
| --van-index-bar-index-active-color | var(--van-primary-color) | 激活索引字符颜色 |
锚点变量
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-index-anchor-z-index | 1 | 锚点 z-index |
| --van-index-anchor-padding | 0 var(--van-padding-md) | 锚点内边距 |
| --van-index-anchor-text-color | var(--van-text-color) | 锚点文字颜色 |
| --van-index-anchor-font-weight | var(--van-font-bold) | 锚点字重 |
| --van-index-anchor-font-size | var(--van-font-size-md) | 锚点字号 |
| --van-index-anchor-line-height | 32px | 锚点行高 |
| --van-index-anchor-background | transparent | 锚点背景色 |
| --van-index-anchor-sticky-text-color | var(--van-primary-color) | 吸顶时文字颜色 |
| --van-index-anchor-sticky-background | var(--van-background-2) | 吸顶时背景色 |
这些变量的默认值分别定义在 index-bar/index.less 与 index-anchor/index.less 中。例如吸顶锚点的样式类.van-index-anchor--sticky设置为position: fixed且左右对齐视口,配合背景色--van-index-anchor-sticky-background实现"吸在顶部"的视觉效果。
源码级原理:IndexBar 是如何工作的
父子组件通信:useChildren / useParent
IndexAnchor 之所以能感知 IndexBar 的配置,依赖 Vant 基于依赖注入封装的useChildren/useParent组合式函数。IndexBar 通过INDEX_BAR_KEY(Symbol类型)建立注入上下文并调用linkChildren({ props }),而每个 IndexAnchor 在setup中调用useParent(INDEX_BAR_KEY)获取父级实例;若锚点脱离 IndexBar 单独使用,开发环境下会输出警告[Vant] <IndexAnchor> must be a child component of <IndexBar>.(见 IndexAnchor.tsx)。
滚动时如何确定激活锚点
IndexBar 通过useScrollParent找到最近的滚动容器,并监听其scroll事件。每次滚动时,onScroll会遍历所有子锚点,调用每个锚点暴露的getRect方法获取其距离滚动容器顶部的位移,随后由getActiveAnchor从后向前找到第一个满足scrollTop + reachTop >= rects[i].top的锚点作为激活项(见 IndexBar.tsx)。其中reachTop在开启吸顶时等于上一锚点高度与sticky-offset-top之和,这保证了吸顶状态下激活索引的判断依然准确。
吸顶锚点的实现方式
当某个锚点处于激活态且sticky开启时,IndexBar 会把该锚点及其上一个锚点的位置、宽度等信息写入子组件的state,IndexAnchor 据此渲染出position: fixed的吸顶样式(左、宽、translate3d位移均通过行内样式控制,见 IndexAnchor.tsx),从而实现锚点吸附在顶部的效果;同时外层包裹元素保留原始高度,避免吸顶导致列表布局跳动。
侧边栏的点击与滑动
- 点击:侧边栏绑定
click事件,通过event.target上的data-index找到索引并调用scrollTo; - 滑动:侧边栏绑定
touchstart/touchmove,滑动时通过document.elementFromPoint获取手指当前位置的元素,若命中了新的索引字符则触发scrollTo;同时用touch.isVertical()判断滑动方向,仅纵向滑动时才响应并preventDefault,避免与页面横向手势冲突(见 IndexBar.tsx)。
测试用例佐证
仓库在 index-bar/test/index.spec.jsx 中覆盖了核心交互路径,可作为理解行为契约的参考:
- 点击索引字符会调用
scrollIntoView并触发select事件(携带对应索引值); - 纵向拖动侧边栏可依次命中索引并触发
select,横向拖动则不会响应; - 页面滚动时
change事件携带激活索引变化; - 调用
scrollTo('C')实例方法会滚动到目标锚点并触发select; - 传入
teleport后,侧边栏会正确渲染到指定 DOM 节点; sticky-offset-top与吸顶状态组合时的渲染结果有快照断言(见 demo.spec.ts 与__snapshots__目录)。
典型场景建议
- 通讯录 / 城市列表:使用默认
A-Z索引,配合van-cell展示联系人,开启sticky让分类标题吸顶,是最典型的用法; - 数字分组的商品分类:传入
[1, 2, ..., 10]数字索引,配合自定义锚点插槽显示"标题 N"; - 吸顶到自定义导航下方:当页面顶部存在自定义导航栏时,设置
sticky-offset-top为导航栏高度,使吸顶锚点停留在导航栏下方; - 弹层内的索引选择:当 IndexBar 需要渲染在 Popup 弹层中时,通过
teleport指定挂载节点,保证索引栏出现在正确的容器内。
以上用法覆盖了 IndexBar 的绝大多数生产场景。若需要统一管理全局主题变量(如把激活色改为品牌色),推荐结合 ConfigProvider 组件 一次性注入上述 CSS 变量。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考