您的当前位置:首页正文

uniapp 、vue2/3自定义底部 tabbar 导航栏凹凸透明显示,高级效果。(兼容h5、微信小程序、app)

2024-10-31 来源:个人技术集锦

1.项目介绍

2.不同端的效果展示图

2.1无底部安全区域:

2.2有底部安全区域:

3. 代码编写

3.1 html 代码部分:

3.2 html 核心代码:

3.3 css 代码部分:

3.4 css 核心代码:

3.5 vue2 代码编写:

3.6 vue3 代码编写:

3.7 vue2/3 核心代码:

3.8 Tabbar 组件的使用:

4. vue2 完整代码

5. vue3 完整代码


1.项目介绍

项目描述:uniapp 使用 vue2/3,自定义封装 tabbar,底部凹凸透明显示。兼容h5、微信小程序、app等。html 和 css 部分代码一样,只有 js 功能代码,分别使用 vue2 和 vue3 的写法。

兼容性:h5、微信小程序、app端兼容,其他端没有测试过,应该也兼容的。

技术栈:uniapp + vue2 /3。

2.不同端的效果展示图

2.1无底部安全区域:

h5端,底部是没有安全区域的,效果图如下:

2.2有底部安全区域:

微信小程序端app端中,苹果手机底部可能会有安全区域,效果图如下

3. 代码编写

3.1 html 代码部分:

<template>
	<view class="tabbar" :style="`--repeat: ${tabbar.length}`">
		<view class="tabbar-nav">
			<view class="tabbar-nav-a" v-for="(item, index) in tabbar" :key="index"
				:class="pageIndex == index ? 'after' : ''" @tap="tapTabbar(item, index)">
				<view class="after-icon" v-if="pageIndex == index">
					<uni-icons :type="item.selectedIconPath" size="60rpx" color="#fff"></uni-icons>
				</view>
				<uni-icons :type="item.iconPath" size="48rpx" v-else></uni-icons>
				<text class="text">{{ item.text }}</text>
			</view>
		</view>
		<view class="safeZone" :style="{ 'height': safeAreaInsetsBottom }"></view>
	</view>
	<view :style="{ 'height': tabbarHeight }"></view>
</template>

3.2 html 核心代码:

:style="`--repeat: ${tabbar.length}`" tabbar的数量,根据数组的长度改变。

:style="{ 'height': safeAreaInsetsBottom }" :底部安全区域的高度。

:style="{ 'height': tabbarHeight }":tabbar导航栏的高度,防止内容被遮挡。


3.3 css 代码部分:

<style lang="scss" scoped>
.tabbar {
	--width: 100rpx;
	--height: 100rpx;
	--size: 60rpx;
	--repeat: 4;
	--color: #FC536E;
	--radius: 20rpx;
	--rgba: rgba(255, 255, 255, 0.95);
	position: fixed;
	width: 100%;
	bottom: 0;
	z-index: 999;

	.tabbar-nav {
		display: grid;
		grid-template-columns: repeat(var(--repeat), 1fr);

		.tabbar-nav-a {
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: flex-end;
			padding: 4rpx 0;
			position: relative;
			height: var(--height);
			background-color: var(--rgba);
			box-sizing: border-box;
			filter: drop-shadow(2rpx -2rpx 2rpx rgba(0, 0, 0, 0.1));
			font-size: 24rpx;

			&:nth-child(1) {
				border-top-left-radius: var(--radius);
			}

			&:nth-last-child(1) {
				border-top-right-radius: var(--radius);
			}
		}

		.after {
			background: radial-gradient(circle at center top, transparent var(--size), var(--rgba) 0)center top;

			.after-icon {
				width: var(--width);
				height: var(--height);
				background-color: var(--color);
				border-radius: 50%;
				position: absolute;
				left: 50%;
				top: 0;
				transform: translate(-50%, -50%);
				display: flex;
				align-items: center;
				justify-content: center;
			}

			text {
				color: var(--color);
			}
		}
	}

	.safeZone {
		background-color: var(--rgba);
	}
}
</style>

3.4 css 核心代码:

background: radial-gradient(circle at center top, transparent var(--size), var(--rgba) 0)center top;凹凸透明显示,最主要就是靠这行样式代码。

--repeat: 4;:控制 tabbar 的数量


3.5 vue2 代码编写:

<script>
export default {
	name: "Tabbar",
	props: {
		pageIndex: {
			type: Number,
			default: 0,
		}
	},
	data() {
		return {
			tabbarHeight: '',
			safeAreaInsetsBottom: '',
			tabbar: [{
				iconPath: "heart",
				selectedIconPath: "heart-filled",
				text: "爱吧",
				pagePath: "/pages/index/index"
			},
			{
				iconPath: "wallet",
				selectedIconPath: "wallet-filled",
				text: "记账",
				pagePath: "/pages/bookkeeping/bookkeeping"

			},
			{
				iconPath: "chat",
				selectedIconPath: "chat-filled",
				text: "社区",
				pagePath: "/pages/community/community"

			},
			{
				iconPath: "person",
				selectedIconPath: "person-filled",
				text: "我",
				pagePath: "/pages/my/my"
			}]
		}
	},
	created() {
		uni.hideTabBar({ animation: true })
		let res = uni.getWindowInfo().safeAreaInsets.bottom + 50
		this.safeAreaInsetsBottom = res + "rpx";
		this.tabbarHeight = res * 2 + "rpx"
	},
	methods: {
		tapTabbar(val, index) {
			uni.switchTab({
				url: val.pagePath
			})
		}
	}

}
</script>

3.6 vue3 代码编写:

<script setup>
	defineOptions({
		name: "Tabbar"
	});
	const tabbarHeight = ref('')
	const safeAreaInsetsBottom = ref(0)
	
	const props = defineProps({
		pageIndex: {
			type: Number,
			default: 0
		}
	});
	const tabbar = reactive([{
			iconPath: "heart",
			selectedIconPath: "heart-filled",
			text: "爱吧",
			pagePath: "/pages/index/index"
		},
		{
			iconPath: "wallet",
			selectedIconPath: "wallet-filled",
			text: "记账",
			pagePath: "/pages/bookkeeping/bookkeeping"

		},
		{
			iconPath: "chat",
			selectedIconPath: "chat-filled",
			text: "社区",
			pagePath: "/pages/community/community"

		},
		{
			iconPath: "person",
			selectedIconPath: "person-filled",
			text: "我",
			pagePath: "/pages/my/my"
		}

	])
	onMounted(() => {
		uni.hideTabBar({ animation: true })
		let res = uni.getWindowInfo().safeAreaInsets.bottom
		safeAreaInsetsBottom.value = res * 2 + "rpx";
		tabbarHeight.value = res + 50 * 2 + "rpx";
	});
	const tapTabbar = (val, index) => {
		uni.switchTab({
			url: val.pagePath
		})
	}
</script>

3.7 vue2/3 核心代码:

uni.hideTabBar({ animation: true })  :隐藏默认的 tabBar 。

let res = uni.getWindowInfo().safeAreaInsets.bottom :获取底部安全区域的高度,返回px单位。

this.safeAreaInsetsBottom = res * 2 + "rpx":将高度 px单位 转为 rpx单位。

this.tabbarHeight = res + 50 * 2 + "rpx" :(底部安全区域高度 + 导航栏自身高度)转 rpx单位。


3.8 Tabbar 组件的使用:

:pageIndex="0":用于控制激活状态,高亮显示。


4. vue2 完整代码

<template>
	<view class="tabbar" :style="`--repeat: ${tabbar.length}`">
		<view class="tabbar-nav">
			<view class="tabbar-nav-a" v-for="(item, index) in tabbar" :key="index"
				:class="pageIndex == index ? 'after' : ''" 
				@tap="tapTabbar(item, index)">
				<view class="after-icon" v-if="pageIndex == index">
					<uni-icons size="60rpx" color="#fff" 
					:type="item.selectedIconPath" ></uni-icons>
				</view>
				<uni-icons :type="item.iconPath" size="48rpx" v-else></uni-icons>
				<text class="text">{{ item.text }}</text>
			</view>
		</view>
		<view class="safeZone" :style="{ 'height': safeAreaInsetsBottom }"></view>
	</view>
	<view :style="{ 'height': tabbarHeight }"></view>
</template>
<script>
export default {
	name: "Tabbar",
	props: {
		pageIndex: {
			type: Number,
			default: 0,
		}
	},
	data() {
		return {
			tabbarHeight: '',
			safeAreaInsetsBottom: '',
			tabbar: [{
				iconPath: "heart",
				selectedIconPath: "heart-filled",
				text: "爱吧",
				pagePath: "/pages/index/index"
			},
			{
				iconPath: "wallet",
				selectedIconPath: "wallet-filled",
				text: "记账",
				pagePath: "/pages/bookkeeping/bookkeeping"

			},
			{
				iconPath: "chat",
				selectedIconPath: "chat-filled",
				text: "社区",
				pagePath: "/pages/community/community"

			},
			{
				iconPath: "person",
				selectedIconPath: "person-filled",
				text: "我",
				pagePath: "/pages/my/my"
			}]
		}
	},
	created() {
		uni.hideTabBar({ animation: true })
		let res = uni.getWindowInfo().safeAreaInsets.bottom
		this.safeAreaInsetsBottom = res * 2 + "rpx";
		this.tabbarHeight = res + 50 * 2 + "rpx"
	},
	methods: {
		tapTabbar(val, index) {
			uni.switchTab({
				url: val.pagePath
			})
		}
	}

}
</script>
<style lang="scss" scoped>
.tabbar {
	--width: 100rpx;
	--height: 100rpx;
	--size: 60rpx;
	--repeat: 4;
	--color: #FC536E;
	--radius: 20rpx;
	--rgba: rgba(255, 255, 255, 0.95);
	position: fixed;
	width: 100%;
	bottom: 0;
	z-index: 999;

	.tabbar-nav {
		display: grid;
		grid-template-columns: repeat(var(--repeat), 1fr);

		.tabbar-nav-a {
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: flex-end;
			padding: 4rpx 0;
			position: relative;
			height: var(--height);
			background-color: var(--rgba);
			box-sizing: border-box;
			filter: drop-shadow(2rpx -2rpx 2rpx rgba(0, 0, 0, 0.1));
			font-size: 24rpx;

			&:nth-child(1) {
				border-top-left-radius: var(--radius);
			}

			&:nth-last-child(1) {
				border-top-right-radius: var(--radius);
			}
		}

		.after {
			background: radial-gradient(circle at center top, 
			transparent var(--size), var(--rgba) 0)center top;

			.after-icon {
				width: var(--width);
				height: var(--height);
				background-color: var(--color);
				border-radius: 50%;
				position: absolute;
				left: 50%;
				top: 0;
				transform: translate(-50%, -50%);
				display: flex;
				align-items: center;
				justify-content: center;
			}

			text {
				color: var(--color);
			}
		}
	}

	.safeZone {
		background-color: var(--rgba);
	}
}
</style>

5. vue3 完整代码

<template>
	<view class="tabbar" :style="`--repeat: ${tabbar.length}`">
		<view class="tabbar-nav">
			<view class="tabbar-nav-a" v-for="(item, index) in tabbar" :key="index"
				:class="pageIndex == index ? 'after' : ''" 
				@tap="tapTabbar(item, index)">
				<view class="after-icon" v-if="pageIndex == index">
					<uni-icons size="60rpx" color="#fff" 
					:type="item.selectedIconPath">	
					</uni-icons>
				</view>
				<uni-icons :type="item.iconPath" size="48rpx" v-else></uni-icons>
				<text class="text">{{ item.text }}</text>
			</view>
		</view>
		<view class="safeZone" :style="{ 'height': safeAreaInsetsBottom }"></view>
	</view>
	<view :style="{ 'height': tabbarHeight }"></view>
</template>
<script setup>
	defineOptions({
		name: "Tabbar"
	});
	const tabbarHeight = ref('')
	const safeAreaInsetsBottom = ref(0)
	
	const props = defineProps({
		pageIndex: {
			type: Number,
			default: 0
		}
	});
	const tabbar = reactive([{
			iconPath: "heart",
			selectedIconPath: "heart-filled",
			text: "爱吧",
			pagePath: "/pages/index/index"
		},
		{
			iconPath: "wallet",
			selectedIconPath: "wallet-filled",
			text: "记账",
			pagePath: "/pages/bookkeeping/bookkeeping"

		},
		{
			iconPath: "chat",
			selectedIconPath: "chat-filled",
			text: "社区",
			pagePath: "/pages/community/community"

		},
		{
			iconPath: "person",
			selectedIconPath: "person-filled",
			text: "我",
			pagePath: "/pages/my/my"
		}

	])
	onMounted(() => {
		uni.hideTabBar({ animation: true })
		let res = uni.getWindowInfo().safeAreaInsets.bottom
		safeAreaInsetsBottom.value = res * 2 + "rpx";
		tabbarHeight.value = res + 50 * 2 + "rpx";
	});
	const tapTabbar = (val, index) => {
		uni.switchTab({
			url: val.pagePath
		})
	}
</script>
<style lang="scss" scoped>
.tabbar {
	--width: 100rpx;
	--height: 100rpx;
	--size: 60rpx;
	--repeat: 4;
	--color: #FC536E;
	--radius: 20rpx;
	--rgba: rgba(255, 255, 255, 0.95);
	position: fixed;
	width: 100%;
	bottom: 0;
	z-index: 999;

	.tabbar-nav {
		display: grid;
		grid-template-columns: repeat(var(--repeat), 1fr);

		.tabbar-nav-a {
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: flex-end;
			padding: 4rpx 0;
			position: relative;
			height: var(--height);
			background-color: var(--rgba);
			box-sizing: border-box;
			filter: drop-shadow(2rpx -2rpx 2rpx rgba(0, 0, 0, 0.1));
			font-size: 24rpx;

			&:nth-child(1) {
				border-top-left-radius: var(--radius);
			}

			&:nth-last-child(1) {
				border-top-right-radius: var(--radius);
			}
		}

		.after {
			background: radial-gradient(circle at center top, 
			transparent var(--size), 
			var(--rgba) 0)center top;

			.after-icon {
				width: var(--width);
				height: var(--height);
				background-color: var(--color);
				border-radius: 50%;
				position: absolute;
				left: 50%;
				top: 0;
				transform: translate(-50%, -50%);
				display: flex;
				align-items: center;
				justify-content: center;
			}

			text {
				color: var(--color);
			}
		}
	}

	.safeZone {
		background-color: var(--rgba);
	}
}
</style>

Top