呈现效果
说明:实现导航与滑动内容之间的双向绑定,页面滑动时导航选中项会自动进行切换、当切换导航选中项,滑动内容会跳转至相应位置;
操作步骤
1.页面渲染,设置导航部分与滑动盒子;
<template>
<view class="detail">
<!-- 导航部分 -->
<view class="nav_box">
<view class="nav_item">{{item.title}} </view>
</view>
</view>
<!-- 滑动内容部分 -->
<scroll-view ref="scrollView" scroll-y="true" class="scroll_box" >
<view id="cp"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cp</view>
<view id="cb"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cb</view>
<view id="pj" ></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} pj</view>
<view id="yy"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} yy</view>
<view id="sg"></view>
<view class="item" v-for="item,index in 50" :key="index">{{item}} sg</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from "vue"
// 导航列表
const navList = ref([
{
id:1,
title:"产品",
},
{
id:2,
title:"成本",
},
{
id:3,
title:"评论",
},
{
id:4,
title:"运营",
},
{
id:5,
title:"事故"
},
])
</script>
<style lang="scss" scoped>
.detail{
.top{
display: flex;
align-items: center;
padding: 0 30rpx;
height: 100rpx;
.search{
flex: 1;
}
.top_icon{
width: 60rpx;
height: 60rpx;
padding: 20rpx;
margin-left: 20rpx;
image{
width: 100%;
height: 100%;
}
}
.share{
width: 50rpx;
height: 50rpx;
}
}
.nav_box{
position: fixed;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
height: 100rpx;
padding: 0 30rpx;
box-shadow: 5rpx 5rpx 10rpx rgba(0, 0,0, 0.5);
background-color: #fff;
z-index: 10;
.nav_item{
padding: 20rpx;
// background-color: pink;
}
.active{
color: $theme-color;
}
}
.scroll_box{
background-color: pink;
}
}
</style>
2.获取滚动高度;
通过uni.getSystemInfoSync()获取页面信息来动态设置导航盒子的高度与位置、获取的区域
<template>
<view class="detail">
<view class="nav_box" id="nav_box" :style="{top:nav_height + 'px'}">
<view class="nav_item" :class="{'active':index == currentIndex}" v-for="item,index in navList" :key="index" >{{item.title}} </view>
</view>
<view class="fill" :style="{height:nav_box_height + 'px'}">
</view>
<scroll-view ref="scrollView" scroll-y="true" class="scroll_box" :style="{height: window_height + 'px'}" >
<view id="cp"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cp</view>
<view id="cb"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cb</view>
<view id="pj" ></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} pj</view>
<view id="yy"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} yy</view>
<view id="sg"></view>
<view class="item" v-for="item,index in 50" :key="index">{{item}} sg</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref,onMounted } from "vue"
const window_height = ref(0)// 视图可用高度
const nav_height = ref(0)//头部导航条高度
const nav_box_height = ref(0)// 导航高度
onMounted(()=>{
// 设置导航头部高度、导航高度
let SYSTEM_INFO = uni.getSystemInfoSync()
const nav_box = document.getElementById("nav_box").offsetHeight
nav_box_height.value = nav_box
window_height.value = SYSTEM_INFO.windowHeight - nav_box
nav_height.value = SYSTEM_INFO.windowTop
})
</script>
3.获取滚动各节点距离滑动的位置;
通过uni.createSelectorQuery()获取滑动各节点位置
<script setup>
import { onLoad, } from "@dcloudio/uni-app"
import { ref,onMounted } from "vue"
// 各部分距离头部的高度
const scrollView = ref(null)
const cpNode = ref(null)
const cbNode = ref(null)
const pjNode = ref(null)
const yyNode = ref(null)
const sgNode = ref(null)
onMounted(()=>{
// 获取滑动各个子节点的滑动高度
const query = uni.createSelectorQuery().in(scrollView.value)
query.select('#cp').boundingClientRect((rect)=>{
cpNode.value = rect.top
}).exec();
query.select('#cb').boundingClientRect((rect)=>{
cbNode.value = rect.top
}).exec();
query.select('#pj').boundingClientRect((rect)=>{
pjNode.value = rect.top
}).exec();
query.select('#yy').boundingClientRect((rect)=>{
yyNode.value = rect.top
}).exec();
query.select('#sg').boundingClientRect((rect)=>{
sgNode.value = rect.top
}).exec();
})
</script>
4.在scroll-view中的scroll事件中,设置导航选中项;
<view class="nav_box" id="nav_box" :style="{top:nav_height + 'px'}">
<view class="nav_item" :class="{'active':index == currentIndex}" v-for="item,index in navList" :key="index" >{{item.title}} </view>
</view>
<view class="fill" :style="{height:nav_box_height + 'px'}">
</view>
<scroll-view ref="scrollView" scroll-y="true" :scroll-top="scroll_top" class="scroll_box" :style="{height: window_height + 'px'}" @scroll="scrollHandle">
<view id="cp"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cp</view>
<view id="cb"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cb</view>
<view id="pj" ></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} pj</view>
<view id="yy"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} yy</view>
<view id="sg"></view>
<view class="item" v-for="item,index in 50" :key="index">{{item}} sg</view>
</scroll-view>
const currentIndex = ref(0)// 选中导航
const scrollHandle = (e) => {
let scrollTop = e.detail.scrollTop
if(scrollTop>=cpNode.value && scrollTop<cbNode.value){
currentIndex.value = 0
}else if(scrollTop>=cbNode.value && scrollTop<pjNode.value){
currentIndex.value = 1
}else if(scrollTop>=pjNode.value && scrollTop<yyNode.value){
currentIndex.value = 2
}else if(scrollTop>=yyNode.value && scrollTop<sgNode.value){
currentIndex.value = 3
}else{
currentIndex.value = 4
}
}
5.设置切换导航项;
<view class="nav_box" id="nav_box" :style="{top:nav_height + 'px'}">
<view class="nav_item" :class="{'active':index == currentIndex}" v-for="item,index in navList" :key="index" @click="navHandle(index)">{{item.title}} </view>
</view>
// 切换导航位置
const scroll_top = ref(0)// 滚动头部跳转的位置
const navHandle = (e) => {
switch (e) {
case 0:
scroll_top.value = cpNode.value ? cpNode.value : 0; // 使用 offsetTop 获取节点顶部位置
break;
case 1:
scroll_top.value = cbNode.value ? cbNode.value : 0;
break;
case 2: // 注意:这里你写的是 1,但我假设你可能是想写 3
scroll_top.value = pjNode.value ? pjNode.value : 0; // 根据你的描述,这里似乎仍然是 pjNode
break;
case 3:
scroll_top.value = yyNode.value ? yyNode.value : 0;
break;
default:
scroll_top.value = sgNode.value ? sgNode.value : 0; // 默认滚动到 sgNode
}
}
完整代码
<template>
<view class="detail">
<!-- 导航部分 -->
<view class="nav_box" id="nav_box" :style="{top:nav_height + 'px'}">
<view class="nav_item" :class="{'active':index == currentIndex}" v-for="item,index in navList" :key="index" @click="navHandle(index)">{{item.title}} </view>
</view>
<view class="fill" :style="{height:nav_box_height + 'px'}">
</view>
<!-- 滑动内容部分 -->
<scroll-view ref="scrollView" scroll-y="true" :scroll-top="scroll_top" class="scroll_box" :style="{height: window_height + 'px'}" @scroll="scrollHandle">
<view id="cp"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cp</view>
<view id="cb"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cb</view>
<view id="pj" ></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} pj</view>
<view id="yy"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} yy</view>
<view id="sg"></view>
<view class="item" v-for="item,index in 50" :key="index">{{item}} sg</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref,onMounted } from "vue"
// 导航列表
const navList = ref([
{
id:1,
title:"产品",
},
{
id:2,
title:"成本",
},
{
id:3,
title:"评论",
},
{
id:4,
title:"运营",
},
{
id:5,
title:"事故"
},
])
const currentIndex = ref(0)// 选中导航
const scroll_top = ref(0)// 滚动头部跳转的位置
// 各部分距离头部的高度
const scrollView = ref(null)
const cpNode = ref(null)
const cbNode = ref(null)
const pjNode = ref(null)
const yyNode = ref(null)
const sgNode = ref(null)
// 视图可用高度
const window_height = ref(0)
const nav_height = ref(0)//头部导航条高度
const nav_box_height = ref(0)//导航高度
// 切换导航位置
const navHandle = (e) => {
switch (e) {
case 0:
scroll_top.value = cpNode.value ? cpNode.value : 0; // 使用 offsetTop 获取节点顶部位置
break;
case 1:
scroll_top.value = cbNode.value ? cbNode.value : 0;
break;
case 2: // 注意:这里你写的是 1,但我假设你可能是想写 3
scroll_top.value = pjNode.value ? pjNode.value : 0; // 根据你的描述,这里似乎仍然是 pjNode
break;
case 3:
scroll_top.value = yyNode.value ? yyNode.value : 0;
break;
default:
scroll_top.value = sgNode.value ? sgNode.value : 0; // 默认滚动到 sgNode
}
}
const scrollHandle = (e) => {
let scrollTop = e.detail.scrollTop
if(scrollTop>=cpNode.value && scrollTop<cbNode.value){
currentIndex.value = 0
}else if(scrollTop>=cbNode.value && scrollTop<pjNode.value){
currentIndex.value = 1
}else if(scrollTop>=pjNode.value && scrollTop<yyNode.value){
currentIndex.value = 2
}else if(scrollTop>=yyNode.value && scrollTop<sgNode.value){
currentIndex.value = 3
}else{
currentIndex.value = 4
}
}
onMounted(()=>{
// 设置导航头部高度、导航高度
let SYSTEM_INFO = uni.getSystemInfoSync()
const nav_box = document.getElementById("nav_box").offsetHeight
nav_box_height.value = nav_box
window_height.value = SYSTEM_INFO.windowHeight - nav_box
nav_height.value = SYSTEM_INFO.windowTop
// 获取滑动各个子节点的滑动高度
const query = uni.createSelectorQuery().in(scrollView.value)
query.select('#cp').boundingClientRect((rect)=>{
cpNode.value = rect.top
}).exec();
query.select('#cb').boundingClientRect((rect)=>{
cbNode.value = rect.top
}).exec();
query.select('#pj').boundingClientRect((rect)=>{
pjNode.value = rect.top
}).exec();
query.select('#yy').boundingClientRect((rect)=>{
yyNode.value = rect.top
}).exec();
query.select('#sg').boundingClientRect((rect)=>{
sgNode.value = rect.top
}).exec();
})
</script>
<style lang="scss" scoped>
.detail{
.top{
display: flex;
align-items: center;
padding: 0 30rpx;
height: 100rpx;
.search{
flex: 1;
}
.top_icon{
width: 60rpx;
height: 60rpx;
padding: 20rpx;
margin-left: 20rpx;
image{
width: 100%;
height: 100%;
}
}
.share{
width: 50rpx;
height: 50rpx;
}
}
.nav_box{
position: fixed;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
height: 100rpx;
padding: 0 30rpx;
box-shadow: 5rpx 5rpx 10rpx rgba(0, 0,0, 0.5);
background-color: #fff;
z-index: 10;
.nav_item{
padding: 20rpx;
// background-color: pink;
}
.active{
color: $theme-color;
}
}
.scroll_box{
background-color: pink;
}
}
</style>
扩展:步骤五可以通过中的scroll-into-view属性进行切换(当不需要进行滑动框内容对导航绑定时可以进行使用,例如:常规分类页中左右结构的滑动,*代码量会减少一些)
<view class="nav_box" id="nav_box" :style="{top:nav_height + 'px'}">
<view class="nav_item" :class="{'active':index == currentIndex}" v-for="item,index in navList" :key="index" @click="navHandle(index)">{{item.title}} </view>
</view>
<view class="fill" :style="{height:nav_box_height + 'px'}">
</view>
<scroll-view scroll-y="true" :scroll-into-view="activeId" :style="{height: window_height + 'px'}">
<view id="cp"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cp</view>
<view id="cb"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} cb</view>
<view id="pj" ></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} pj</view>
<view id="yy"></view>
<view class="item" v-for="item,index in 20" :key="index">{{item}} yy</view>
<view id="sg"></view>
<view class="item" v-for="item,index in 50" :key="index">{{item}} sg</view>
</scroll-view>
<script setup>
import { ref,onMounted } from "vue"
// 导航列表
const navList = ref([
{
id:1,
title:"产品",
},
{
id:2,
title:"成本",
},
{
id:3,
title:"评论",
},
{
id:4,
title:"运营",
},
{
id:5,
title:"事故"
},
])
const currentIndex = ref(0)// 选中导航
// 各部分距离头部的高度
// 视图可用高度
const window_height = ref(0)
const nav_height = ref(0)//头部导航条高度
const nav_box_height = ref(0)//导航条高度// 导航高度
const activeId = ref('')
// 切换导航位置
const navHandle = (e) => {
currentIndex.value = e
switch (e) {
case 0:
activeId.value = 'cp'
break;
case 1:
activeId.value = 'cb'
break;
case 2:
activeId.value = 'pj'
break;
case 3:
activeId.value = 'yy'
break;
default:
activeId.value = 'sg'
}
}
onMounted(()=>{
// 设置导航头部高度、导航高度
let SYSTEM_INFO = uni.getSystemInfoSync()
const nav_box = document.getElementById("nav_box").offsetHeight
nav_box_height.value = nav_box
window_height.value = SYSTEM_INFO.windowHeight - nav_box
nav_height.value = SYSTEM_INFO.windowTop
})
</script>
*存在优化法:将节点名称放入一个数组中,例:
const scrollIds = ['cp', 'cb', 'pj', 'yy', 'sg'];
const navHandle = (e) => {
currentIndex.value = e
activeId.value = scrollIds[e]
}