手把手教你用原生JavaScript造輪子(四)——Tabs選項卡

Tabs 選項卡

文檔: Tabs
源碼: tiny-wheels
若是以爲好用就點個 Star 吧~(〃'▽'〃)

效果

Tabs

思路

這個組件的難點在於控制每一個tab項底部條的移動以及對應panel的移動,最多見的作法是經過transform來改變元素的位置,不廢話,直接上代碼javascript

實現

文章只列出關鍵部分的代碼,其他邏輯可自行研究項目中的源碼

Tabs組件因爲須要讓用戶自定義內容,因此一些配置咱們經過 HTML 的自定義屬性實現(自定義標籤兼容性目前還不太好,因此暫不考慮),組件的 HTML 結構以下:html

<div class="tabs" data-tab-active="2" data-tab-disabled="3">
  <div data-tab-name="選項卡1" data-tab-key="1">內容1</div>
  <div data-tab-name="選項卡2" data-tab-key="2">內容2</div>
  <div data-tab-name="選項卡3" data-tab-key="3">內容3</div>
  <div data-tab-name="選項卡4" data-tab-key="4">內容4</div>
</div>

每一個屬性的具體用法文檔裏已有說明,因此這裏再也不贅述java

組件結構的渲染源碼裏已有,最終渲染出的 HTML 結構是這樣的:git

<div class="tabs tiny-tabs" data-tab-active="2" data-tab-disabled="3">
  <div class="tab-header">
    <span class="tab-item">選項卡1</span>
    <span class="tab-item active">選項卡2</span>
    <span class="tab-item disabled">選項卡3</span>
    <span class="tab-item">選項卡4</span>
    <span
      class="tab-line"
      style="width: 46px; transform: translateX(77px);"
    ></span>
  </div>
  <div class="tab-panels animated" style="transform: translateX(-100%);">
    <div data-tab-name="選項卡1" data-tab-key="1" class="tab-panel">內容1</div>
    <div data-tab-name="選項卡2" data-tab-key="2" class="tab-panel active">
      內容2
    </div>
    <div data-tab-name="選項卡3" data-tab-key="3" class="tab-panel">內容3</div>
    <div data-tab-name="選項卡4" data-tab-key="4" class="tab-panel">內容4</div>
  </div>
</div>

根據用戶設置的選項卡內容,咱們能夠渲染出對應數量的tab-item,而tab-item位置、寬度的計算能夠經過offsetLeftoffsetWidth獲得,而後改變對應的樣式便可:github

setTabs () {
    this.$$tabItems = this.$container.querySelectorAll('.tab-item')
    this.$tabLine = this.$container.querySelector('.tab-line')
    this.setTabStatus()
    const tabIndex = this.getTabIndex() ? this.getTabIndex() : 0
    if (this.$$tabItems[tabIndex]) {
        const { offsetWidth, offsetLeft } = this.$$tabItems[tabIndex]
        this.setTabItem(this.$$tabItems[tabIndex])
        this.setTabLine(offsetWidth, offsetLeft)
        this.setTabPanel(this.$$tabPanels[tabIndex], tabIndex)
    }
}
setTabLine (width, left) {
    this.$tabLine.style.width = `${width}px`
    this.$tabLine.style.transform = `translateX(${left}px)`
}

tab-panel的設置也是同樣:element-ui

setTabPanel ($panel, index) {
    this.$tabPanelContainer.style.transform = `translateX(-${index * 100}%)`
    this.$$tabPanels.forEach($panel => $panel.classList.remove('active'))
    $panel.classList.add('active')
    setTimeout(() => {
        if (this.options.animated) {
            this.$tabPanelContainer.classList.add('animated')
        }
    })
}

須要注意的是,第一次加載組件時,咱們不但願tab-panel有滑動效果,因此這裏須要用setTimeout延時加載transition動畫樣式api

Tabs組件的的核心邏輯就這麼多了,剩下的是一些配置屬性、事件綁定的實現:瀏覽器

getTabIndex () {
    const tabKey = this.$container.dataset.tabActive
    let tabIndex = tabKey
    if (tabKey) {
        this.$$tabPanels.forEach(($panel, index) => {
            if ($panel.dataset.tabKey === tabKey) {
                tabIndex = index
            }
        })
    }
    return tabIndex
}
setTabStatus () {
    const tabKey = this.$container.dataset.tabDisabled
    if (tabKey) {
        this.$$tabPanels.forEach(($panel, index) => {
            if ($panel.dataset.tabKey === tabKey) {
                this.$$tabItems[index].classList.add('disabled')
            }
        })
    }
}
bindTabs () {
    this.$$tabItems.forEach($tab => {
        $tab.addEventListener('click', () => {
            if (!$tab.classList.contains('disabled')) {
                const index = [...this.$$tabItems].indexOf($tab)
                this.setTabItem($tab)
                this.setTabLine($tab.offsetWidth, $tab.offsetLeft)
                this.setTabPanel(this.$$tabPanels[index], index)
                this.options.callback.call(null, $tab, this.$$tabPanels[index].dataset.tabKey)
            }
        })
    })
}

tab-active(初始激活項)與tab-disabled(初始禁用項)都是經過dataset的api拿到對應的屬性值,而後遍歷找到須要設置的項便可;綁定事件時須要給回調函數傳入當前元素的引用、tab-key等參數iview

Tabs組件的基本功能到此就實現完畢了,固然,還能夠實現一些更復雜的功能:選項卡的添加刪除、響應式展現tab-item、卡片樣式式的選項卡等等,這些功能在element-uiiviewant-design中都有實現,能夠參考它們的效果自行拓展~函數

Vue或者React來封裝這樣的組件,無非只是把DOM操做省去,組件屬性的配置更簡化了而已,一些內部的核心實現原理是通用的,好比用Vue來寫組件的結構,可能就會變成這樣:

<template>
    <tabs :active.sync="activeTab" @update:selected="callback">
        <tabs-head>
            <tabs-item name="one" disabled>
                選項卡1
            </gabs-item>
            <tabs-item name="two" active>
                選項卡2
            </tabs-item>
            <tabs-item name="three">
                選項卡3
            </tabs-item>
        <tabs-head>
        <tabs-body>
            <tabs-panel name="one">
                內容1
            </tabs-panel>
            <tabs-panel name="two">
                內容2
            </tabs-panel>
            <tabs-panel name="three">
                內容3
            </tabs-panel>
        </tabs-body>
    <tabs>
</template>

能夠看到,屬性的配置簡化了不少,組件結構和咱們用原生 HTML 渲染出來的結果是差很少的(實際上用原生的自定義標籤也能夠模擬出這樣的效果來,只是目前瀏覽器兼容還不好),而tabs組件的樣式實現仍然須要計算offsetWidthoffsetLeft等等屬性,換湯不換藥,你們感興趣的話能夠用Vue重寫一遍,這裏就很少囉嗦了~

To be continued...

相關文章
相關標籤/搜索