vue中自定义IndexBar 索引栏
vant是一款基于vue的牛皮移动端组件库,比如类似手机电话簿的字母索引兰组件
https://youzan.github.io/vant/#/zh-CN/index-bar
今天看了朋友分享的一个自定义索引兰组件代码,是慕课网几年前的某个vue教程,结合vant的indexBar源码,值得一看学习学习
父组件
<list-view @select="selectSinger" :data="singers" ref="list"></list-view>
子组件
<template> <!-- 右侧字母样式 索引栏 子组件 --> <scroll @scroll="scroll" :listen-scroll="listenScroll" :probe-type="probeType" :data="data" class="listview" ref="listview"> <!-- 左侧内容列表 --> <ul> <li v-for="group in data" :key="group" class="list-group" ref="listGroup"> <!-- 子分类标题(‘热’、字母) --> <h2 class="list-group-title">{{group.title}}</h2> <!-- 子分类列表 --> <uL> <li @click="selectItem(item)" v-for="item in group.items" :key="item" class="list-group-item"> <img class="avatar" v-lazy="item.avatar"> <span class="name">{{item.name}}</span> </li> </uL> </li> </ul> <!-- 右侧字母快速导航条 --> <div class="list-shortcut" @touchstart.stop.prevent="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove" @touchend.stop> <ul> <li v-for="(item, index) in shortcutList" :data-index="index" :key="index" class="item" :class="{'current':currentIndex===index}">{{item}} </li> </ul> </div> <!-- 顶部标题栏(显示当前子分类所属字母) --> <div class="list-fixed" ref="fixed" v-show="fixedTitle"> <div class="fixed-title">{{fixedTitle}} </div> </div> <!-- 封装的加载状态子组件 --> <div v-show="!data.length" class="loading-container"> <loading></loading> </div> </scroll> </template> <script type="text/ecmascript-6"> import Scroll from 'base/scroll/scroll' import Loading from 'base/loading/loading' import {getData} from 'common/js/dom' // 顶部标题栏高度 const TITLE_HEIGHT = 30 // 右侧每个字母高度 const ANCHOR_HEIGHT = 18 export default { props: { data: { type: Array, default: [] } }, computed: { shortcutList() { return this.data.map((group) => { return group.title.substr(0, 1) }) }, //左侧列表页每个子列表滚动到顶部时,顶部标题栏显示当前字母 fixedTitle() { if (this.scrollY > 0) { return '' } return this.data[this.currentIndex] ? this.data[this.currentIndex].title : '' } }, data() { return { scrollY: -1, currentIndex: 0, diff: -1 } }, created() { this.probeType = 3 this.listenScroll = true this.touch = {} this.listHeight = [] }, methods: { selectItem(item) { this.$emit('select', item) }, //右侧快捷栏点击事件 onShortcutTouchStart(e) { let anchorIndex = getData(e.target, 'index') let firstTouch = e.touches[0] this.touch.y1 = firstTouch.pageY this.touch.anchorIndex = anchorIndex this._scrollTo(anchorIndex) }, //右侧快捷栏滑动事件 onShortcutTouchMove(e) { let firstTouch = e.touches[0] this.touch.y2 = firstTouch.pageY let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0 let anchorIndex = parseInt(this.touch.anchorIndex) + delta this._scrollTo(anchorIndex) }, refresh() { this.$refs.listview.refresh() }, scroll(pos) { this.scrollY = pos.y }, _calculateHeight() { this.listHeight = [] const list = this.$refs.listGroup 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(index) { if (!index && index !== 0) { return } if (index < 0) { index = 0 } else if (index > this.listHeight.length - 2) { index = this.listHeight.length - 2 } this.scrollY = -this.listHeight[index] this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0) } }, watch: { data() { setTimeout(() => { this._calculateHeight() }, 20) }, //监听Y轴方向滚动 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 }, //左侧内容区,子分类的标题向上滚动到顶部标题栏位置时,取代原来的顶部标题栏内容 // 为了提升用户体验,原来的顶部标题栏添加向上滑动的动画效果 // 此时需要判断子分类的标题的scrollY值(即height2 + newY)是否小于顶部标题栏的高度TITLE_HEIGHT diff(newVal) { let fixedTop = (newVal > 0 && newVal < TITLE_HEIGHT) ? newVal - TITLE_HEIGHT : 0 if (this.fixedTop === fixedTop) { return } this.fixedTop = fixedTop this.$refs.fixed.style.transform = `translate3d(0,${fixedTop}px,0)` } }, components: { Scroll, Loading } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> @import "~common/stylus/variable" .listview position: relative width: 100% height: 100% overflow: hidden background: $color-background .list-group padding-bottom: 30px .list-group-title height: 30px line-height: 30px padding-left: 20px font-size: $font-size-small color: $color-text-l background: $color-highlight-background .list-group-item display: flex align-items: center padding: 20px 0 0 30px .avatar width: 50px height: 50px border-radius: 50% .name margin-left: 20px color: $color-text-l font-size: $font-size-medium .list-shortcut position: absolute z-index: 30 right: 0 top: 50% transform: translateY(-50%) width: 20px padding: 20px 0 border-radius: 10px text-align: center background: $color-background-d font-family: Helvetica .item padding: 3px line-height: 1 color: $color-text-l font-size: $font-size-small &.current color: $color-theme .list-fixed position: absolute top: 0 left: 0 width: 100% .fixed-title height: 30px line-height: 30px padding-left: 20px font-size: $font-size-small color: $color-text-l background: $color-highlight-background .loading-container position: absolute width: 100% top: 50% transform: translateY(-50%) </style>