Taro实现 微信小程序 可左右滑动切换的Tab组件

在这里插入图片描述

左右滑动切换Tab

一、文件组成:

  • BgTitleTouchGroup.vue
  • BgTitleTouchGroup.less
  • BgTitleTouchItem.vue
  • BgTitleTouchItem.less
  • announcement.vue
1、BgTitleTouchGroup.vue
<template>
	<!-- 可以滑屏切换的 tabGroup -->
	<view class="tab-group">
		<!-- 标题列表 -->
		<scroll-view
			id="bgTitleTouchGroup"
			:scroll-x="true"
			:scroll-into-view="`bgg${activeIndex}`"
			:scroll-with-animation="true"
			class="bg-title-list flex f-ai-c"
			:class="[align]"
		>
			<view
				v-for="(item, index) in newTabList"
				:id="`bgg${index}`"
				:key="index"
				class="tab-item-box"
			>
				<BgTitle
					v-if="item.active"
					:title="item.title"
					:fontSize="30"
				/>
				<text
					v-else
					class="normal"
					:style="{
						minWidth: normalMinWidth
					}"
					@tap="clickHandle(index)"
				>
					{{ item.title }}
				</text>
			</view>
		</scroll-view>
		<!-- </view> -->
		<!-- 内容容器 -->
		<view
			class="content"
			:style="{'transition-duration': `${duration}s`, transform: `translateX(${xPositon}%)`}"
			@touchstart="onTouchStart"
			@touchmove="onTouchMove"
			@touchend="onTouchEnd"
		>
			<view class="tabs__track flex">
				<slot />
			</view>
		</view>
	</view>
</template>

<script>
import './BgTitleTouchGroup.less';

import BgTitle from '../BgTitle/index.vue';
import { getDirection, resetTouchStatus } from '@/combination';

export default {
	name: 'BgTitleTouchGroup',
	components: { BgTitle },
	inject: {
		normalMinWidth: {
			from: 'normalMinWidth',
			// default: '110rpx'
			default: 'auto'
		}
	},
	props: {
		// 过度动画的时间
		duration: {
			type: Number,
			default: 0.2
		},
		align: {
			type: String,
			default: 'f-jc-sb'
		}
	},
	data () {
		return {
			activeIndex: 0, // 当前查看的tab的索引
			newTabList: [],
			xPositon: 0, // 容器在X轴移动的距离
			swipeable: true,
			swiping: false,
			direction: '',
			deltaX: 0,
			deltaY: 0,
			offsetX: 0,
			offsetY: 0,
			startX: 0,
			startY: 0
		};
	},
	created () {
		this.initTabList();
	},
	methods: {
		setActiveIndex (index) {
			this.activeIndex = index;
			for (let i = 0; i < this.newTabList.length; i++) {
				if (i === index) {
					this.newTabList[i].active = true;
				} else this.newTabList[i].active = false;
			}
		},
		initTabList () {
			let slots = this.$slots.default();
			// for循环方式添加的 tabItem, 反之则为逐个写入的tabItem
			if (slots.length === 1 && typeof slots[0].type === 'symbol') {
				slots = slots[0].children;
			}
			for (let i = 0; i < slots.length; i++) {
				this.newTabList.push({
					title: slots[i].props.title,
					active: !i
				});
			}
		},
		clickHandle (index) {
			for (let i = 0; i < this.newTabList.length; i++) {
				if (i === index) this.newTabList[i].active = true;
				else this.newTabList[i].active = false;
			}
			this.xPositon = -100 * index;
			this.activeIndex = index;
			this.$emit('bgTitleClick', index);
		},
		touchStart (event) {
			resetTouchStatus(this);
			var touch = event.touches[0];
			this.startX = touch.clientX;
			this.startY = touch.clientY;
		},
		touchMove (event) {
			var touch = event.touches[0];
			this.deltaX = touch.clientX - this.startX;
			this.deltaY = touch.clientY - this.startY;
			this.offsetX = Math.abs(this.deltaX);
			this.offsetY = Math.abs(this.deltaY);
			this.direction = this.direction || getDirection(this.offsetX, this.offsetY);
		},
		onTouchStart (event) {
			if (!this.swipeable) { return; }
			this.swiping = true;
			this.touchStart(event);
		},
		onTouchMove (event) {
			if (!this.swipeable || !this.swiping) { return; }
			this.touchMove(event);
		},
		// watch swipe touch end
		onTouchEnd () {
			if (!this.swipeable || !this.swiping) { return; }
			var _a = this; var direction = _a.direction; var deltaX = _a.deltaX; var offsetX = _a.offsetX;
			var minSwipeDistance = 50;
			if (direction === 'horizontal' && offsetX >= minSwipeDistance) {
				this.activeIndex = this.getAvaiableTab(deltaX);
				const list = this.newTabList.map((l, ind) => {
					l.active = ind === this.activeIndex;
					return l;
				});
				this.newTabList = list;
				this.xPositon = -100 * this.activeIndex;
				this.$emit('bgTitleClick', this.activeIndex);
			}
			this.swiping = false;
		},
		getAvaiableTab (direction) {
			var _a = this;
			var tabs = _a.newTabList;
			var currentIndex = _a.activeIndex;
			var step = direction > 0 ? -1 : 1;
			for (var i = step; currentIndex + i < tabs.length && currentIndex + i >= 0; i += step) {
				var index = currentIndex + i;
				if (index >= 0 && index < tabs.length && tabs[index] && !tabs[index].active) {
					return index;
				}
			}
			return 0;
		}
	}
};
</script>
2、BgTitleTouchGroup.less
.tab-group {
	width: 100%;
	overflow: hidden;
	.bg-title-list {
		white-space: nowrap;
		.normal {
			display: inline-block;
			font-size: 26rpx;
		}
		.tab-item-box {
			display: inline-block;
			margin-right: 61rpx;
		}
		.tab-item-box:last-child {
			margin-right:0;
		}
	}
	.content {
		.tabs__track {
			position: relative;
			width: 100%;
			height: 100%;
			will-change: left;
		}
	}
}
3、BgTitleTouchItem.vue
<template>
	<!-- 可以滑屏切换的 tabItem -->
	<view class="tab-item">
		<slot />
	</view>
</template>

<script>
import './index.less';

export default {
	name: 'BgTitleTouchItem',
	props: {
		title: {
			type: String,
			default: '标题1'
		}
	}
};
</script>
4、BgTitleTouchItem.less
.tab-item {
	width: 100%;
	flex-shrink: 0;
    box-sizing: border-box;
}
.tab-item_inactive {
	height: 0;
    overflow: visible;
}
5、announcement.vue
<template>
	<view>
		<BgTitleTouchGroup
			v-if="tabList.length"
			:align="'f-jc-fs'"
			@bgTitleClick="bgTitleClickHandle"
		>
			<BgTitleTouchItem
				v-for="(ite, ind) in tabList"
				:key="ite.id"
				:title="ite.typeName"
			>
				<view v-if="activeTypeIndex === ind">
					<scroll-view
						v-if="recodes.length"
						:scroll-y="true"
						class="announs"
						@scrolltolower="onTolowerMixin(getRecodeList)"
							>
						<CommonListItem
							v-for="item in recodes"
							:key="item.id"
							:record="item"
							style="margin-bottom: 30rpx;"
							@equiClick="clickHandle(item.id)"
						/>
					</scroll-view>
					<NoData
						v-else
						style="margin-top: 20rpx;"
					/>
				</view>
			</BgTitleTouchItem>
		</BgTitleTouchGroup>
		<NoData
			v-else
			style="margin-top: 20rpx;"
		/>
	</view>
</template>
<script>
	import {
		BgTitleTouchGroup,
		BgTitleTouchItem,
		CommonListItem,
		NoData
	} from '@/components';
export default {
	name: 'Announcement',
	components: {
		BgTitleTouchGroup,
		BgTitleTouchItem,
		CommonListItem,
		NoData
	},
	data () {
		return {
			tabList: [],
			recodes: [],
			activeTypeId: '',
			activeTypeIndex: 0
		};
	},
	methods: {
		// 某个类型title点击
		bgTitleClickHandle (index) {
			this.activeTypeId = this.tabList[index].id;
			this.activeTypeIndex = index;
			this.initRecords();
			this.getRecodeList();
		},
	}
};
</script>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值