Taro实现 微信小程序 可左右滑动切换的Tab组件
2022-10-27 本文已影响0人
R_X
image.png
左右滑动切换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>