vue3uniapp实现自定义拱形底部导航栏,解决首次闪烁问题
前言:
我最初在网上翻阅查找了很多方法,发现大家都是说在page.json中tabbar中添加:"custom": true,即可解决首次闪烁的问题,可是添加了我这边还是会闪烁,因此我这边改变了思路,使用了虚拟页面来解决此问题。
效果图:
一:编写
1. 在page.json中写一个初始页面,pages中的第一个对象是默认展示第一个页面,所以一定要在写一个。
"pages": [
{
"name": "index",
"path": "pages/index/index",
"style":{
"navigationStyle":"custom"
}
},
2.新建index文件
3.编写index文件,因为我有三个页面,底部会有三个选项,每一个组件对应一个页面,Tabbr是我自定义的底部导航栏
<view>
<view>
<view v-if="pageStatus[0]" class="page__container" :style="pageContainerStyle(0)">
<scroll-view class="scroll-view" scroll-y>
<BasicPage />
</scroll-view>
</view>
<view v-if="pageStatus[1]" class="page__container" :style="pageContainerStyle(1)">
<scroll-view class="scroll-view" scroll-y>
<InquiriesPage />
</scroll-view>
</view>
<view v-if="pageStatus[2]" class="page__container" :style="pageContainerStyle(2)">
<scroll-view class="scroll-view" scroll-y>
<user />
</scroll-view>
</view>
</view>
//自定义底部组件,后面会讲怎么写
<Tabbr :current-page="0" @change="change"></Tabbr>
</view>
4.编写自定义导航栏,可以直接复制,修改路径为自己文件路径
<template>
<view class="tabbar-home">
<!-- 拱形区域 -->
<view class="arched"></view>
<view class="arched-bg"></view>
<!-- 盒子 -->
<view class="tabbar-container">
<view class="tabbar-item" v-for="(item, index) in tabbarList" :class="[item.centerItem ? ' center-item' : '']" @click="changeItem(item)">
<view class="item-top">
<image :src="currentItem == item.id ? item.selectIcon : item.icon"></image>
</view>
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
<text>{{ item.text }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { defineProps, onMounted, ref } from 'vue'
const props = defineProps({
currentPage: {
type: Number,
default: 0,
},
})
const currentItem = ref(0)
const tabbarList = ref([
{
id: 0,
path: '/pages/home/index',
icon: '/static/images/tabBar/unhome.png',
selectIcon: '/static/images/tabBar/homeSelect.png',
text: '首页',
centerItem: false,
},
{
id: 1,
path: '/pages/detail/index',
icon: '/static/images/tabBar/unInquiries.png',
selectIcon: '/static/images/tabBar/Inquiries.png',
text: '问询',
centerItem: true,
},
{
id: 2,
path: '/pages/user/index',
icon: '/static/images/tabBar/unhome.png',
selectIcon: '/static/images/tabBar/homeSelect.png',
text: '我的',
centerItem: false,
},
])
const emit = defineEmits(['change'])
function changeItem(item) {
currentItem.value = item.id
emit('change', item)
// uni.switchTab({
// url: item.path,
// })
}
onMounted(() => {
currentItem.value = props.currentPage
// 非微信小程序需隐藏原生tabBar(微信小程序已通过"custom": true配置项隐藏原生tabbar)
if (process.env.VUE_APP_PLATFORM != 'mp-weixin') {
uni.hideTabBar()
}
})
</script>
<style lang="scss" scoped>
.tabbar-home {
z-index: 20090;
height: 100rpx;
position: fixed;
left: 0;
bottom: 0;
box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
width: 100%;
box-sizing: content-box;
padding-bottom: env(safe-area-inset-bottom) !important;
}
view {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.tabbar-container {
position: absolute;
bottom: 0rpx;
left: 50%;
transform: translateX(-50%);
width: 100%;
/* box-shadow: 0 0 5px #8d6c36; */
display: flex;
align-items: center;
justify-content: space-around;
padding: 5rpx 0;
color: #8d6c36;
height: 100%;
padding-bottom: env(safe-area-inset-bottom) !important;
box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
background-color: rgba(255, 255, 255, 1);
z-index: inherit;
}
.tabbar-container .tabbar-item {
width: 20%;
height: 80rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.tabbar-container .item-active {
color: #01beff;
}
.tabbar-container .center-item {
display: block;
position: relative;
margin-top: 20rpx;
}
.tabbar-container .tabbar-item .item-top {
width: 70rpx;
height: 70rpx;
padding: 10rpx;
background: #ffffff;
}
.tabbar-container .center-item .item-top {
flex-shrink: 0;
width: 100%;
height: 140rpx;
padding: 20rpx;
position: absolute;
top: -70rpx;
/* left: calc(50% - 50rpx); */
border-radius: 50%;
/* box-shadow: 0 0 5px #999; */
// background-color: #f3d9a6;
}
.tabbar-container .tabbar-item .item-top image {
width: 40rpx;
height: 40rpx;
}
.tabbar-container .center-item .item-top image {
width: 70rpx;
height: 70rpx;
}
.tabbar-container .tabbar-item .item-bottom {
font-size: 28rpx;
width: 100%;
}
.tabbar-container .center-item .item-bottom {
position: absolute;
bottom: 0;
}
.arched {
width: 120rpx;
height: 120rpx;
left: 50%;
top: -42rpx;
position: absolute;
transform: translateX(-50%);
border-radius: 50%;
box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
background-color: rgba(255, 255, 255, 1);
// border: 2rpx solid rgba(0, 0, 0, 0.1);
z-index: 20089;
}
.arched-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 20089;
background-color: rgba(255, 255, 255, 1);
}
</style>
5.在index文件中引入了自定义导航栏组件和各页面组件
<script setup lang="ts">
import Tabbr from '../Tabbar/index.vue'
import BasicPage from './sub-page/BasicPage/index.vue'
import InquiriesPage from './sub-page/Inquiries/index.vue'
import user from './sub-page/user/index.vue'
import { useOrderedChildren } from './sub-page/hooks'
const { children: items, addChild: addItem, removeChild: removeItem } = useOrderedChildren<any>()
const tabbarData = ref([
{
id: 0,
path: '/pages/home/index',
icon: '/static/images/tabBar/unhome.png',
selectIcon: '/static/images/tabBar/homeSelect.png',
text: '首页',
centerItem: false,
},
{
id: 1,
path: '/pages/detail/index',
icon: '/static/images/tabBar/unhome.png',
selectIcon: '/static/images/tabBar/homeSelect.png',
text: '问询',
centerItem: true,
},
{
id: 2,
path: '/pages/user/index',
icon: '/static/images/tabBar/unhome.png',
selectIcon: '/static/images/tabBar/homeSelect.png',
text: '我的',
centerItem: false,
},
])
// 记录每个子页面的状态
const pageStatus = ref(Array.from({ length: tabbarData.value.length }, () => false))
const currentIndex = ref(0)
const change = (item: any) => {
pageStatus.value = pageStatus.value.map(() => false)
if (!pageStatus.value?.[item.id as number]) {
pageStatus.value[item.id as number] = true
currentIndex.value = item.id
nextTick(() => {
items.value?.[item.id as number]?.onLoad?.()
})
}
}
const pageContainerStyle = computed<(index: number) => any>(() => {
console.log('currentIndex', currentIndex.value)
return (index: number) => {
const style: any = {}
if (index !== currentIndex.value) {
style.display = 'none'
}
return style
}
})
onLoad((options) => {
const index = Number(options?.index || 0)
pageStatus.value[index] = true
nextTick(() => {
currentIndex.value = index
})
})
</script>
6.对了提个醒,自定义导航栏组件要写在page文件中~