**【组件】vue实现自定义tab栏下划线滚动过渡效果以及点击当前tab自动居中**

vue实现自定义tab栏下划线滚动过渡效果以及点击当前tab自动居中

小程序、前端交流群:609690978

先上效果图
在这里插入图片描述

组件代码:

<template>
  <div v-if="list.length > 0" class="tabBlock">
    <div @scroll="scroll" class="tabContent">
      <div ref="tabContentScroll" class="tabContentScroll">
        <div id="tab_list" class="tab">
          <div
            ref="tab_item"
            v-for="(item, index) in list"
            :key="index"
            :class="['tab__item', { 'tab__item--active': currentIndex === index }]"
            :style="{ color: currentIndex === index ? `${itemColor}` : '' }"
            @click="select(item, index)"
          >
            <div class="tab__item-title">
              <slot :title="item" name="title"></slot>
            </div>
            <div v-if="!showTitleSlot" class="tab__item-title">{{ item.title }}</div>
          </div>
        </div>
        <div :style="{ background: lineColor, width: lineStyle.width, transform: lineStyle.transform, transitionDuration: lineStyle.transitionDuration }" class="tab__line"></div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: [Number, String],
      default: null
    },
    list: {
      type: Array,
      default: () => {
        return []
      }
    },
    itemColor: {
      type: Array,
      default: () => {
        return []
      }
    },
    lineColor: {
      type: Array,
      default: () => {
        return []
      }
    },
    lineAnimated: {
      type: Array,
      default: () => {
        return []
      }
    }
  },
  data() {
    return {
      currentIndex: 0,
      lineStyle: {},
      scrollLeft: 0,
      tabsScrollLeft: 0,
      duration: 0.3
    }
  },
  computed: {
    showTitleSlot() {
      return this.$scopedSlots.title
    }
  },
  watch: {
    list() {
      this.setTabList()
    },
    value() {
      this.currentIndex = this.value
      this.setTabList()
    },
    scrollLeft(val) {
      console.log(val)
    }
  },
  mounted() {
    this.currentIndex = this.value
    this.setTabList()
    if (!this.lineAnimated) {
      this.duration = 0
    }
    // console.log(this.$scopedSlots)
  },
  methods: {
    select(item, index) {
      this.$emit('input', index)
      this.scroll()
    },
    setTabList() {
      this.$nextTick(() => {
        if (this.list.length > 0) {
          this.setLine()
          this.scrollIntodiv()
        }
      })
    },
    setLine() {
      let lineWidth = 0
      let lineLeft = 0
      this.getElementData(`#tab_item`, data => {
        const el = data[this.currentIndex]
        lineWidth = el.width
        // lineLeft = el.width * (this.currentIndex + 0.5)  // 此种只能针对每个item长度一致的
        lineLeft = el.width / 2 + -data[0].left + el.left
        this.lineStyle = {
          width: `${lineWidth}px`,
          transform: `translateX(${lineLeft}px) translateX(-50%)`,
          transitionDuration: `${this.duration}s`
        }
      })
    },
    // item滚动计算
    scrollIntodiv() {
      this.getElementData('#tab_list', data => {
        // const list = data[0]
        this.getElementData('#tab_item', data => {
          const item = data[this.currentIndex]
          // 滚动父级元素宽度
          // const tabContentWidth = this.$refs.tabContentScroll.scrollWidth
          // 父级元素可视区域宽度
          const screenWdith = this.$refs.tabContentScroll.offsetWidth
          // item的宽度
          const itemWidth = item.width
          // item距离最左侧的距离
          const itemOffsetLeft = item.left
          // item中间值 = (父级可视区域宽度 - item的宽度) / 2
          const itemCenter = (screenWdith - itemWidth) / 2
          // item目标值 = 当前item左移值 - 中间值
          const itemTarget = itemOffsetLeft - itemCenter
          // 屏幕宽度
          const winWidth = window.innerWidth
          console.log(winWidth)
          // 目标值小于0时,滚动值设置为0
          if (itemTarget < 0) {
            this.$refs.tabContentScroll.scrollLeft = 0
            return
          }
          // 目标值小于屏幕宽减去父级可是区域宽时,滚动值设置为 父级可是区域宽-屏幕宽+元素宽
          if (itemTarget < winWidth - screenWdith) {
            this.$refs.tabContentScroll.scrollLeft = screenWdith - winWidth + itemWidth
            return
          }
          this.$refs.tabContentScroll.scrollLeft = itemTarget
        })
      })
    },
    getElementData(el, callback) {
      const domArr = this.$refs.tab_item
      const arr = []
      domArr.forEach(e => {
        arr.push({ id: 'tab_item', left: e.offsetLeft, width: e.offsetWidth })
      })
      callback(arr)
    },
    scroll() {
      this.scrollLeft = this.$refs.tabContentScroll.scrollLeft
    }
  }
}
</script>

<style lang="scss">
.tabBlock {
  position: relative;
  margin-bottom: 30px;
  .tabContent {
    .tabContentScroll {
      margin: 0 15px;
      overflow: auto;
    }
    .tabContentScroll::-webkit-scrollbar-thumb {
      display: none;
    }
  }
  .tabContent::before {
    content: '';
    height: 1px;
    width: 100%;
    position: absolute;
    bottom: 1px;
    left: 0;
    background: #cccccc;
  }
  .tab {
    position: relative;
    display: flex;
    font-size: 28px;
    padding-bottom: 15px;
    white-space: nowrap;
    &__item {
      // flex: 1;
      // text-align: center;
      line-height: 90px;
      color: #999999;
      margin-right: 40px;
      position: relative;
      &--active {
        color: black;
      }
      &-title {
        font-size: 14px;
        font-weight: 400;
        line-height: 22px;
      }
    }
  }
  .tab__line {
    display: block;
    height: 1px;
    position: absolute;
    bottom: 0px;
    left: 0;
    z-index: 1;
    border-radius: 3px;
    position: relative;
    background: black;
  }
}
</style>

父级页面调用方法
1.自定义标题样式,如上述效果图

<filters-tab :list="filtersList" v-model="active">
  <template v-slot:title="{ title }">
   <div class="tab-block">
    <div class="tab-block-title">{{ title.title }}</div>
     <div v-if="title.info != 0" class="tab-block-info">
      <p class="tab-block-info-text">{{ title.info }}</p>
     </div>
    </div>
  </template>
</filters-tab>
data() {
    return {
      filtersList: [
        { title: '所有订单1', info: 0 },
        { title: '待支付1', info: 0 },
        { title: '待发货1', info: 0 },
        { title: '已发货1', info: 0 },
        { title: '售后中1', info: 0 },
        { title: '已完成1', info: 0 },
        { title: '所有订单2', info: 0 },
        { title: '待支付2', info: 0 },
        { title: '待发货2', info: 0 },
        { title: '已发货2', info: 0 },
        { title: '售后中2', info: 0 },
        { title: '已完成2', info: 0 }
      ],
      active: 0
    }
  },
.tabContent {
    margin-top: 20px;
    .tab-block {
      &-info {
        width: 16px;
        height: 16px;
        border-radius: 50%;
        background: #ccc;
        position: absolute;
        right: -18px;
        top: 40%;
        transform: translate(0, -50%);
        &-text {
          font-size: 11px;
          font-weight: 600;
          color: #999999;
          line-height: 16px;
          text-align: center;
        }
      }
    }
  }

2.直接调用

<filters-tab :list="list" v-model="active" itemColor="#03648f" lineColor="#03648f"></filters-tab>
<div>{{active}}</div>

小程序、前端交流群:609690978

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值