uniapp点击tab标签自动滚动对应位置,且滚动页面,对应tab标签自动高亮

1.效果图

2.原理

利用uni.createSelectorQuery获取节点信息,从而获取每组商品距离顶部的距离及tab标签的高度从而实现联动滚动

3.完整代码

<template>
	<view class="wrap">
		<view class="tab_box" id="tabBox">
			<scroll-view scroll-x="true" :scroll-into-view="'tabs'+tabIndex">
				<view class="tab_item skeleton-rect" :id="'tabs'+index" :class="{'active':tabIndex==index}"
					v-for="(item,index) in tabList" @click="changeTab(index)" :key="index">
					{{item.name}}
				</view>
			</scroll-view>
		</view>
		<view class="goods_class" :id="'wrap'+index" v-for="(item,index) in goodsList" :key="index">
			<view class="goods_box">
				<view class="title_box">
					{{item.title}}
				</view>
				<view class="goods_list" v-if="item.good_item.length">
					<view class="goods_item goods_item_inline" v-for="(i,j) in item.good_item" :key="j"
						@click="$util.JumpPath(`/pages/goods/detail?id=${i.id}`)">
						<view class="goods_img">
							<!-- <image :src="i.slider[0]" mode="aspectFill"></image> -->
						</view>
						<view class="goods_title">
							{{i.title}}
						</view>
					</view>
				</view>
			</view>
		</view>

	</view>
</template>

<script>
	export default {
		data() {
			return {
				tabIndex: 0, //选择的tab索引
				tabList: [], //tab标签列表
				tabHeight: 0, //tab的整体高度
				goodsList: [], //商品列表
				scrollWarp: [], //点击tab,页面需要滚动的距离集合
				scrollInto: 0, //点击tab,页面自动滚动的距离
				lock: false, //点击tab,防止与页面滚动产生冲突导致tab高亮频闪的标识
			}
		},
		onLoad() {
			this.initData() //初始化数据
		},
		onPageScroll(e) {
			this.scrollSetTabIndex(e.scrollTop)
		},
		methods: {
            //模拟的数据函数
			initData() {
				let data = []
				for (let i = 0; i < 6; i++) {
					data.push({
						name: '测试',
						id: ''
					})
				}
				// 模拟顶部tab数据
				this.tabList = data.map((item, index) => {

					return {
						name: item.name + index,
						id: index
					}
				})
				// 模拟商品数据
				this.goodsList = this.tabList.map((item, index) => {
					let goodItem = {
						title: item.name,
						good_item: [{
							title: '商品' + item.name
						}, {
							title: '商品' + item.name
						}, {
							title: '商品' + item.name
						}, {
							title: '商品' + item.name
						}]
					}
					return goodItem
				})
				
				this.$nextTick(()=>{
					this.GetRect("#tabBox").then(res => {
						this.tabHeight = res.height
						this.barInit()
						console.log(this.tabHeight,"tab高度");
					})
					
				})


			},
			changeTab(e) {
				this.tabIndex = e
				this.$set(this, 'lock', true);
				this.scrollInto = this.scrollWarp[e]
				this.scrollTo()

			},
			// 滚动自动激活tab标签
			scrollSetTabIndex(top) {
				if (this.lock) {
					// 防止点击tab标签时和页面滚动造成冲突导致tab标签的选中状态会不停闪烁
					setTimeout(() => {
						this.$set(this, 'lock', false);
					}, 700)
					return;
				}
				if (top <= this.scrollWarp[0]) {
					this.tabIndex = 0
				} else if (top >= this.scrollWarp[this.scrollWarp.length - 1]) {
					this.tabIndex = this.scrollWarp.length - 1
				} else {
					for (let i = 0; i < this.scrollWarp.length - 1; i++) {
						if (top >= this.scrollWarp[i] && top < this.scrollWarp[i + 1]) {
							this.tabIndex = i
						}
					}
				}
			},
			scrollTo() {
				uni.pageScrollTo({
					scrollTop: this.scrollInto
				})
			},
			//获取节点信息
			GetRect(selector) {
				return new Promise((resolve, reject) => {
					let view = uni.createSelectorQuery().in(this)
					view.select(selector).boundingClientRect(rect => {
						resolve(rect)
					}).exec();
				})
			},
			//获取节点距离顶部距离
			barInit: async function(index) {
				let navTargetTop1 = [];

				let randoms = this.tabHeight; //顶部栏高度+tab高度+兼容值	(注,如果navbar是自定义的,还需要获取navbar的高度并减去此高度)
				console.log(randoms,"--------------------");
				for (let i = 0; i < this.goodsList.length; i++) {
					this.GetRect("#wrap" + i).then(res => {
						navTargetTop1.push(parseInt(res.top) - randoms) //滚动的距离:元素距离顶部的距离-tab标签的高度
						console.log(res.top,randoms,"----------");
					})
				}
				this.scrollWarp = navTargetTop1;
				console.log(this.scrollWarp, "需要滚动的合集");
			},
		},


	}
</script>

<style lang="scss" scoped>
	.goods_class {
		margin-bottom: 50rpx;

		.goods_box {
			margin-bottom: 50rpx;
			padding: 0 20rpx;

			.title_box {
				font-size: 40rpx;
				font-weight: 600;
				padding-top: 10rpx;
				margin-bottom: 20rpx;
			}

			.goods_list {
				display: grid;
				grid-template-columns: repeat(2, 1fr);
				gap: 20rpx;

				.goods_item_out {
					width: 270rpx;
					display: inline-block;
					vertical-align: top;
				}

				.scroll-view_H {
					white-space: nowrap;
					width: 100%;
				}

				.goods_item_inline {
					display: inline-block;
				}

				.goods_item {
					// width: 270rpx;
					overflow: hidden;
					vertical-align: top;
					.goods_img {
						width: 100%;
						height: 270rpx;
						background: #303030;

						image {
							width: 100%;
							height: 100%;
						}
					}

					.goods_title {
						margin: 10rpx 0;
						white-space: normal;
						width: 100%;
						max-height: 76rpx;
						line-height: 38rpx;
					}

					.goods_title,
					.goods_price {
						font-size: 26rpx;
						color: #303030;
					}
				}
			}
		}

		.more_btn {
			width: 252rpx;
			height: 82rpx;
			margin: 0 auto;
			background: var(--view-theme);

			.in_btn {
				width: 236rpx;
				height: 70rpx;
				line-height: 70rpx;
				border: 1px solid #FFFFFF;
				text-align: center;
				font-size: 28rpx;
				color: #fff;
			}
		}

		.scroll_box {
			position: relative;
			width: 250rpx;
			height: 3rpx;
			background: #CCCCCC;
			margin: 0 auto 37rpx;

			.scroll_icon {
				/* 初始位置 */
				transform: translateX(0px);
				width: 60rpx;
				height: 3rpx;
				background: #383838;
			}
		}
	}

	.tab_box {
		position: sticky;
		z-index: 999;
		top: 0; //动态设置
		background: #fff;
		padding: 0 35rpx;
		// height: 80rpx;
		line-height: 80rpx;
		// margin-bottom: 20rpx;
		padding-bottom: 20rpx;
		white-space: nowrap;

		.tab_item {
			display: inline-block;
			position: relative;
			height: 100%;
			font-size: 34rpx;
			color: #303030;

			&:not(:last-child) {
				margin-right: 40rpx;
			}
		}

		.active {
			font-weight: 700;
			color: red;
		}

		.active::after {
			position: absolute;
			left: 0;
			bottom: 0;
			content: '';
			width: 100%;
			height: 2rpx;
			background: red;
		}
	}
</style>

### 实现 UniApp 中带滑条效果的可滑动 Tab 标签切换组件 为了实现在 UniApp 中带有滑条效果的可滑动 Tab 标签切换功能,可以借鉴 CustomSlider 组件的设计思路[^1]。具体来说,在此场景中需要创建一个自定义 Vue 组件用于表示 Tab 控件,并利用 CSS 和 JavaScript 来增强用户体验。 #### 自定义 Tab 切换逻辑 首先构建基础结构: ```html <template> <view class="tabs"> <!-- Tabs --> <scroll-view scroll-x style="white-space: nowrap;"> <block v-for="(tab, index) in tabs" :key="index"> <text @click="switchTab(index)" :class="{ active: currentIndex === index }">{{ tab }}</text> </block> </scroll-view> <!-- Slider --> <view class="slider" :style="{ transform: `translateX(${currentIndex * sliderWidth}px)` }"></view> <!-- Content Area --> <component :is="currentComponent"></component> </view> </template> ``` 这里使用了 `<scroll-view>` 提供水平滚动能力,而每个 Tab点击事件绑定至 `switchTab` 方法上;同时动态计算滑块位置以匹配当前选中的 Tab。 #### 数据与样式管理 接着配置数据属性以及相应的方法: ```javascript <script> export default { data() { return { tabs: ['首页', '分类', '购物车', '我的'], currentComponent: null, currentIndex: 0, sliderWidth: 80 // 假设每个 Tab 宽度固定为 80px }; }, methods: { switchTab(index) { this.currentIndex = index; this.currentComponent = () => import(`./components/${this.tabs[index]}.vue`); } }, mounted() { this.switchTab(0); } }; </script> ``` 上述代码片段展示了如何初始化默认显示的第一个 Tab 及其对应的内容区域,并每当用户选择不同 Tab 时更新状态变量 `currentIndex` 和加载相应的子组件。 对于视觉上的滑动指示器,则可以通过简单的 CSS 进行设置: ```css <style scoped> .tabs text.active { color: red; } .slider { height: 3px; background-color: blue; transition: all .3s ease-in-out; } </style> ``` 这段样式规则使得被激活项的文字颜色变为红色,并给底部添加了一个蓝色的小横线作为高亮标记,它会随着用户的操作平滑过渡到新的目标位置。 #### 处理视图尺寸变化 考虑到实际应用过程中可能会存在屏幕旋转或其他因素引起布局调整的情况,因此还需要监听窗口大小改变事件以便及时修正滑块宽度参数: ```javascript mounted() { window.addEventListener('resize', this.updateSliderWidth); const container = uni.createSelectorQuery().select('.tabs'); container.boundingClientRect((rect) => { if (rect.width !== undefined && rect.width > 0) { this.sliderWidth = Math.floor(rect.width / this.tabs.length); } }).exec(); this.switchTab(0); }, beforeDestroy() { window.removeEventListener('resize', this.updateSliderWidth); }, methods: { updateSliderWidth() { const container = uni.createSelectorQuery().select('.tabs'); container.boundingClientRect((rect) => { if (rect.width !== undefined && rect.width > 0) { this.sliderWidth = Math.floor(rect.width / this.tabs.length); } }).exec(); } } ``` 以上部分实现了对容器宽度测量的功能,并据此重新分配各个 Tab 所占的空间比例,从而确保无论何时都能获得最佳展示效果[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值