uniapp 基于swiper 二次封装tab组件 实现可滑动切换 并且underline随滑动比例移动

uniapp 小程序可用 vue3


1.png
2.png

上代码
子组件:

<!-- tab -->
<template>
  <view class="tab">
    <view style="padding: 0 24rpx;">
    <view class="v-tabs ">
      <view
        v-for="(item, index) in tabList"
        :key="index"
        class="custom-tab"
        :class="activeTab === index?`active  active${index}`: `active${index}`"
        @click="changeTab($event,index)"
      >{{ item }}</view>
      <view class="underline" :style="{ transform: 'translateX(' + underlineTranslateX + 'px)' }"></view>
    </view>
  </view>
    <swiper
      class="swiper"
      :circular="false"
      :indicator-dots="false"
      :autoplay="false"
      @change="change"
      @transition="transition"
      :current="activeTab"
      :duration="200"
    >
      <swiper-item v-for="(item, index) in tabList" :item-id="index" :key="index">
        <slot :name="item"></slot>
      </swiper-item>
    </swiper>
  </view>
</template>

<script setup>
import { defineProps, nextTick } from "vue";
const props = defineProps({
  list: {
    type: Array
  }
});
const activeTab = ref(0);
const tabList = ref(props.list);
const underlineTranslateX = ref(0);
const widthList = ref([]);
const screenWidth = ref(0);

onMounted(async () => {
  //储存宽度
  const instance = getCurrentInstance();
  const query = uni.createSelectorQuery().in(instance);
  for (let i = 0; i < tabList.value.length; i++) {
    const data = await new Promise(resolve => {
      query
        .select(`.active${i}`)
        .boundingClientRect(resolve)
        .exec();
    });
    widthList.value.push(data.width);
    underlineTranslateX.value = widthList.value[0] / 2 - 8; //这里的8是下划线宽度的一半
    startX.value = JSON.parse(JSON.stringify(underlineTranslateX.value))
  }
  screenWidth.value = uni.getSystemInfoSync().windowWidth;
});

// 滑块位置
const changeTab = (event, index) => {
  activeTab.value = index;
  let width = 0;
  for (let i = 0; i <= index; i++) {
    width += widthList.value[i];
  }
  width -= widthList.value[index] / 2; //这里是tab宽度减去自身的1/2
  width += 32 * index - 8;
  underlineTranslateX.value = width;
  startX.value = JSON.parse(JSON.stringify(underlineTranslateX.value))
  //changeTab改变时 取消页面滑动改变
  transitionB.value = false;
  setTimeout(()=>{
    transitionB.value = true;
  },300)
};

//翻页
const change = e => {
  if (e.detail) {
    changeTab("", e.detail.current);
  }
};

//滑块随着页面翻动而移动
const startX = ref(0); // 触摸开始时的 X 坐标
const transitionB = ref(true); // 触摸开始时的 X 坐标
const transition = e => {
  if (e.detail.dx < 0 && activeTab.value != 0&&transitionB.value) {//避免第一个翻页
    //滚动长度是margin的一般 自身的一半以及 滚动的下一个目标的一半
    underlineTranslateX.value=(startX.value+((e.detail.dx/screenWidth.value)*(32+((widthList.value[activeTab.value])/2)+((widthList.value[activeTab.value-1])/2))))
  } else if (e.detail.dx > 0 && activeTab.value != tabList.value.length - 1&&transitionB.value) {
    //滚动长度是margin的一般 自身的一半以及 滚动的下一个目标的一半  右滑是加1
    underlineTranslateX.value=(startX.value+((e.detail.dx/screenWidth.value)*(32+((widthList.value[activeTab.value])/2)+((widthList.value[activeTab.value+1])/2))))
    //向右滑动
  }
};
</script>
<style lang='scss' scoped>
.v-tabs {
  display: flex;
  align-items: center;
  position: relative;
  .custom-tab {
    font-size: 32rpx;
    color: #969ba0;
    margin-right: 64rpx;
    padding: 0 2rpx; //保持放大缩小都一样
  }
  .active {
    font-family: $psm;
    font-size: 36rpx;
    color: #171b1e;
    line-height: 54rpx;
    padding: 0; //保持放大缩小都一样
  }
  .underline {
    width: 32rpx;
    height: 8rpx;
    background: #0078e6;
    border-radius: 8rpx 8rpx 8rpx 8rpx;
    position: absolute;
    bottom: -8rpx;
    left: 0;
    transition: width 0.3s, transform 0.3s;
  }

}

.tab{
  height: 100%;
}
::v-deep .swiper {
    // height: 150px;
    height: 100%;
  }
</style>

父组件

<!-- 记录 -->
<template>
  <!-- <topbar :title="'记录'" /> -->
  <view :style="{paddingTop: aqTop,height:height}">
    <myTab class="myTab" :list="list" >
      <!-- slot与name对应 具名插槽 -->
      <view slot="a"> 
        11111111
      </view>
      <view slot="b">22222222222222</view>
      <view slot="c">33333333333333</view>
    </myTab>
  </view>
  <tabbar :value="2" />
</template>

<script setup>
import tabbar from "@/components/tabbar/tabbar.vue";
import myTab from "@/components/myTab/myTab.vue";
import search from "@/components/search/search.vue";
// 安全区域顶部高度
const aqTop = uni.$u.sys().safeArea.top + "px";
const height = `calc(100% - ${aqTop})`

const list = ['a','b','c',]
const current = ref(0)

const change = (e) =>{
  console.log(e.detail);
  if(e.detail){
    changeIndex(e.detail.current)
  }
}

const changeIndex = (index) =>{
  current.value = index
}
</script>
<style lang='scss' scoped>
.myTab{

}
</style>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容