文档:Tabsjavascript
源码:tiny-wheelshtml
若是以为好用就点个 Star 吧~(〃'▽'〃)java
这个组件的难点在于控制每一个tab
项底部条的移动以及对应panel
的移动,最多见的作法是经过transform
来改变元素的位置,不废话,直接上代码git
文章只列出关键部分的代码,其他逻辑可自行研究项目中的源码github
Tabs
组件因为须要让用户自定义内容,因此一些配置咱们经过 HTML 的自定义属性实现(自定义标签兼容性目前还不太好,因此暂不考虑),组件的 HTML 结构以下:element-ui
<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>
复制代码
每一个属性的具体用法文档里已有说明,因此这里再也不赘述api
组件结构的渲染源码里已有,最终渲染出的 HTML 结构是这样的:浏览器
<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
位置、宽度的计算能够经过offsetLeft
与offsetWidth
获得,而后改变对应的样式便可:iview
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
的设置也是同样:函数
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
动画样式
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
等参数
Tabs
组件的基本功能到此就实现完毕了,固然,还能够实现一些更复杂的功能:选项卡的添加删除、响应式展现tab-item
、卡片样式式的选项卡等等,这些功能在element-ui
、iview
、ant-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
组件的样式实现仍然须要计算offsetWidth
、offsetLeft
等等属性,换汤不换药,你们感兴趣的话能够用Vue
重写一遍,这里就很少啰嗦了~
To be continued...