You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
8.3 KiB
246 lines
8.3 KiB
import { GREEN } from '../common/color'; |
|
import { VantComponent } from '../common/component'; |
|
import { useChildren } from '../common/relation'; |
|
import { getRect, isDef } from '../common/utils'; |
|
import { pageScrollMixin } from '../mixins/page-scroll'; |
|
const indexList = () => { |
|
const indexList = []; |
|
const charCodeOfA = 'A'.charCodeAt(0); |
|
for (let i = 0; i < 26; i++) { |
|
indexList.push(String.fromCharCode(charCodeOfA + i)); |
|
} |
|
return indexList; |
|
}; |
|
VantComponent({ |
|
relation: useChildren('index-anchor', function () { |
|
this.updateData(); |
|
}), |
|
props: { |
|
sticky: { |
|
type: Boolean, |
|
value: true, |
|
}, |
|
zIndex: { |
|
type: Number, |
|
value: 1, |
|
}, |
|
highlightColor: { |
|
type: String, |
|
value: GREEN, |
|
}, |
|
stickyOffsetTop: { |
|
type: Number, |
|
value: 0, |
|
}, |
|
indexList: { |
|
type: Array, |
|
value: indexList(), |
|
}, |
|
}, |
|
mixins: [ |
|
pageScrollMixin(function (event) { |
|
this.scrollTop = (event === null || event === void 0 ? void 0 : event.scrollTop) || 0; |
|
this.onScroll(); |
|
}), |
|
], |
|
data: { |
|
activeAnchorIndex: null, |
|
showSidebar: false, |
|
}, |
|
created() { |
|
this.scrollTop = 0; |
|
}, |
|
methods: { |
|
updateData() { |
|
wx.nextTick(() => { |
|
if (this.timer != null) { |
|
clearTimeout(this.timer); |
|
} |
|
this.timer = setTimeout(() => { |
|
this.setData({ |
|
showSidebar: !!this.children.length, |
|
}); |
|
this.setRect().then(() => { |
|
this.onScroll(); |
|
}); |
|
}, 0); |
|
}); |
|
}, |
|
setRect() { |
|
return Promise.all([ |
|
this.setAnchorsRect(), |
|
this.setListRect(), |
|
this.setSiderbarRect(), |
|
]); |
|
}, |
|
setAnchorsRect() { |
|
return Promise.all(this.children.map((anchor) => getRect(anchor, '.van-index-anchor-wrapper').then((rect) => { |
|
Object.assign(anchor, { |
|
height: rect.height, |
|
top: rect.top + this.scrollTop, |
|
}); |
|
}))); |
|
}, |
|
setListRect() { |
|
return getRect(this, '.van-index-bar').then((rect) => { |
|
if (!isDef(rect)) { |
|
return; |
|
} |
|
Object.assign(this, { |
|
height: rect.height, |
|
top: rect.top + this.scrollTop, |
|
}); |
|
}); |
|
}, |
|
setSiderbarRect() { |
|
return getRect(this, '.van-index-bar__sidebar').then((res) => { |
|
if (!isDef(res)) { |
|
return; |
|
} |
|
this.sidebar = { |
|
height: res.height, |
|
top: res.top, |
|
}; |
|
}); |
|
}, |
|
setDiffData({ target, data }) { |
|
const diffData = {}; |
|
Object.keys(data).forEach((key) => { |
|
if (target.data[key] !== data[key]) { |
|
diffData[key] = data[key]; |
|
} |
|
}); |
|
if (Object.keys(diffData).length) { |
|
target.setData(diffData); |
|
} |
|
}, |
|
getAnchorRect(anchor) { |
|
return getRect(anchor, '.van-index-anchor-wrapper').then((rect) => ({ |
|
height: rect.height, |
|
top: rect.top, |
|
})); |
|
}, |
|
getActiveAnchorIndex() { |
|
const { children, scrollTop } = this; |
|
const { sticky, stickyOffsetTop } = this.data; |
|
for (let i = this.children.length - 1; i >= 0; i--) { |
|
const preAnchorHeight = i > 0 ? children[i - 1].height : 0; |
|
const reachTop = sticky ? preAnchorHeight + stickyOffsetTop : 0; |
|
if (reachTop + scrollTop >= children[i].top) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
}, |
|
onScroll() { |
|
const { children = [], scrollTop } = this; |
|
if (!children.length) { |
|
return; |
|
} |
|
const { sticky, stickyOffsetTop, zIndex, highlightColor } = this.data; |
|
const active = this.getActiveAnchorIndex(); |
|
this.setDiffData({ |
|
target: this, |
|
data: { |
|
activeAnchorIndex: active, |
|
}, |
|
}); |
|
if (sticky) { |
|
let isActiveAnchorSticky = false; |
|
if (active !== -1) { |
|
isActiveAnchorSticky = |
|
children[active].top <= stickyOffsetTop + scrollTop; |
|
} |
|
children.forEach((item, index) => { |
|
if (index === active) { |
|
let wrapperStyle = ''; |
|
let anchorStyle = ` |
|
color: ${highlightColor}; |
|
`; |
|
if (isActiveAnchorSticky) { |
|
wrapperStyle = ` |
|
height: ${children[index].height}px; |
|
`; |
|
anchorStyle = ` |
|
position: fixed; |
|
top: ${stickyOffsetTop}px; |
|
z-index: ${zIndex}; |
|
color: ${highlightColor}; |
|
`; |
|
} |
|
this.setDiffData({ |
|
target: item, |
|
data: { |
|
active: true, |
|
anchorStyle, |
|
wrapperStyle, |
|
}, |
|
}); |
|
} |
|
else if (index === active - 1) { |
|
const currentAnchor = children[index]; |
|
const currentOffsetTop = currentAnchor.top; |
|
const targetOffsetTop = index === children.length - 1 |
|
? this.top |
|
: children[index + 1].top; |
|
const parentOffsetHeight = targetOffsetTop - currentOffsetTop; |
|
const translateY = parentOffsetHeight - currentAnchor.height; |
|
const anchorStyle = ` |
|
position: relative; |
|
transform: translate3d(0, ${translateY}px, 0); |
|
z-index: ${zIndex}; |
|
color: ${highlightColor}; |
|
`; |
|
this.setDiffData({ |
|
target: item, |
|
data: { |
|
active: true, |
|
anchorStyle, |
|
}, |
|
}); |
|
} |
|
else { |
|
this.setDiffData({ |
|
target: item, |
|
data: { |
|
active: false, |
|
anchorStyle: '', |
|
wrapperStyle: '', |
|
}, |
|
}); |
|
} |
|
}); |
|
} |
|
}, |
|
onClick(event) { |
|
this.scrollToAnchor(event.target.dataset.index); |
|
}, |
|
onTouchMove(event) { |
|
const sidebarLength = this.children.length; |
|
const touch = event.touches[0]; |
|
const itemHeight = this.sidebar.height / sidebarLength; |
|
let index = Math.floor((touch.clientY - this.sidebar.top) / itemHeight); |
|
if (index < 0) { |
|
index = 0; |
|
} |
|
else if (index > sidebarLength - 1) { |
|
index = sidebarLength - 1; |
|
} |
|
this.scrollToAnchor(index); |
|
}, |
|
onTouchStop() { |
|
this.scrollToAnchorIndex = null; |
|
}, |
|
scrollToAnchor(index) { |
|
if (typeof index !== 'number' || this.scrollToAnchorIndex === index) { |
|
return; |
|
} |
|
this.scrollToAnchorIndex = index; |
|
const anchor = this.children.find((item) => item.data.index === this.data.indexList[index]); |
|
if (anchor) { |
|
anchor.scrollIntoView(this.scrollTop); |
|
this.$emit('select', anchor.data.index); |
|
} |
|
}, |
|
}, |
|
});
|
|
|