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>