概述
本篇笔者来讲解一下 index-bar
和 index-anchor
的实现原理和细节处理,以及结合实际场景会对其进行拓展,来实现Wechat
通讯录相似的功能,保证让index-bar
变得更加生动有趣,满足更多的业务场景。当然笔者会结合自身的理解,已经为每个核心的方法增加了必要的注释,会尽最大努力将其中的原理讲清楚,若有不妥之处,还望不吝赐教,欢迎批评指正。
预览
层级结构
index-bar
:主要由 内容
和 van-index-bar__sidebar
组成,van-index-bar__sidebar
主要就是用来 点击或者触摸滑动
来滚动到指定的锚点(index-anchor
).
index-anchor
:主要由一个 div
包裹着一个 van-index-anchor
,其中 van-index-anchor
如果 吸顶
了会变成 fixed
定位,以及包裹他的父元素( div
)会设置高度,用于弥补其脱离文档流后的高度。
实现原理
笔者觉得 index-bar
中最核心的地方,在于滚动过程中,锚点的吸顶的处理。其中主要包括:获取哪个活跃的锚点将要吸顶,以及上一个活跃的锚点如何退场等。所以我们把核心点关注在:index-bar
所处的滚动容器 scroller
的滚动事件上。
mixins: [
TouchMixin,
ParentMixin('vanIndexBar'),
BindEventMixin(function (bind) {
// bind: on/off 函数
if (!this.scroller) {
this.scroller = getScroller(this.$el);
}
bind(this.scroller, 'scroll', this.onScroll);
}),
],
onScroll() {
if (isHidden(this.$el)) {
return;
}
// 获取滚动容器的scrollTop
const scrollTop = getScrollTop(this.scroller);
// 返回滚动容器元素的大小及其相对于视口的位置 因为滚动容器可能不是 window/body,而且也有可能距离视口顶部有一段距离
const scrollerRect = this.getScrollerRect();
// 计算每一个锚点在滚动容器中的具体位置 top/height
const rects = this.children.map((item) =>
item.getRect(this.scroller, scrollerRect)
);
// 获取当前活跃的锚点
const active = this.getActiveAnchorIndex(scrollTop, rects);
this.activeAnchorIndex = this.indexList[active];
if (this.sticky) {
this.children.forEach((item, index) => {
// 由于要设置 active 和 active-1 锚点的 fixed 属性,所以要把其,父容器的宽高 继承过来
if (index === active || index === active - 1) {
const rect = item.$el.getBoundingClientRect();
item.left = rect.left;
item.width = rect.width;
} else {
item.left = null;
item.width = null;
}
// 核心代码
if (index === active) {
// 这里锚点已经是 fixed 定位
item.active = true;
// 计算top: 由于锚点 fixed 定位的 top为0,这里设置的top 是用于设置自身锚点的transform.y
// rects[index].top 是相对于滚动容器的位置,是固定值
// scrollTop: 是变量,向上滚动 增大, 向下滚动 减小
item.top =
Math.max(this.stickyOffsetTop