|
|
<template> <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" --> <view> <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> </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; };
/** * indexList 索引列表 * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用 * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
* @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入 * @property {Array} index-list 索引字符列表,数组(默认A-Z) * @property {Number String} z-index 锚点吸顶时的层级(默认965) * @property {Boolean} sticky 是否开启锚点自动吸顶(默认true) * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离(默认0) * @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff) * @event {Function} select 选中右边索引字符时触发 * @example <u-index-list :scrollTop="scrollTop"></u-index-list> */ 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() { // #ifdef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44; // #endif
// #ifndef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0; // #endif
// 只能在created生命周期定义children,如果在data定义,会因为循环引用而报错
this.children = []; }, data() { return { activeAnchorIndex: 0, showSidebar: true, // children: [],
touchmove: false, touchmoveIndex: 0, } }, watch: { scrollTop() { this.updateData() } }, computed: { // 弹出toast的z-index值
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 }
.u-index-bar__sidebar { position: fixed; top: 50%; right: 0; @include vue-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: 22rpx; 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); @include vue-flex; justify-content: center; align-items: center; padding: 0; z-index: 9999999; }
.u-indexed-list-alert text { line-height: 50rpx; } </style>
|