IndexList
IndexList组件使用的时候父级盒子是inde-list,子集的数据组用index-section。
下面是index-list.vue:
<template>
<div class="mint-indexlist">
<ul class="mint-indexlist-content" ref="content" :style="{ 'height': currentHeight + 'px', 'margin-right': navWidth + 'px'}">
<slot></slot>
</ul>
<div class="mint-indexlist-nav" @touchstart="handleTouchStart" ref="nav">
<!-- mint-indexlist-nav是右侧滑动菜单 -->
<ul class="mint-indexlist-navlist">
<li class="mint-indexlist-navitem" v-for="(section, index) in sections" :key="index">{{ section.index }}</li>
</ul>
</div>
<div class="mint-indexlist-indicator" v-if="showIndicator" v-show="moving">{{ currentIndicator }}</div>
</div>
</template>
<style lang="scss" scoped>
@import "../style/var.scss";
.mint-indexlist {
width: 100%;
position: relative;
overflow: hidden;
.mint-indexlist-content {
margin: 0;
padding: 0;
overflow: auto;
}
.mint-indexlist-nav {
position: absolute;
top: 0;
bottom: 0;
right: 0;
margin: 0;
background-color: #fff;
border-left: solid 1px #ddd;
text-align: center;
max-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.mint-indexlist-navlist {
padding: 0;
margin: 0;
list-style: none;
max-height: 100%;
display: flex;
flex-direction: column;
}
.mint-indexlist-navitem {
padding: 2px 6px;
font-size: 12px;
user-select: none;
-webkit-touch-callout: none;
}
.mint-indexlist-indicator {
position: absolute;
height: 50px;
width: 50px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
line-height: 50px;
background-color: rgba(0, 0, 0, .7);
border-radius: 5px;
color: #fff;
font-size: 22px;
}
}
</style>
<script type="text/babel">
export default {
name: 'mt-index-list',
props: {
height: Number, //组件高度
showIndicator: { //是否显示索引提示符
type: Boolean,
default: true
}
},
data() {
return {
sections: [], //存有所有indexSction子组件实例的数组
navWidth: 0, //右侧滑动彩蛋的宽度
indicatorTime: null,
moving: false,
firstSection: null,
currentIndicator: '',
currentHeight: this.height,
navOffsetX: 0
};
},
watch: {
sections() {
this.init();
},
height(val) {
if (val) {
this.currentHeight = val;
}
}
},
methods: {
init() {
this.$nextTick(() => { //dom更新后计算右侧导航栏的宽度
this.navWidth = this.$refs.nav.clientWidth;
});
let listItems = this.$refs.content.getElementsByTagName('li'); //获取到每一个索引块组成的数组
if (listItems.length > 0) {
this.firstSection = listItems[0]; //第一个索引块
}
},
handleTouchStart(e) { //在右侧导航上touchstart的时候触发
if (e.target.tagName !== 'LI') { //如果没有在li上滑动就return
return;
}
this.navOffsetX = e.changedTouches[0].clientX; //在导航上滑动初始x轴坐标
this.scrollList(e.changedTouches[0].clientY); //touchstart到了哪个索引,就将页面立即滑动到那个索引所在的地方,通过y轴坐标来判断
if (this.indicatorTime) { //清除掉索引提示符的timer
clearTimeout(this.indicatorTime);
}
this.moving = true; //moving显示或隐藏索引提示符
window.addEventListener('touchmove', this.handleTouchMove); //给window绑定touchmove事件处理函数
window.addEventListener('touchend', this.handleTouchEnd); //给window绑定touchend事件处理函数
},
handleTouchMove(e) {
e.preventDefault();
this.scrollList(e.changedTouches[0].clientY); //touchmove的哦时候将content滚动到索引对应的位置即可
},
handleTouchEnd() {
this.indicatorTime = setTimeout(() => { //touchend后定时器500毫秒后隐藏提示符
this.moving = false;
this.currentIndicator = '';
}, 500);
window.removeEventListener('touchmove', this.handleTouchMove); //移除touchmove事件处理函数
window.removeEventListener('touchend', this.handleTouchEnd); //移除touchend事件处理函数
},
scrollList(y) { //根据touch事件的y轴坐标来滚动组件到特定位置
let currentItem = document.elementFromPoint(this.navOffsetX, y);
//获取到当前touch的元素
//doucument.elementFromPoint根据x轴和y轴坐标获取元素
if (!currentItem || !currentItem.classList.contains('mint-indexlist-navitem')) {
//如果获取不到mint-indexlist-navitem类名的元素,就return
//也就是必须获取到当前touch的导航索引字母
return;
}
this.currentIndicator = currentItem.innerText; //获取到当前touch的导航索引内容存入this.currentIndicator
let targets = this.sections.filter(section => section.index === currentItem.innerText);
//从sections数组里循环判断找到对应的indexSection子组件实例
let targetDOM;
if (targets.length > 0) {
targetDOM = targets[0].$el; //对应子组件的DOM
this.$refs.content.scrollTop = targetDOM.getBoundingClientRect().top - this.firstSection.getBoundingClientRect().top;
//将content的滚动距离设置为导航到的子组件的位置
//导航到的子组件的位置 = 目标子组件的top值 - 第一个子组件的top在值
}
}
},
mounted() {
if (!this.currentHeight) { //如果用户没有传递组件的height
window.scrollTo(0, 0); //页面滚动到最上面
requestAnimationFrame(()=>{ //执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画
this.currentHeight = document.documentElement.clientHeight - this.$refs.content.getBoundingClientRect().top;
//计算出组件的高度,文档高度减去content距离顶部的距离
});
}
this.init(); //初始化
}
};
</script>
下面是index-section.vue:
<template>
<li class="mint-indexsection">
<p class="mint-indexsection-index">{{ index }}</p>
<ul>
<slot></slot>
</ul>
</li>
</template>
<style lang="scss" scoped>
.mint-indexsection {
padding: 0;
margin: 0;
.mint-indexsection-index {
margin: 0;
padding: 10px;
background-color: #fafafa;
& + ul {
padding: 0;
}
}
}
</style>
<script type="text/babel">
export default {
name: 'mt-index-section',
props: {
index: { //使用时传进来的props,index是字母表的首字母
type: String,
required: true
}
},
mounted() {
this.$parent.sections.push(this); //将父组件indexList里的sections数组里加入indexSction子组件实例
},
beforeDestroy() { //销毁之前从父组件里的sections数组里去除当前子组件实例
let index = this.$parent.sections.indexOf(this);
if (index > -1) {
this.$parent.sections.splice(index, 1);
}
}
};
</script>
原理就是页面右侧有一个导航栏,当在导航栏上面发生touch事件的时候左边对应就移动到对应的数据项上面。

浙公网安备 33010602011771号