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

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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容