Vue.js自己实现简单的Tab切换栏组件

557 阅读1分钟

Tab组件介绍

element-ui 也有类似的组件 但是每个TabItem组件会生成一个TabContent,这点不太喜欢。 其实我们可以使用Vue<component is='name'></compoent>替代。话不多说直接上代码。

Tabs组件

<template>
   <div class="tab-box">
    <div class="tab-wrap " ref="tabList" :class="TabStyle">
      <slot></slot>
      <div class="scrollbar" :style="getLineStyle" v-show="left !== 0"></div>
    </div>
  </div>
</template>
<script>
let scrollLeftRafId
export function scrollLeftTo (scroller, to, duration) {
  window.cancelAnimationFrame(scrollLeftRafId)
  let count = 0
  const from = scroller.scrollLeft
  const frames = duration === 0 ? 1 : Math.round((duration * 1000) / 16)
  function animate () {
    scroller.scrollLeft += (to - from) / frames
    if (++count < frames) {
      scrollLeftRafId = window.requestAnimationFrame(animate)
    }
  }
  animate()
}
export default {
  name: 'tabs',
  data () {
    return {
      left: 0,
      lineWidth: null
    }
  },
  mounted () {
  // 监听当前实例上的$emit 触发的setActive事件
    this.$on('setActive', name => {
      this.$emit('input', name)
      this.$emit('tabClick', name)
    })
  },
  beforeDestroy () {
    this.$off('setActive')
  },
  watch: {
  //监听传过来的value值,当value改变重新渲染tabLine和滚动动画
    value: {
      immediate: true,
      handler () {
        this.$nextTick(() => {
          this.setTabLine()
          this.scrollIntoView()
        })
      }
    }
  },
  methods: {
    setTabLine () {
      const currentTab = this.getCurrentTab.$el
      this.left = currentTab.offsetLeft + currentTab.offsetWidth / 2
      this.lineWidth =
          currentTab.querySelector('.tab-text').offsetWidth + 'px'
      this.getTab.forEach(item => {
        item.$emit('active', {
          active: this.value,
          titleActiveColor: this.titleActiveColor
        })
      })
    },
    scrollIntoView () {
      const nav = this.$refs.tabList
      const title = this.getCurrentTab.$el
      const scrollLeft = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2
      scrollLeftTo(nav, scrollLeft, 0.3)
    }
  },
  computed: {
    getCurrentTab () {
      return this.getTab.find(item => {
        return item.name === this.value
      })
    },
    getTab () {
      return this.$children
    },
    getLineStyle () {
      return {
        transform: `translateX(${this.left}px) translateX(-50%)`,
        width: this.lineWidth
      }
    }
  },
  props: {
    value: {
      type: [String, Number]
    },
    titleActiveColor: {
      type: String,
      default: '#333'
    },
    TabStyle: {
      type: String,
      default: 'spaceBetween'
    }
  }
}
</script>

TabItem组件

<template>
  <div
    class="tabItem"
    @click="handleClick"
    :class="getActive"
    :style="getTitleActiveColor"
  >
    <div class="tab-text" ref="text">
      <slot></slot>
      <div class="dot" v-if="dot!==''">{{dot}}</div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'tabItem',
  data () {
    return {
      active: false,
      titleActiveColor: '#333'
    }
  },
  mounted () {
    this.$on('active', e => {
      const { active, titleActiveColor } = e
      this.active = active === this.name
      this.titleActiveColor = titleActiveColor
    })
  },
  beforeDestroy () {
    this.$off('active')
  },
  computed: {
    getActive () {
      return {
        active: this.active
      }
    },
    getTitleActiveColor () {
      if (this.active) {
        return {
          color: this.titleActiveColor
        }
      }
      return ''
    }
  },
  methods: {
    handleClick () {
      this.$parent.$emit('setActive', this.name)
    }
  },
  props: {
    name: {
      type: [String, Number],
      default: ''
    },
    dot: {
      type: [Number, String],
      default: ''
    }
  }
}
</script>

Tab组件的使用

  <tabs v-model="search.status" @tabClick="handleClick">
      <tabItem name="1">进行中</tabItem>
      <tabItem name="2">已结束</tabItem>
  </tabs>