<template>
<view class="u-index-bar">
<slot />
<view
v-if="showSidebar"
class="u-index-bar__sidebar"
@touchstart.stop.prevent="onTouchMove"
@touchmove.stop.prevent="onTouchMove"
@touchend.stop.prevent="onTouchStop"
@touchcancel.stop.prevent="onTouchStop"
>
<view
v-for="(item, index) in indexList"
:key="index"
class="u-index-bar__index"
:style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
:data-index="index"
>{{ item }}</view>
</view>
<view
class="u-indexed-list-alert"
v-if="touchmove && indexList[touchmoveIndex]"
:style="{
zIndex: alertZIndex
}"
>
<text>{{indexList[touchmoveIndex]}}</text>
</view>
</view>
</template>
<script>
var indexList = function() {
var indexList = []
var charCodeOfA = 'A'.charCodeAt(0)
for (var i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i))
}
return indexList
}
export default {
name: 'u-index-list',
props: {
sticky: {
type: Boolean,
default: true
},
zIndex: {
type: [Number, String],
default: ''
},
scrollTop: {
type: [Number, String],
default: 0
},
offsetTop: {
type: [Number, String],
default: 0
},
indexList: {
type: Array,
default() {
return indexList()
}
},
activeColor: {
type: String,
default: '#2979ff'
}
},
created() {
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0
this.children = []
},
provide() {
return {
UIndexList: this
}
},
data() {
return {
activeAnchorIndex: 0,
showSidebar: true,
touchmove: false,
touchmoveIndex: 0
}
},
watch: {
scrollTop() {
this.updateData()
}
},
computed: {
alertZIndex() {
return this.$u.zIndex.toast
}
},
methods: {
updateData() {
this.timer && clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.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, index) =>
anchor.$uGetRect('.u-index-anchor-wrapper').then(rect => {
Object.assign(anchor, {
height: rect.height,
top: rect.top
})
})
)
)
},
setListRect() {
return this.$uGetRect('.u-index-bar').then(rect => {
Object.assign(this, {
height: rect.height,
top: rect.top + this.scrollTop
})
})
},
setSiderbarRect() {
return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
this.sidebar = {
height: rect.height,
top: rect.top
}
})
},
getActiveAnchorIndex() {
const { children } = this
const { sticky } = this
for (let i = this.children.length - 1; i >= 0; i--) {
const preAnchorHeight = i > 0 ? children[i - 1].height : 0
const reachTop = sticky ? preAnchorHeight : 0
if (reachTop >= children[i].top) {
return i
}
}
return -1
},
onScroll() {
const { children = [] } = this
if (!children.length) {
return
}
const { sticky, stickyOffsetTop, zIndex, scrollTop, activeColor } = this
const active = this.getActiveAnchorIndex()
this.activeAnchorIndex = active
if (sticky) {
let isActiveAnchorSticky = false
if (active !== -1) {
isActiveAnchorSticky = children[active].top <= 0
}
children.forEach((item, index) => {
if (index === active) {
let wrapperStyle = ''
let anchorStyle = {
color: `${activeColor}`
}
if (isActiveAnchorSticky) {
wrapperStyle = {
height: `${children[index].height}px`
}
anchorStyle = {
position: 'fixed',
top: `${stickyOffsetTop}px`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
}
}
item.active = active
item.wrapperStyle = wrapperStyle
item.anchorStyle = anchorStyle
} 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)`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
}
item.active = active
item.anchorStyle = anchorStyle
} else {
item.active = false
item.anchorStyle = ''
item.wrapperStyle = ''
}
})
}
},
onTouchMove(event) {
this.touchmove = true
const sidebarLength = this.children.length
const touch = event.touches[0]
const itemHeight = this.sidebar.height / sidebarLength
let clientY = 0
clientY = touch.clientY
let index = Math.floor((clientY - this.sidebar.top) / itemHeight)
if (index < 0) {
index = 0
} else if (index > sidebarLength - 1) {
index = sidebarLength - 1
}
this.touchmoveIndex = index
this.scrollToAnchor(index)
},
onTouchStop() {
this.touchmove = false
this.scrollToAnchorIndex = null
},
scrollToAnchor(index) {
if (this.scrollToAnchorIndex === index) {
return
}
this.scrollToAnchorIndex = index
const anchor = this.children.find(
item => item.index === this.indexList[index]
)
if (anchor) {
this.$emit('select', anchor.index)
uni.pageScrollTo({
duration: 0,
scrollTop: anchor.top + this.scrollTop
})
}
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';
.u-index-bar {
position: relative;
/deep/ .u-index-bar__sidebar {
z-index: 99999 !important;
}
/deep/ .list-cell {
background: #ebebeb;
}
/deep/ .u-index-anchor {
background: #e0e0e0;
}
}
.u-index-bar__sidebar {
position: fixed;
top: 50%;
right: 0;
display: flex;
flex-direction: column;
text-align: center;
transform: translateY(-50%);
user-select: none;
z-index: 99;
}
.u-index-bar__index {
font-weight: 500;
padding: 8rpx 18rpx;
font-size: 10px;
line-height: 1;
}
.u-indexed-list-alert {
position: fixed;
width: 120rpx;
height: 120rpx;
right: 90rpx;
top: 50%;
margin-top: -60rpx;
border-radius: 24rpx;
font-size: 50rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.65);
display: flex;
justify-content: center;
align-items: center;
padding: 0;
z-index: 9999999;
}
.u-indexed-list-alert text {
line-height: 50rpx;
}
</style>