I looked down at my wristband. It has been 3 days since my own flag jumped…

No why, because I’m lazy # zany # zany # zany

Hey, let’s get down to business today -Tab component writing

First, take a look at the Tab preview:

Tab preview

Very simple function, very simple UI.

I’m going to start with Tab HTML code and CSS code (I’m not very good at CSS so you can ignore this # comical)

In order to have a higher degree of freedom, we use the way of Tab+TabPanel. This is how most UI frameworks are made

Tab – HTML code

  <div class="tab-wrapper">
    <div class="tab__header">
      <div class="tab__header__item">
        <div style="width: 100%;">
          <div class="tab__item"> <span> Verification code login </span> </div> <div class="tab__item"> < span > password < / span > < / div > < / div > < / div > < / div > < div class ="tab__content">
</template>Copy the code

Tab-CSS code

<style scoped>. Iconfont {font-size: 1.5rem; } .tab__header { display: flex; } .tab__header__item { margin: 0; } .tab__header__item > div { display: flex; flex-direction: row; white-space: nowrap; transition: transform .3s; }. Tab__item {font-size: 1.3rem; cursor: pointer; margin-right: 1rem; flex: 1; text-align: center; } .tab--active > span { border-bottom: 2px solid#1890FF;
    color: #1890FF;padding-bottom: .5rem; } .tab__header__btn--left { margin-right: .5rem; cursor: pointer; } .tab__header__btn--right { margin-left: .5rem; cursor: pointer; } .tab--panel-wrapper { display: none; } .tab--panel-wrapper--active { display: block ! important; } </style>Copy the code

TabPanel code:

  <div class="tab--panel-wrapper" :name="name">
    <div class="tab--panel-content">

  export default {
    name: "ZbTabPanel", props: {name: {//Tab module name required:true

<style scoped>

Copy the code

Start making basic functions: Tab switching

Tab switch function production

First comes the question of comprehension:

We used to write Tab heads and stuff like that, content and stuff like that. Let me write them separately. Easy to understand

But in this case, it seems a lot easier to use. But it makes it harder to write

Also, how does a parent control the display and hiding of its children?

Then take it one step at a time.

Just to make it easier to write and understand. I chose content insertion, head traversal

  <div class="tab-wrapper">
    <div class="tab__header" ref="tabHeaderItem">
      <div class="tab__header__item">
        <div style="width: 100%;">
          <div class="tab__item"
               @click="tabItemClick(item.name,key)"// TAB click event @touchStart ="tabItemClick(item.name,key)"// The move side of the TAB click event v-for="(item,key) in tabList"// loop list :class="{'tab--active':activeTab.index===key}"<span>{{item.name}}</ SPAN > </div> </div> </div> </div> </div> </div> <div class="tab__content"

Copy the code


data() {
  returnActiveTab: {tabList: [], // TAB title list activeTab: {// TAB information index: 0, // subscript name:' ' //名称
}Copy the code

Since it’s a slot, let’s use a slot to do something # heh heh heh

So I’m going to look at the contents of $slot in Mounted

It turns out there is

However, an empty slot value appears… But it doesn’t matter, we can add a layer of null judgment ~~

letself = this; // The outer new variable references this this.$slots.default.forEach((components) => {// loop through the contents of defaultif(components. The tag && ponentOptions components.com {/ / if the child element key tag && componentOptions have content. Self. TabList. Push (components.com ponentOptions propsData) / / in components.com ponentOptions propsDate this attribute in the key. We can use this property to get the props of the child component}}); this.$nextTickActiveTab = {index: 0, activeTab: 0, activeTab: 0, activeTab: 0, activeTab: 0 This.tablist [0].name // Find the first element of the tabList and its name}; });Copy the code

In this way the switch header function is achieved. But the bottom body is indifferent

So let’s listen for the activeTab variable in the watch function:

watch: {
  activeTab(newValue, oldValue) {
    this.$refs.content.children[oldValue.index].className = "tab--panel-wrapper";
    this.$refs.content.children[newValue.index].className = "tab--panel-wrapper--active"; }}Copy the code

So the basics are done.

Use the reference

  <zb-tab-panel name="Verification code login"> <! </zb-tab-panel> <zb-tab-panel name="Password login"> <! </zb-tab> </zb-tab>Copy the code

This is required because the name attribute cannot be less

One problem with this is that our TAB is written in Flex. But what happens if the TAB has more horizontal scrolling?

Here is a rolling component developed by Amway @ustBhuangyi:better-scroll

First we install it:

Better to scroll the document

npm install better-scroll --saveCopy the code

We then introduce the following in the VUE component:

import BScroll from 'better-scroll'Copy the code

I won’t go into too much here. So let’s just use simple. Please refer to the documentation for details

_initScroll: function () {
  new BScroll(this.$refs.tabHeaderItem, {
    scrollX: true// Whether X roll bounce is supported:true}); }Copy the code

Inside the mount function

setTimeout(() => {
  this.$nextTick(() => {// Avoid data is not updated this._initscroll (); }}), 20)Copy the code

If you see inline styles that have CSS animations. The mount is successful

There’s a little bug. Mount success is ok. But you can’t scroll. As shown in figure

< span style = “max-width :100%” style = “max-width :100%” So he lost the actual length. Unable to mount.

However, after removing width:100%, the element will never tile. Will float to the left of the same arrangement

So we need to add props external control for both

props: {
  floatLeft: {// Whether to enable the Left floating mode default:false}}Copy the code


<div class="tab__header__item"
     :style="floatLeft? '': 'width:100%'">Copy the code

And that’s a not-so-perfect TAB. I hope the production ideas can help you