uniapp 实现tabbar分类导航及滚动联动效果

思路:使用两个scroll-view,tabbar分类导航使用scrollleft移动,内容联动使用页面滚动onPageScroll监听滚动高度

效果图

html 复制代码
<template>
	<view class="content" >
		<view :class="[isSticky ? 'tab-sticky': '']">
			<view class="base-combox" >
				<uni-icons type="location-filled" color="#85D8CE" size="25" />
				<text class="tag-text">{{base[0].name}}</text>
			</view>
			<view class="u-tab" :class="[!isSticky ? 'tab-sticky': '']">
				<scroll-view scroll-x="true" scroll-with-animation="true" class="u-tab-view menu-scroll-view" :scroll-left="scrollLeft">
					<view v-for="(item,index) in tabbar" :key="index" class="u-tab-item" :class="[isActive == index ? 'u-tab-item-active' : '']"
					 @tap.stop="swichMenu(index)">
						<view class="u-line-1">{{item.name}}</view>
					</view>
				</scroll-view>
			</view>
			
		</view>
		
		<scroll-view :scroll-top="scrollTop" scroll-y scroll-with-animation class="right-box" @scroll="upScroll">
			<view class="page-view">
				<view class="class-item" :id="'item' + index" v-for="(item , index) in tabbar" :key="index">
					<view class="item-title">
						<text>{{item.name}}</text>
					</view>
					<view class="item-container">
						<view class="thumb-box" v-for="(item1, index1) in item.children" :key="index1" @tap="goList(item1)">
							<image v-if="item1.icon != ''" class="item-menu-image" :src="item1.icon" mode=""></image>
							<view v-else class="item-menu-image row-c" style="background-color: #F4F6F8;"><text style="font-size: 20rpx;color: #d0d0d0;">加载失败</text></view>
							<view class="item-menu-name">{{item1.name}}</view>
						</view>
					</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	export default {
		components: {
			
		},
		data() {
			return {
				moduleData: [],
				base:[{name:"请选择地点",id:'0'}],
				showType:'horizontally', //vertically,horizontally
				menuIcon:'../../static/list_h.png',//../../static/list_v.png
				tabbar: [],
				isActive : 0,
				scrollLeft: 0, // 横向滚动条位置
				scrollTop: 0,
				baseComboxH: 0,   // 地址区的高度
				tabbarScrollW: 0, // 导航区宽度
				tabbarScrollH: 0, // 导航区高度
				isSticky: true, // 是否吸顶
				timer: null // 定时器
			}
		},
		onLoad(option) {
			this.tabbar = [{id:1,name:"菜单一",children:[{id:11,name:"子菜单一"},{id:12,name:"子菜单二"}]},
			{id:2,name:"菜单二",children:[{id:11,name:"子菜单一子菜单一子菜单一子菜单一",icon:"/static/bq2.png"},{id:12,name:"子菜单二"},{id:13,name:"子菜单三"},{id:14,name:"子菜单四"},{id:15,name:"子菜单五"},{id:16,name:"子菜单六"}]},
			{id:3,name:"菜单三",children:[{id:21,name:"子菜单一"},{id:22,name:"子菜单二"}]},
			{id:3,name:"菜单四",children:[{id:31,name:"子菜单一"},{id:32,name:"子菜单二"}]},
			{id:5,name:"菜单五",children:[{id:41,name:"子菜单一"},{id:42,name:"子菜单二"}]},
			{id:6,name:"菜单六",children:[{id:51,name:"子菜单一"},{id:52,name:"子菜单二"}]}]
			
		},
		onReady() {
			
		},
		mounted(){
			this.getScrollW();
		},
		onPageScroll(e){
			//console.log(e,e.scrollTop)
			if(this.timer){
				clearTimeout(this.timer)
			}
			this.timer = setTimeout(() => { // 节流
				this.timer = null;
				// scrollHeight为右边菜单垂直中点位置
				// let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
				// scrollHeight为右边菜单头部位置
				let scrollHeight = e.scrollTop + this.tabbarScrollH + this.baseComboxH + 26;
				let len = this.tabbar.length
				for (let i = 0; i < len-1; i++) {
					let height1 = this.tabbar[i].top;
					let height2 = this.tabbar[i + 1].top;
					// console.log(height2,scrollHeight,height1)
					if (scrollHeight >= height1 && scrollHeight < height2) {
						this.tabbarStatus(i);
						return ;
					}
				}
				if(scrollHeight >= this.tabbar[len-1].top){
					this.tabbarStatus(len-1);
					return ;
				}
			}, 10)
		},
		methods: {
			goList(value) {
				var item = {title:value.title,labelName:value.labelName}
				uni.navigateTo({
					url:value.url+'?item='+encodeURIComponent(JSON.stringify(item))
				})
			},
			
			/**
			* 点击上方的tab切换
			* @index 传入的 ID
			*/
			async swichMenu(index) {
				if (index == this.isActive ) return;
				//this.scrollLeft = 0;
				this.$nextTick(function(){
					//for (let i = 0; i < index - 1; i++) {
						//this.scrollLeft += this.tabbar[i].width
					//};
					// this.isActive = index;
					// // 效果三(当前点击子元素居中展示)  不受子元素宽度影响
					// this.scrollLeft = this.tabbar[index].left - this.tabbarScrollW / 2 + this.tabbar[index].width / 2;
					this.tabbarStatus(index);
					//this.scrollTop = this.tabbar[index].top
					uni.pageScrollTo({
					    duration:200, // 毫秒
						scrollTop: (this.tabbar[index].top - this.tabbarScrollH - this.baseComboxH - 18) // 位置
					});
					// console.log(this.scrollLeft,this.scrollTop)
				})
			},
			// 获取标题区域宽度,和每个子元素节点的宽度以及元素距离左边栏的距离
			getScrollW() {
				const query = uni.createSelectorQuery().in(this);
				query.select('.u-tab-view').boundingClientRect(data => {
					  // 拿到 scroll-view 组件宽度高度
					  this.tabbarScrollW = data.width
					  this.tabbarScrollH = data.height
				 }).exec();
				 query.select('.base-combox').boundingClientRect(data => {
				 	  // 拿到 base-combox 高度
				 	  this.baseComboxH = data.height
				  }).exec();
				 
				query.selectAll('.u-tab-item').boundingClientRect(data => {
					 let dataLen = data.length;
					  for (let i = 0; i < dataLen; i++) {
						  //  scroll-view 子元素组件距离左边栏的距离
						  this.tabbar[i].left = data[i].left;
						 //  scroll-view 子元素组件宽度
						 this.tabbar[i].width = data[i].width
					}
				}).exec();
				query.selectAll('.class-item').boundingClientRect(data => {
					 let dataLen = data.length;
					  for (let i = 0; i < dataLen; i++) {
						  //  scroll-view 子元素组件距离上边栏的距离
						  this.tabbar[i].top = data[i].top;
					}
				}).exec()
			
			},
			upScroll(e){
				if(e.detail.scrollTop>50){
					this.isSticky = true
				}else{
					this.isSticky = false
				}
				console.log(e.detail.scrollTop)
			},
			/**
			* 设置上方菜单的滚动状态
			* @index 传入的 ID
			*/
			async tabbarStatus(index) {
				this.isActive = index;
				// 效果三(当前点击子元素居中展示)  不受子元素宽度影响
				this.scrollLeft = this.tabbar[index].left - this.tabbarScrollW / 2 + this.tabbar[index].width / 2;
			}
			
		}
	}
</script>

<style scoped>
	page{
		background-color: #fafafa !important;
		display: block;
		/* overflow: hidden; */
	}
	
	.content {
	    min-height: 100vh;
		display: flex;
		flex-direction: column;
	}
	
	.base-combox{
		align-items: center;
		justify-content: center;
		height: 50rpx;
	}
	
	.u-tab-item-active {
		position: relative;
		color: #000;
		font-size: 16px;
		font-weight: 600;
	}
	
	.u-tab-item-active::after {
		content: ''; // 必须
		display: block;
		width: 30px;
		height: 4px;
		background-color: #000;
		margin: 0 auto;
		border-radius: 10px;
	}
	
	.u-tab{
		border-bottom: 1rpx solid #f2f2f2;
		background-color: #ffffff;
		z-index: 99;
		width: 100%;
		align-items: center;
		height: 100rpx;
		margin-top: 10px;
	}
	
	.u-tab-view{
		box-sizing: border-box;
		padding-left: 30rpx;
		padding-right: 30rpx;
		width: 100%;
		white-space: nowrap;
	}
	.u-tab-item{
		display: inline-block;
		box-sizing: border-box;
		line-height: 60rpx;
		margin-right: 35rpx;
		font-size: 16px;
		padding-top: 10px;
	},
	.u-line-1{
		padding-bottom: 7px;
	}
	
	/* 隐藏scroll-view滚动条 */
	/deep/::-webkit-scrollbar{
		display: none;
	}
	uni-scroll-view .uni-scroll-view::-webkit-scrollbar {
		display: none
	}
	
	.right-box{
		/* height: 100vh; */
	}

	.page-view {
		padding: 16rpx;
		flex-direction: column;
	}
	
	.class-item {
		display: block;
		margin-bottom: 30rpx;
		background-color: #fff;
		padding: 16rpx;
		border-radius: 8rpx;
	}
	
	.class-item:last-child {
		min-height: calc(100vh - 102rpx - 50rpx - 20rpx - 88rpx - 32rpx - 64rpx );
		
	}
	
	.item-title {
		font-size: 26rpx;
		color: $u-main-color; 
		font-weight: bold;
	}
	
	.item-menu-name {
		margin-top: 8rpx;
		font-weight: normal;
		font-size: 24rpx;
		color: $u-main-color;
	}
	
	.item-container {
		display: flex;
		flex-wrap: wrap;
	}
	
	.thumb-box {
		width: 25%;
		display: flex;
		align-items: center;
		justify-content: center;
		flex-direction: column;
		margin-top: 20rpx;
	}
	
	.item-menu-image {
		width: 60rpx;
		height: 60rpx;
	}
	
	.tab-sticky{
		display: flex;
		flex-direction: column;
		position: -webkit-sticky;
		position: sticky;
		top: var(--window-top);
		z-index: 99;
		background-color: #fff;
	}

</style>
相关推荐
A_ugust__18 分钟前
Vue3.2 项目打包成 Electron 桌面应用
javascript·vue.js·electron
恋猫de小郭19 分钟前
JetBrains Terminal 又发布新架构,Android Studio 将再次迎来新终端
android·前端·flutter
夕秋一梦1 小时前
vue项目本地调试使用https
前端·vue.js·https
问道飞鱼1 小时前
【Vue3知识】组件间通信的方式
开发语言·javascript·ecmascript·组件·通信
小破孩呦1 小时前
动态列表的数据渲染、新增、编辑等功能开发及数据处理
前端·javascript·elementui
成长ing121381 小时前
点击音效系统
前端·cocos creator
熟悉不过1 小时前
cesium项目之cesiumlab地形数据加载
前端·javascript·vue.js·cesium·webgis·cesiumlab
神经毒素2 小时前
WEB安全--XSS--DOM破坏
前端·web安全·xss
不简说2 小时前
sv-print可视化打印组件不完全指南③
前端·javascript·vue.js