小程序篇-tab組件

如何編寫(xiě)小程序的tab組件?

小討論:我們都知道小程序可以用template編寫(xiě)一些模版缀辩,后來(lái)小程序又可以實(shí)現(xiàn)與vue類似組件的編寫(xiě)——Component構(gòu)造器踪央。但是個(gè)人覺(jué)得功能還是沒(méi)有vue組件來(lái)得強(qiáng)大,不過(guò)實(shí)現(xiàn)一些平時(shí)用到的業(yè)務(wù)場(chǎng)景還是可以的健无。

下面我給大家來(lái)表演如何實(shí)現(xiàn)tab組件

tab的話就是上面一排標(biāo)簽液斜,點(diǎn)擊標(biāo)簽實(shí)現(xiàn)底下的面板進(jìn)行切換顯示旗唁,這個(gè)其實(shí)是不難實(shí)現(xiàn)的,但是我們要整點(diǎn)復(fù)雜的,才可以寫(xiě)在簡(jiǎn)書(shū)里祷嘶。
思路:借鑒了一些vue第三方組件的封裝思路论巍,我們讓tab組件由tab和tab-panel兩個(gè)組件以父子關(guān)系組成嘉汰,然后我們根據(jù)tab-panel的一些屬性和數(shù)量來(lái)生成tab双泪,這就涉及到Component構(gòu)造器父子組件之間的聯(lián)系。

項(xiàng)目結(jié)構(gòu)圖
tab效果圖

我們新建一個(gè)Component 名為tab
tab.js

Component({
  // 關(guān)聯(lián)子組件
  relations: {
    '../tab-panel/tab-panel': {
      type: 'child',
      linked(target) {},
      linkChanged(target) {},
      unlinked(target) {}
    }
  },

  properties: {
    // 內(nèi)聯(lián)樣式
    iStyle: {
      type: String,
      value: ''
    },
    // 用來(lái)初始化顯示某個(gè)panel
    value: {
      type: String,
      value: ''
    },
    // tab標(biāo)簽數(shù)組
    tab: {
      type: Array,
      value: []
    }
  },

  data: {
    selectIndex: 0,
    tabIndex: 0,
    scrollLeft: 0,
    width: 0,
    ml: 0,
    initMl: 0,
    svWidth: 0,
    panelNodes: [],
    isLower: false,
    lastLeft: 0,
    lastWidth: 0
  },
  ready() {
    this.getAllPanel();
    this.initCal();
  },
  methods: {
    /**
     * @desc 獲取子組件tab-panel,用來(lái)生成tab
     */
    getAllPanel() {
      const { value } = this.data;
      const ttab = [];
      const panelNodes = this.getRelationNodes('../tab-panel/tab-panel');

      this.setData({ panelNodes });
      panelNodes.map((item, i) => {
        const {
          data: { label, name }
        } = item;
        if (value === name) this.setData({ selectIndex: i });
        ttab.push({ text: label });
      });
      this.setData({ tab: ttab });
    },
    /**
     * @desc 初始化tab及一些元素的計(jì)算
     */
    initCal() {
      wx.createSelectorQuery()
        .in(this)
        .selectAll('.tab__item')
        .boundingClientRect(rects => {
          const { tab } = this.data;
          tab.map((item, i) => {
            if (i === tab.length - 1) {
              this.setData({
                lastLeft: rects[i].left,
                lastWidth: rects[i].width
              });
            }
            item.left = rects[i].left;
          });

          this.setData({
            tab
          });
        })
        // 設(shè)置第一個(gè)tab元素的left
        .select('.first')
        .boundingClientRect(rect => {
          this.setData({ initMl: rect.left });
        })
        // 獲取tab外層滾動(dòng)的view的寬度
        .select('.scroll-view')
        .boundingClientRect(rect => {
          this.setData({ svWidth: rect.width });
          const { selectIndex, tab } = this.data;
          this.changeTabFun(selectIndex, tab[selectIndex].left);
        })
        .exec();
    },
    /**
     * @desc 切換tab事件
     */
    changeTab({
      currentTarget: {
        dataset: { index, left }
      }
    }) {
      if (this.data.tabIndex === index) return;
      this.changeTabFun(index, left);
    },
    /**
     * @desc 切換tab事件,計(jì)算scroll-view顯示位置
     */
    changeTabFun(index, left) {
      const { tab, initMl, svWidth, panelNodes } = this.data;
      tab.map((item, i) => (item.active = i === index));

      this.setData({ tab, tabIndex: index });
      wx.createSelectorQuery()
        .in(this)
        .select('.active')
        .boundingClientRect(rect => {
          // 計(jì)算scrollleft
          const sc = left - (svWidth - rect.width) / 2 - initMl;
          this.setData({
            width: rect.width,
            scrollLeft: sc
          });
          // 延遲底部橫線切換效果
          setTimeout(() => {
            this.setData({
              ml: left - initMl
            });
          }, 80);
        })
        .exec();

      panelNodes.map((item, i) => {
        item.setData({ isShow: index === i });
      });

      this.triggerEvent('changeTab', { name: panelNodes[index].data.name });
    },
    /**
     * @desc 綁定滾動(dòng),判斷是否滾動(dòng)到最右側(cè)來(lái)顯示漸變蒙版
     */
    bindscroll({ detail: { scrollLeft } }) {
      const { svWidth, initMl, lastLeft, lastWidth } = this.data;
      const l = Math.floor(lastLeft - svWidth + lastWidth - initMl);
      if (scrollLeft >= l - 1) {
        this.setData({ isLower: true });
      } else {
        this.setData({ isLower: false });
      }
    },
    /**
     * @desc 切換到某個(gè)面板
     */
    toPanel(panelName) {
      const { panelNodes } = this.data;
      this.setData({ selectIndex: 0 });
      panelNodes.map((item, i) => {
        const {
          data: { name }
        } = item;
        if (panelName === name) this.setData({ selectIndex: i });
      });
      this.initCal();
    }
  }
});

我們可以看到tab.js Component有幾個(gè)大屬性組成,分別是relations【定義與子組件關(guān)系】尚卫,properties【父組件傳遞接收】,data【組件內(nèi)部data】怎爵,ready【組件生命周期函數(shù),在組件布局完成后執(zhí)行芙委,此時(shí)可以獲取節(jié)點(diǎn)信息】,這里只用到所有生命周期中的ready侧啼,可查閱 組件的生命周期痊乾,methods【組件內(nèi)部方法】。
this.getRelationNodes('../tab-panel/tab-panel') 我們有了小程序獲取所有子組件這個(gè)方法的支持协饲,讓我們與子組件的操作更加便利描馅。

tab.wxml

<view class="tab" style="{{iStyle}}">
  <view class="tab__scroll">
    <view class="tab__scroll-wrapper {{isLower?'lower':''}}">
      <scroll-view class="scroll-view" scroll-x="{{true}}" scroll-with-animation="{{true}}" bindscroll="bindscroll" scroll-left="{{scrollLeft}}">
        <view class="tab__list">
          <view class="tab__item {{index===0?'first':''}} {{index===tab.length-1?'last':''}} {{item.active?'active':''}}" wx:for="{{tab}}" wx:key="{{index}}" bindtap="changeTab" data-index="{{index}}" data-left="{{item.left}}">
            {{item.text}}
          </view>
        </view>
        <view class="tab__line transition" style="width:{{width}}px; margin-left:{{ml}}px;"></view>
      </scroll-view>
    </view>
  </view>

  <slot></slot>

</view>

加入了scroll-view 實(shí)現(xiàn)tab標(biāo)簽多了之后可以進(jìn)行滾動(dòng)嘹狞,而且在scroll-view可以加入平滑滾動(dòng)效果,slot插槽是用來(lái)放置tab-panel組件的

tab.wxss

.tab__scroll {
  padding: 20rpx 30rpx 0rpx;
  font-size: 28rpx;
  color: #9b9b9b;
  position: relative;
}
.tab__scroll-wrapper {
  border-bottom: 1rpx solid #f0f0f0;
  -webkit-mask-image: linear-gradient(to right, #1a1a1a 80%, transparent);
  mask-image: linear-gradient(to right, #1a1a1a 80%, transparent);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.tab__scroll-wrapper.lower {
  -webkit-mask-image: linear-gradient(#1a1a1a 100%, transparent);
  mask-image: linear-gradient(#1a1a1a 100%, transparent);
}
.tab__list {
  white-space: nowrap;
  width: 100%;
}
.tab__item {
  vertical-align: top;
  display: inline-block;
  margin-right: 40rpx;
  padding-top: 10rpx;
  padding-bottom: 5rpx;
  padding-left: 5rpx;
  padding-right: 5rpx;
}
.tab__item:last-child {
  margin-right: 0;
}
.tab__item.active {
  color: #383538;
}
.tab__line {
  width: 56rpx;
  height: 4rpx;
  background: #ffe700;
  border-radius: 4rpx;
}
.tab .transition {
  transition: all 0.3s ease 0s;
}

我們可以看到wxss有這樣的樣式

  /* ... */
 -webkit-mask-image: linear-gradient(to right, #1a1a1a 80%, transparent);
  mask-image: linear-gradient(to right, #1a1a1a 80%, transparent);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  /* ... */

這是實(shí)現(xiàn)右側(cè)漸變蒙版的效果,可以看下我之前寫(xiě)的 css篇-mask-image + linear-gradient 優(yōu)雅顯示富文本過(guò)長(zhǎng)

tab的父組件就這樣完成了,接下來(lái)我們來(lái)看下子組件tab-panel的編寫(xiě)


我們新建一個(gè)Component 名為tab-panel
tab-panel.js

Component({
  relations: {
    '../tab/tab': {
      type: 'parent',
      linked(target) {},
      linkChanged(target) {},
      unlinked(target) {}
    }
  },
  properties: {
    // 內(nèi)聯(lián)樣式
    iStyle: {
      type: String,
      value: ''
    },
    // label用來(lái)顯示tab的標(biāo)簽名
    label: {
      type: String,
      value: ''
    },
    // name為panel的唯一標(biāo)識(shí),用來(lái)確定要顯示哪個(gè)panel
    name: {
      type: String,
      value: ''
    }
  },
  data: {
    // 是否顯示當(dāng)前panel
    isShow: false
  }
});

tab-panel.wxml

<view class="tab-panel" style="display:{{isShow?'block':'none'}};{{iStyle}}">
  <slot></slot>
</view>

slot 插槽用來(lái)放置實(shí)際的內(nèi)容

tab-panel.wxss

.tab-panel {
  box-sizing: border-box;
  padding: 0 30rpx 0;
}

css可以自己定義,根據(jù)需求

這樣子我們就完成了tab-panel子組件了


我們可以看到主要的代碼編寫(xiě)還是在tab.js里面,因?yàn)閠ab-panel 說(shuō)白了就支持了tab顯示需要的數(shù)組绿语,接下我們們看看在index頁(yè)面中如何調(diào)用這個(gè)組件岗仑。

index.js

Page({
  data: {},
  onLoad() {},
  // 子組件事件觸發(fā)
  onChangeTab({ detail: { name } }) {
    console.log('name :', name);
  },
  // 跳轉(zhuǎn)到制定panel
  toPanel({
    currentTarget: {
      dataset: { panelName }
    }
  }) {
    this.selectComponent('#tab').toPanel(panelName);
  }
});
/* 
這里的onChangeTab是子組件觸發(fā)調(diào)用的荠雕,
類似vue中的$emit的用法既鞠,
this.selectComponent('#tab').toPanel(panelName) 為調(diào)用子組件方法蚯姆,
類似vue中的this.$refs['xxx'].func()
*/

index.json

{
  "usingComponents": {
    "tab": "../../components/tab/tab/tab",
    "tab-panel": "../../components/tab/tab-panel/tab-panel"
  }
}

指定使用的組件 tab、tab-panel

index.wxml

<view class="index">
  <tab i-style="height:100%;" id="tab" value="panel2" bind:changeTab="onChangeTab">
    <tab-panel label="我是panel1" name="panel1">
      <view class="index__panel">
        <view>第一個(gè)panel</view>
        <button bindtap="toPanel" data-panel-name="{{'panel6'}}">跳轉(zhuǎn)到panel6</button>
      </view>
    </tab-panel>
    <tab-panel i-style="height:calc(100% - 86rpx);box-sizing:border-box;" label="我是panel2我比較長(zhǎng)" name="panel2">
      <scroll-view class="index__scroll-view" scroll-y="{{true}}">
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
        <view>第二個(gè)panel</view>
      </scroll-view>
    </tab-panel>
    <tab-panel label="我是panel3" name="panel3">
      <view class="index__panel">第三個(gè)panel</view>
    </tab-panel>
    <tab-panel label="我是panel4" name="panel4">
      <view class="index__panel">第四個(gè)panel</view>
    </tab-panel>
    <tab-panel label="我是panel5" name="panel5">
      <view class="index__panel">第五個(gè)panel</view>
    </tab-panel>
    <tab-panel label="我是panel6" name="panel6">
      <view class="index__panel">
        <view class="index__panel">
          <view>第六個(gè)panel</view>
          <button bindtap="toPanel" data-panel-name="{{'panel1'}}">跳轉(zhuǎn)到panel1</button>
        </view>
      </view>
    </tab-panel>
    <tab-panel label="我是panel7" name="panel7">
      <view class="index__panel">第七個(gè)panel</view>
    </tab-panel>
    <tab-panel label="我是panel8" name="panel8">
      <view class="index__panel">第八個(gè)panel</view>
    </tab-panel>
  </tab>
</view>

我們這里寫(xiě)了8個(gè)panel作為例子煮落,tab-panel為自定義的內(nèi)容蝉仇,我們現(xiàn)在需要管理維護(hù)的就只是tab-panel里面的內(nèi)容啦轿衔。

index.wxss

page {
  height: 100%;
}
.index {
  height: 100%;
}
.index__scroll-view {
  height: 100%;
  box-sizing: border-box;
  padding: 10rpx 0;
}

表演結(jié)束蛤育!

學(xué)會(huì)了組件的編寫(xiě)瓦糕,我們可以舍棄template模版的那種不靈活的編寫(xiě)方式亥揖,雖然組件一些方法需要微信客戶端更高版本费变,我們有時(shí)需要去兼容低版本微信胡控,但是我們秉持擁抱高版本庇绽,擁抱新增功能的態(tài)度。
——尼古拉斯·峰

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哼转,隨后出現(xiàn)的幾起案子壹蔓,更是在濱河造成了極大的恐慌,老刑警劉巖亲雪,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異夺巩,居然都是意外死亡喳张,警方通過(guò)查閱死者的電腦和手機(jī)销部,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門酱虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)聊记,“玉大人,你說(shuō)我怎么就攤上這事舆床“ざ樱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)俯在。 經(jīng)常有香客問(wèn)我跷乐,道長(zhǎng),這世上最難降的妖魔是什么浅侨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任不见,我火速辦了婚禮稳吮,結(jié)果婚禮上灶似,老公的妹妹穿的比我還像新娘席纽。我一直安慰自己润梯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著竟纳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上际歼,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音溯泣,去河邊找鬼。 笑死用押,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缎讼。 我是一名探鬼主播血崭,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼设凹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼月匣!你這毒婦竟也來(lái)了奋姿?” 一聲冷哼從身側(cè)響起院刁,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤再榄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后剑按,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年缩擂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了添寺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡博脑,死狀恐怖薄坏,靈堂內(nèi)的尸體忽然破棺而出胶坠,到底是詐尸還是另有隱情,我是刑警寧澤沈善,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布闻牡,位于F島的核電站,受9級(jí)特大地震影響罩润,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜金度,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一消玄、第九天 我趴在偏房一處隱蔽的房頂上張望携龟。 院中可真熱鬧相满,春花似錦方灾、人聲如沸嘿棘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至液走,卻和暖如春嘱根,著一層夾襖步出監(jiān)牢的瞬間慌洪,已是汗流浹背欧引。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怎炊,地道東北人糟港。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吭敢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凄鼻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容