<template>
<div class="box">
<div class="tab"></div>
<div class="content-box">
<div ref="wrapBox" class="wrapBox">
<div class="scrollBox">
<ul v-for="(item, i) in list" :key="i" ref="listItem">
<h2 class="letter">{{item.letter}}</h2>
<ul v-for="(iitem, ii) in item.letterList" :key="ii">
<li class="name">{{iitem}}</li>
</ul>
</ul>
</div>
</div>
<!-- -->
<div class="currentTitle" v-if="scrollY<0" ref="currentTitle">
<p>{{currentTitle}}</p>
</div>
<!-- 右侧导航栏 -->
<div class="rightBar" @touchstart='onShortcutTouchstart' @touchmove.stop.prevent='onShortcuutTouchMove'>
<span ref="letterList" :class="[currentIndex === i ?'active':'']" v-for="(item, i) in list" :key="i" :data-index='i'>{{item.letter}}</span>
</div>
</div>
</div>
</template>
<style scoped>
.box{
background-color: #222;
}
.tab{
height: 44px;
}
.letter{
background-color: #ddd;
}
.wrapBox{
position: fixed;
top: 44px;
bottom: 0;
width: 100%;
overflow: hidden;
}
.content-box{
position: fixed;
width: 100%;
bottom: 0;
overflow: hidden;
top: 0;
top: 44px;
}
.name{
line-height: 40px;
}
.rightBar{
position: absolute;
display: flex;
flex-direction: column;
top: 50%;
right: 0;
text-align: center;
padding: 0 20px;
transform: translateY(-50%)
}
.active{
color: red;
}
.currentTitle{
position: absolute;
top: 0;
width: 100%;
height: 32px;
line-height: 32px;
background-color: #dddddd;
color: #000;
/* transition: all 0.8s; */
}
</style>
<script>
import BScroll from 'better-scroll'
const TITLE_HEIGHT = 32
export default {
data () {
return {
diff: -1,
// currentTitle: '热门',
scrollY: -1,
currentIndex: 0,
list: [
{
letter: '热门',
letterList: ['薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦']
},{
letter: 'A',
letterList: ['A-Lin','A-Lin','A-Lin','A-Lin','A-Lin','A-Lin','A-Lin']
},{
letter: 'B',
letterList: ['本兮','本兮','本兮','本兮','本兮','本兮','本兮','本兮']
},{
letter: 'C',
letterList: ['陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅']
},{
letter: 'H',
letterList: ['华晨宇','华晨宇','华晨宇','华晨宇','华晨宇']
},{
letter: 'L',
letterList: ['林俊杰','林俊杰','林俊杰','林俊杰','林俊杰']
},{
letter: 'M',
letterList: ['马伊利','马伊利','马伊利','马伊利','马伊利','马伊利','马伊利','马伊利']
}
]
}
},
watch: {
list() {
setTimeout(() => {
this.calculateHeight()
}, 20);
},
scrollY (newY) {
const listHeight = this.listHeight
// 当滚动到顶部,newY > 0
if(newY > 0) {
this.currentIndex = 0
return
}
// 当在中间滚动
for(let i=0; i<listHeight.length - 1; i++) {
let height1 = listHeight[i]
let height2 = listHeight[i+1]
if(-newY >= height1 && -newY < height2) {
this.currentIndex = i
this.diff = height2 + newY
return
}
}
// 当滚动到底部,且 -newY 大于最后一个元素的上限
this.currentIndex = listHeight.length - 2
},
// 如果达到顶部,添加一个动画来缓冲上面的头部切换字母
diff (newVal) {
let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0
if (this.fixedTop === fixedTop) {
return
}
this.fixedTop = fixedTop
this.$refs.currentTitle.style.transform = `translate3d(0,${fixedTop}px,0)`
}
},
computed: {
currentTitle () {
if(this.scrollY > 0) {
return ''
}
return this.list[this.currentIndex] ? this.list[this.currentIndex].letter : ''
}
},
created () {
this.touch = {}
this.listenScroll = true
},
mounted () {
this._initScroll()
// 这个方法可以放在watch中,左边滚动部分封装成一个组件,然后传入渲染的数据,监听这个渲染的数据,这里因为没有做组件,所以手动调用一下
this.calculateHeight()
},
methods: {
_initScroll () {
this.scroll = new BScroll(this.$refs.wrapBox, {
scrollY: true,
probeType: 3
})
if(this.listenScroll) {
this.scroll.on('scroll', pos => {
this.scrollY = pos.y
})
}
},
// 点击右侧的字母
onShortcutTouchstart (e) {
let anchorIndex = e.target.getAttribute('data-index') //获取右侧索引
// 点击右侧的字母栏,左边跳转到对应的字母
// this.$refs.wrapBox.scrollToElement(this.$refs.listItem[anchorIndex], 0)
this.scrollToElement(this.$refs.listItem[anchorIndex], 0)
// 点击高亮
this.scrollY = -this.listHeight[anchorIndex]
// 滚动右侧字母
let firstTouch = e.touches[0]
this.touch.y1 = firstTouch.pageY
this.touch.anchorIndex = anchorIndex
},
// 滚动右侧的字母
onShortcuutTouchMove (e) {
let ANCHOR_HEIGHT = 21 //右侧每个字母的高度
let firstTouch = e.touches[0]
this.touch.y2 = firstTouch.pageY
let delta = Math.floor((this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT)
let anchorIndex = parseInt(this.touch.anchorIndex) + delta
this.scrollToElement(this.$refs.listItem[anchorIndex], 0)
// 实现点击高亮
// 这里的anchorIndex可能超过listHeight的范围而导致 this.listHeight[anchorIndex] NaN 所以这里要对 anchorIndex 添加一层校验
if(!this.listHeight[anchorIndex] && anchorIndex !== 0) {
return
}
this.scrollY = -this.listHeight[anchorIndex]
},
// 计算左边每一块字母的高度,使得左边的滚动右边对应字母高亮
calculateHeight () {
this.listHeight = []
const list = this.$refs.listItem
let height = 0
this.listHeight.push(height)
for(let i=0; i<list.length; i++) {
let item = list[i]
height += item.clientHeight
this.listHeight.push(height)
}
},
scrollTo () {
this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
},
scrollToElement () {
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
}
}
}
</script>