Skip to content

Commit 82134fe

Browse files
authored
[new feature] IndexBar: add sticky prop (youzan#3402)
1 parent 7412424 commit 82134fe

14 files changed

Lines changed: 263 additions & 60 deletions

File tree

docs/markdown/changelog.zh-CN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
### [v2.0.0-beta.3](https://github.com/youzan/vant/tree/v2.0.0-beta.3)
44

5+
##### IndexBar
6+
7+
- 新增`sticky`参数
8+
59
##### Rate
610

711
- 新增`gutter`属性

packages/index-anchor/index.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,49 @@ export default sfc({
1010
index: [String, Number]
1111
},
1212

13+
data() {
14+
return {
15+
top: 0,
16+
active: false
17+
};
18+
},
19+
20+
computed: {
21+
sticky() {
22+
return this.active && this.parent.sticky;
23+
},
24+
25+
anchorStyle() {
26+
if (this.sticky) {
27+
return {
28+
top: `${this.top}px`,
29+
zIndex: `${this.parent.zIndex}`
30+
};
31+
}
32+
}
33+
},
34+
35+
mounted() {
36+
this.height = this.$el.offsetHeight;
37+
},
38+
1339
methods: {
1440
scrollIntoView() {
1541
this.$el.scrollIntoView();
1642
}
1743
},
1844

1945
render(h) {
46+
const { sticky } = this;
47+
2048
return (
21-
<div class={bem()}>
22-
{this.slots('default') ? this.slots('default') : this.index}
49+
<div style={{ height: sticky ? `${this.height}px` : null }}>
50+
<div
51+
style={this.anchorStyle}
52+
class={[bem({ sticky }), { 'van-hairline--bottom': sticky }]}
53+
>
54+
{this.slots('default') || this.index}
55+
</div>
2356
</div>
2457
);
2558
}

packages/index-anchor/index.less

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,15 @@
55
font-weight: @index-anchor-font-weight;
66
font-size: @index-anchor-font-size;
77
line-height: @index-anchor-line-height;
8+
background-color: transparent;
9+
transition: background-color .2s;
10+
11+
&--sticky {
12+
position: fixed;
13+
top: 0;
14+
right: 0;
15+
left: 0;
16+
z-index: 1;
17+
background-color: #fff;
18+
}
819
}

packages/index-bar/en-US.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export default {
6464
|------|------|------|------|
6565
| index-list | Index List | `Array` | `A-Z` |
6666
| z-index | z-index | `Number` | `1` |
67+
| sticky | Whether to enable anchor sticky top | `Boolean` | `true` |
6768

6869
### IndexAnchor Props
6970

packages/index-bar/index.js

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { use } from '../utils';
22
import { TouchMixin } from '../mixins/touch';
33
import { ParentMixin } from '../mixins/relation';
4+
import { on, off } from '../utils/event';
5+
import { getScrollTop, getElementTop, getScrollEventTarget } from '../utils/scroll';
46

57
const [sfc, bem] = use('index-bar');
68

79
export default sfc({
810
mixins: [TouchMixin, ParentMixin('vanIndexBar')],
911

1012
props: {
13+
sticky: {
14+
type: Boolean,
15+
default: true
16+
},
1117
zIndex: {
1218
type: Number,
1319
default: 1
@@ -27,13 +33,72 @@ export default sfc({
2733
}
2834
},
2935

36+
mounted() {
37+
this.scroller = getScrollEventTarget(this.$el);
38+
this.handler(true);
39+
},
40+
41+
destroyed() {
42+
this.handler(false);
43+
},
44+
45+
activated() {
46+
this.handler(true);
47+
},
48+
49+
deactivated() {
50+
this.handler(false);
51+
},
52+
3053
methods: {
31-
onClick(event) {
32-
this.scrollToElement(event.target);
54+
handler(bind) {
55+
/* istanbul ignore else */
56+
if (this.binded !== bind) {
57+
this.binded = bind;
58+
(bind ? on : off)(this.scroller, 'scroll', this.onScroll);
59+
}
3360
},
3461

35-
onTouchStart(event) {
36-
this.touchStart(event);
62+
onScroll() {
63+
if (!this.sticky) {
64+
return;
65+
}
66+
67+
const scrollTop = getScrollTop(this.scroller);
68+
const rects = this.children.map(item => ({
69+
height: item.height,
70+
top: getElementTop(item.$el)
71+
}));
72+
73+
const active = this.getActiveAnchorIndex(scrollTop, rects);
74+
75+
this.children.forEach((item, index) => {
76+
if (index === active) {
77+
item.active = true;
78+
item.top = Math.max(0, rects[index].top - scrollTop);
79+
} else if (index === active - 1) {
80+
const activeItemTop = rects[active].top - scrollTop;
81+
item.active = activeItemTop > 0;
82+
item.top = activeItemTop - rects[active].height;
83+
} else {
84+
item.active = false;
85+
}
86+
});
87+
},
88+
89+
getActiveAnchorIndex(scrollTop, rects) {
90+
for (let i = this.children.length - 1; i >= 0; i--) {
91+
const prevHeight = i > 0 ? rects[i - 1].height : 0;
92+
93+
if (scrollTop + prevHeight >= rects[i].top) {
94+
return i;
95+
}
96+
}
97+
return -1;
98+
},
99+
100+
onClick(event) {
101+
this.scrollToElement(event.target);
37102
},
38103

39104
onTouchMove(event) {
@@ -80,7 +145,7 @@ export default sfc({
80145
class={bem('sidebar')}
81146
style={{ zIndex: this.zIndex }}
82147
onClick={this.onClick}
83-
onTouchstart={this.onTouchStart}
148+
onTouchstart={this.touchStart}
84149
onTouchmove={this.onTouchMove}
85150
onTouchend={this.onTouchEnd}
86151
onTouchcancel={this.onTouchEnd}

0 commit comments

Comments
 (0)