手把手教你用原生JavaScript造轮子(四)——Tabs选项卡
Tabs 选项卡
文档:
Tabs
源码:
tiny-wheels
如果觉得好用就点个 Star 吧~(〃’▽’〃)
效果
思路
这个组件的难点在于控制每个tab
项底部条的移动以及对应panel
的移动,最常见的做法是通过transform
来改变元素的位置,不废话,直接上代码
实现
文章只列出关键部分的代码,其余逻辑可自行研究项目中的源码
Tabs
组件由于需要让用户自定义内容,所以一些配置我们通过 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>
每个属性的具体用法文档里已有说明,所以这里不再赘述
组件结构的渲染源码里已有,最终渲染出的 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
得到,然后改变对应的样式即可:
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…
原文地址:https://segmentfault.com/a/1190000022323949
相关推荐
-
Anka——渐进式小程序开发工具集 javascript/jquery
2019-1-29
-
富婆给你说require、import和export javascript/jquery
2019-5-11
-
JavaScript 精粹:Number 与 Math javascript/jquery
2019-2-27
-
[vue.js]监听数据修改,实时获取DOM高度 javascript/jquery
2020-6-16
-
茄子算法每日N题之LeetCode24两两交换链表中的节点 javascript/jquery
2020-5-26
-
HTML5实现无刷新修改URL javascript/jquery
2020-5-28
-
禁用 window.opener javascript/jquery
2019-5-9
-
Android 11 新特性 javascript/jquery
2020-6-10
-
深入理解Shadow DOM v1 javascript/jquery
2019-5-11
-
前端模块化的前世今生 javascript/jquery
2019-9-18