实现左侧点击标题 右侧滑动至相应模块的效果 需要使用scroll-view 进行滚动及模块定位
1.首先实现左侧点击控制右侧滑动至对应模块
左侧渲染模块标签
<div class='containerleft'>
<div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
<p>{{item.title}}</p>
</div>
</div>
menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
复制代码
右侧使用srollview 设置纵向滚动:scroll-y="true",注意设置高度,通过scroll-into-view的id定位到到对应模块 从而实现点击左侧 右侧滑动的效果 同时scroll-with-animation开启定位动画
<div class='containerright'>
<scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
<div v-for="(item,index) in menuList" class='contentitem' :id='item.id' :key='index'>
<div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.name}}</div>
<div class='menubox'>
<div class='menuitem' v-for="(ite,ind) in Data" :key='ind' >
<image :src="ite.icon" mode="" class='icon'></image>
<p>{{ite.name}}</p>
</div>
</div>
</div>
</scroll-view>
</div>
data() {
return {
isClick:false, // 是否点击左侧
isShow: false, //是否显示本页面
currenIndex:0, // 当前点击下标 左侧激活下标
currentid:'', // 当前id
topArr:[], // 各模块顶部距离数组
beforeIndex:-1, // 前一个定位模块
positionIndex:-1, // 当前需要定位模块
positionabsolute:{
'position':'absolute',
'bottom':'0',
'left':'0',
},
menuList:[
{name:'招录数据',id:'zl'},
{name:'升学工具',id:'sx'},
{name:'高校查询',id:'gx'},
{name:'专业探索',id:'zy'},
{name:'了解职业',id:'lj'},],
Data:[
{name:'招生计划',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'历年录取',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'省控线',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'位次表',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},],
};
},
复制代码
2.实现右侧滑动左侧激活对应标题
<div class='containerleft'>
<div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
<p>{{item.title}}</p>
</div>
</div>
menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
复制代码
右侧通过 @scroll='scroll'的e.detail.scrollTop获取距离顶部的距离,
<scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
复制代码
页面渲染后通过id,uni.createSelectorQuery()获取各模块距离顶部距离的数组,注意在页面mounted之后使用
// 获取模块距离顶部高度
getModuleTop(){
const query = uni.createSelectorQuery().in(this);
let topArr=[];
for(var i=0;i<this.DataList.length;i++){
query.select(`#${this.DataList[i].id}`).boundingClientRect(data => {
// console.log("得到布局位置信息" + JSON.stringify(data));
// console.log("节点离页面顶部的距离为" + data.top);
const res= topArr.findIndex((item)=>{
return item===data.top
})
if(res===-1){
topArr.push(data.top)
}
}).exec();
}
setTimeout(()=>{
this.topArr=[...topArr]
},500)
},
复制代码
判断在数组范围内的下标 并返回下标 通过current控制左侧标题
// 判断在数组范围内的下标 并返回下标 右侧标题定位头部
// 判断在数组范围内的下标 并返回下标 控制左侧标题
getIndex(value,arr){
return arr.findIndex((item)=>{
// console.log(item,value)
return value>=(item-300) && value<=(item)
})
},
复制代码
3.右侧标题加定位滑动效果
通过当前移动到的下标 将其固定定位至头部 而之前的固定定位标题beforeIndex则改为相对定位 将其定位至上一个box的底部 从而实现页面标题上推 下一个标题固定的效果
<div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.title}}</div>
data(){
return {
beforeIndex:-1, // 前一个定位模块
positionIndex:-1, // 当前需要定位模块
positionabsolute:{
'position':'absolute',
'bottom':'0',
'left':'0',
},
}
}
复制代码
注意需要提前获取下一个即将固定定位标题的位置 从而将上一个固定定位标题置为相对定位
// 控制头部定位
const rightres=this.getPositionIndex(e.detail.scrollTop,this.topArr)
if(e.detail.scrollTop<5){
this.beforeIndex=-1
this.positionIndex=-1
}else{
if(rightres!==-1){
if(this.positionIndex!==rightres){
this.beforeIndex=-1
this.positionIndex=rightres
}
}else{
this.beforeIndex=-1
this.positionIndex=-1
}
}
const rightbeforeres=this.getBeforePositionIndex(e.detail.scrollTop,this.topArr)
if(rightbeforeres!==-1){
if(this.positionIndex!==rightbeforeres){
const index=this.positionIndex
this.beforeIndex=index
this.positionIndex=-1
}
}
// 判断在数组范围内的下标 并返回下标 右侧标题定位头部
getPositionIndex(value,arr){
return arr.findIndex((item,index)=>{
return value>=(item-50) && value<=(item+(arr[index+1]-arr[index]-40))
})
},
// 判断在数组范围内的下标 并返回下标 上一个固定定位标题相对定位至其box底部
getBeforePositionIndex(value,arr){
return arr.findIndex((item,index)=>{
return value>=(item-60) && value<=(item+(arr[index+1]-arr[index]-60))
})
},
复制代码
4.全部代码
<template>
<div v-if="isShow" class='toolkit-container'>
<div class='containerbox'>
<div class='containerleft'>
<div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
<p>{{item.name}}</p>
</div>
</div>
<div class='containerright'>
<scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
<div v-for="(item,index) in menuList" class='contentitem' :id='item.id' :key='index'>
<div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.name}}</div>
<div class='menubox'>
<div class='menuitem' v-for="(ite,ind) in Data" :key='ind' @click='handleswitch(ite)'>
<image :src="ite.icon" mode="" class='icon'></image>
<p>{{ite.name}}</p>
</div>
</div>
</div>
</scroll-view>
</div>
</div>
<div class='footer'>
主办:贵州省招生考试服务中心
</div>
</div>
</template>
<script>
import { footMixins } from "./config.js";
export default {
data() {
return {
isClick:false, // 是否点击左侧
isShow: false, //是否显示本页面
currenIndex:0, // 当前点击下标 左侧激活下标
currentid:'', // 当前id
topArr:[], // 各模块顶部距离数组
beforeIndex:-1, // 前一个定位模块
positionIndex:-1, // 当前需要定位模块
positionabsolute:{
'position':'absolute',
'bottom':'0',
'left':'0',
},
//测试数据
menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
Data:[{name:'招生计划',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'历年录取',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'省控线',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
{name:'位次表',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},],
};
},
mixins: [footMixins],
components: {
},
watch: {
isShow: function(val, oldVal) {
if(val){
this.getInit()
}
}
},
mounted(){
// this.getModuleTop()
},
methods: {
async getInit(){
const res = await this.$ajax({
url:'/gzsx/data/toolbox'
})
if(res.code===200){
this.DataList=[...res.list]
}
this.DataList.map((item,index)=>{
item.id=`menu${index}`
})
setTimeout(()=>{
this.getModuleTop()
},200)
},
// 左侧点击 定位右侧内容
handleMenuChange(item,index){
this.currentid=item.id
this.currenIndex=index
this.isClick=true
this.beforeIndex=-1
this.positionIndex=-1
},
// 右侧滚动
scroll(e){
//点击滑动不进行顶部定位效果
if(this.isClick){
this.isClick=false
}else{
// 控制左侧标题
const res=this.getIndex(e.detail.scrollTop,this.topArr)
if(res!==-1){
this.currenIndex=res
}
// 控制头部定位
const rightres=this.getPositionIndex(e.detail.scrollTop,this.topArr)
if(e.detail.scrollTop<5){
this.beforeIndex=-1
this.positionIndex=-1
}else{
if(rightres!==-1){
if(this.positionIndex!==rightres){
this.beforeIndex=-1
this.positionIndex=rightres
}
}else{
this.beforeIndex=-1
this.positionIndex=-1
}
}
const rightbeforeres=this.getBeforePositionIndex(e.detail.scrollTop,this.topArr)
if(rightbeforeres!==-1){
if(this.positionIndex!==rightbeforeres){
const index=this.positionIndex
this.beforeIndex=index
this.positionIndex=-1
}
}
}
},
// 获取模块距离顶部高度
getModuleTop(){
const query = uni.createSelectorQuery().in(this);
let topArr=[];
for(var i=0;i<this.menuList.length;i++){
query.select(`#${this.DataList[i].id}`).boundingClientRect(data => {
// console.log("得到布局位置信息" + JSON.stringify(data));
// console.log("节点离页面顶部的距离为" + data.top);
const res= topArr.findIndex((item)=>{
return item===data.top
})
if(res===-1){
topArr.push(data.top)
}
}).exec();
}
setTimeout(()=>{
this.topArr=[...topArr]
},500)
},
// 判断在数组范围内的下标 并返回下标 控制左侧标题
getIndex(value,arr){
return arr.findIndex((item)=>{
// console.log(item,value)
return value>=(item-300) && value<=(item)
})
},
// 判断在数组范围内的下标 并返回下标 右侧标题定位头部
getPositionIndex(value,arr){
return arr.findIndex((item,index)=>{
return value>=(item-50) && value<=(item+(arr[index+1]-arr[index]-40))
})
},
// 判断在数组范围内的下标 并返回下标 右侧标题定位头部
getBeforePositionIndex(value,arr){
return arr.findIndex((item,index)=>{
return value>=(item-60) && value<=(item+(arr[index+1]-arr[index]-60))
})
},
handleswitch(ite){
uni.navigateTo({
url:`${ite.actionTarget}`
})
}
}
};
</script>
<style lang="scss">
$fontfamily:'font-siyuan';
.toolkit-container{
height: calc(100vh - 60px);
.containerbox{
display:flex;
width:100%;
height:92%;
.containerleft{
width:38%;
height:100%;
background: #F8F8F8;
.item{
width: 100%;
height: 70px;
p{
width:100%;
text-align:center;
height:70px;
font-size: 16px;
font-family: $fontfamily;
font-weight: bold;
color: #858585;
line-height: 70px;
}
}
.active{
background: #FFFFFF;
color: #000000;
position:relative;
p{
color: #333;
}
}
.active::after{
content: '';
left: 0;
top: 0;
position: absolute;
width: 3px;
height: 70px;
background: #1D51FD;
}
}
.containerright{
width:62%;
height:100%;
background-color:#FFFFFF;
position: relative;
padding-top: 19px;
.contentitem{
width:100%;
position: relative;
.title{
width: 62vw;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999;
line-height: 20px;
padding-left: 18px;
// background-color: #F8F8F8;
}
.positiontop{
position: fixed;
top: 19px;
right: 0;
background-color: #FFFFFF;
opacity: 1;
z-index: 99;
}
.menubox{
display: flex;
flex-wrap: wrap;
padding: 23px 38px 22px 30px;
justify-content: space-between;
.menuitem{
flex-shrink: 1;
margin-bottom: 23px;
margin-right: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.icon{
display: block;
width: 47px;
height: 47px;
margin-bottom: 8px;
}
p{
text-align: center;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 17px;
}
}
}
}
}
}
.footer{
display: flex;
align-items: center;
justify-content: center;
width:100%;
height:8%;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: bold;
color: #BFBFBF;
text-align: center;
}
}
</style>
复制代码