vue選項(xiàng)卡Tabs組件實(shí)現(xiàn)示例詳解
概述
前端項(xiàng)目中,多數(shù)頁面涉及到選項(xiàng)卡切換,包括路由切換,指令v-if等,本質(zhì)上其實(shí)和選項(xiàng)卡切換思想差不多,如果是個(gè)簡單的選項(xiàng)卡,還是很簡單的,我們也不需要什么組件庫的組件,自己也能幾行代碼寫出來,但是涉及到動(dòng)畫,尺寸計(jì)算,拖拽的功能的時(shí)候,多數(shù)情況下,自己寫還是要花點(diǎn)時(shí)間的,組件庫就提供了現(xiàn)成的,拿來改改樣式就行,為了對這個(gè)組件更加深入的理解,這里自己實(shí)現(xiàn)一個(gè)帶拖拽,過渡的tabs組件。
效果圖

實(shí)現(xiàn)過程
組件分析
- 組件包含兩部分:Tabs組件和TabPane組件,參考絕大多數(shù)組件庫的習(xí)慣
- 組件主要分為需要點(diǎn)擊的tab欄和下面對應(yīng)的內(nèi)容塊
- 我們需要對內(nèi)容區(qū)和選項(xiàng)卡點(diǎn)擊區(qū)分別加上過渡動(dòng)畫,提升用戶體驗(yàn)
- 最后需要加上拖拽調(diào)整選項(xiàng)卡順序的功能
所需的前置知識(shí)
- 熟悉vue內(nèi)置transition組件
- 深入掌握vue父子組件通信,除開emit和props,還需要掌握inject,emit和props,還需要掌握inject,emit和props,還需要掌握inject,parent,vnode,渲染函數(shù)等等,這些業(yè)務(wù)開發(fā)中用的不多,但是組件庫里面比較常見。
- 了解dom中位置計(jì)算和尺寸的基本計(jì)算
- 熟悉html5新增拖拽相關(guān)事件
項(xiàng)目組件文件夾

Tabs.vue
<template>
<div class="gnip-tab">
<div class="gnip-tab-nav">
<div
v-for="(item, index) in tabNavList"
@click.stop="handleTabNavClick(item, index)"
:class="['tab-nav-item', item.name == activeName ? 'active' : '']"
ref="tabNavItemRefs"
@drop="handleDrop(item, $event, index)"
@dragstart="handelDragstart(item, $event, index)"
@dragover="handleDragOver(item, $event, index)"
draggable="true"
>
<span v-if="item.text">{{ item.text }}</span>
<render v-if="item.renderFun" :renderFn="item.renderFun"></render>
</div>
</div>
<!-- 滾動(dòng)滑塊 -->
<div
class="tab-nav-track"
:style="{
background: showTrackBg ? '#e5e7eb' : '',
}"
>
<span
class="track-line"
:style="{ width: trackLineWidht + 'px', left: left + 'px' }"
></span>
</div>
<div class="tab-content-wrap">
<slot></slot>
</div>
</div>
</template>
<script>
// render組件,label為render函數(shù)的時(shí)候進(jìn)行渲染
import Render from "./render";
export default {
props: {
// v-model的那項(xiàng)
value: {
type: String,
},
// 是否顯示滑塊背景
showTrackBg: {
type: Boolean,
default: false,
},
},
components: {
Render,
},
data() {
return {
// tab數(shù)組
tabNavList: [],
// 當(dāng)前活躍項(xiàng)
activeName: "",
// 滑塊的寬度
trackLineWidht: 0,
// 當(dāng)前活躍索引
currentIndex: 0,
// 滑塊偏移量
left: 0,
// 拖拽開始的哪項(xiàng)
dragOriginItemIndex: null,
// 拖拽活躍項(xiàng)的索引
dragStartIndex: null,
};
},
mounted() {
this.init();
},
methods: {
// 初始化
init() {
// 默認(rèn)當(dāng)前活躍項(xiàng)為外部v-model的值
this.activeName = this.value;
// 頁面渲染任務(wù)之后計(jì)算滑塊偏移量和寬度
this.$nextTick(() => {
this.currentIndex = this.$children.findIndex(
(component) => component.name == this.value
);
this.computedTrackWidth();
});
},
// 設(shè)置tab點(diǎn)擊欄
setTabBar(tabsPaneInstance) {
// tab的描述信息可以是字符串也可以是render函數(shù)
const label = tabsPaneInstance.label,
type = typeof label;
// 添加到數(shù)組項(xiàng)中,根據(jù)添加條件渲染
this.tabNavList.push({
text: type == "function" ? "" : label,
renderFun: type == "function" ? label : "",
name: tabsPaneInstance.name,
});
},
handleTabNavClick(item, index) {
if (item.name == this.activeName) return;
// 更新當(dāng)前活躍項(xiàng)
this.activeName = item.name;
// 活躍項(xiàng)的索引
this.currentIndex = index;
// 計(jì)算滑塊的偏移量和寬度
this.computedTrackWidth();
},
// 計(jì)算滑塊的偏移量和寬度
computedTrackWidth() {
// 插槽子組件的索引集合
const tabNavItemRefsList = this.$refs.tabNavItemRefs;
// 導(dǎo)航tab項(xiàng)的寬度
const scrollWidth = tabNavItemRefsList[this.currentIndex].scrollWidth;
// 滑塊的寬度為scrollWidth
this.trackLineWidht = scrollWidth;
// 定位的偏移量為offsetLeft
this.left = tabNavItemRefsList[this.currentIndex].offsetLeft;
},
/*
關(guān)于拖拽請參考MDN文檔: https://developer.mozilla.org/zh-CN/docs/Web/API/DragEvent,實(shí)現(xiàn)拖拽需要清楚關(guān)于拖拽相關(guān)的幾個(gè)事件
*/
// 開始拖拽
handelDragstart(item, event, index) {
// 說明是拖拽的當(dāng)前活躍的哪一項(xiàng),記錄這一項(xiàng)的索引位置
if (item.name == this.activeName) {
this.dragStartIndex = index;
}
this.dragOriginItemIndex = index;
},
// 推拽進(jìn)入目標(biāo)區(qū)域
handleDragOver(item, event) {
// 阻止默認(rèn)事件
event.preventDefault();
},
//拖拽進(jìn)入有效item
handleDrop(item, event, index) {
event.preventDefault();
// 說明拖動(dòng)的位置是變了的
if (this.dragOriginItemIndex != index) {
// 交換數(shù)據(jù),重新渲染生成tab欄
this.swap(this.dragOriginItemIndex, index);
// 重新計(jì)算滑塊的偏移量
if (this.dragStartIndex !== null) {
this.currentIndex = index;
// 記住,數(shù)據(jù)更新為異步操作,因此我們這里需要用到nextTick,將計(jì)算任務(wù)放到渲染任務(wù)完成之后執(zhí)行,避免計(jì)算不準(zhǔn)確
this.$nextTick(() => {
this.computedTrackWidth();
this.dragStartIndex = null;
});
} else {
// 不是點(diǎn)擊拖拽當(dāng)前活躍項(xiàng),也要重新計(jì)算滑塊跨度和位置,因?yàn)槊總€(gè)tab項(xiàng)的寬度不一致,因此,每次拖拽都需要重新計(jì)算
this.$nextTick(() => {
this.computedTrackWidth();
});
}
// 這里還可以根據(jù)需要,發(fā)布一個(gè)拖拽完成事件
}
},
// 交換tab數(shù)據(jù)項(xiàng)
swap(start, end) {
let startItem = this.tabNavList[start];
let endItem = this.tabNavList[end];
// 由于直接通過索引修改數(shù)組,無法觸發(fā)響應(yīng)式,因此需要$set
this.$set(this.tabNavList, start, endItem);
this.$set(this.tabNavList, end, startItem);
},
},
};
</script>
<style lang="less">
.gnip-tab {
.gnip-tab-nav {
display: flex;
position: relative;
.tab-nav-item {
padding: 0 20px;
cursor: pointer;
line-height: 2;
}
}
.tab-nav-item.active {
color: #2d8cf0;
}
.tab-nav-track {
width: 100%;
position: relative;
height: 2px;
.track-line {
height: 2px;
background-color: #2d8cf0;
position: absolute;
transition: left 0.35s;
}
}
}
</style>
TabPane.vue
<template>
<div class="gnip-tabs-pane">
<transition :name="paneTransitionName">
<div class="tab-pane-content" v-show="$parent.activeName == name">
<slot name="default"></slot>
</div>
</transition>
</div>
</template>
<script>
export default {
props: {
// tab項(xiàng)的文本或者render函數(shù)
label: {
type: [String, Function],
},
// 每項(xiàng)標(biāo)識(shí)
name: {
type: String,
},
// 是否禁用當(dāng)前項(xiàng)
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
paneTransitionName: "enter-right",
};
},
created() {
// 統(tǒng)一tab的數(shù)據(jù)給父組件進(jìn)行處理和渲染
this.$parent.setTabBar(this);
},
};
</script>
<style lang="less">
.gnip-tabs-pane {
overflow-x: hidden;
.enter-right-enter-active {
transition: transform 0.35s;
}
.enter-right-enter {
transform: translateX(100%);
}
.enter-right-to {
transform: translateX(0);
}
}
</style>
render.js
主要用于將函數(shù)通過轉(zhuǎn)化為render函數(shù)形式的組件(前提未提供模板)
export default {
name: "RenderCell",
props: {
renderFn: Function,
},
render(h) {
return this.renderFn(h);
},
};
index.js
按需導(dǎo)出組件
import TabPane from "./TabPane.vue";
export { Tabs, TabPane };
使用
App.vue
<template>
<div class="app">
<div class="aline">
<Tabs v-model="tabName" show-track-bg>
<TabPane label="首頁" name="name1">首頁</TabPane>
<TabPane label="圖書詳情頁" name="name2" disabled>圖書詳情頁</TabPane>
<TabPane label="個(gè)人主頁" name="name3">個(gè)人主頁</TabPane>
<TabPane :label="labelRender" name="name4">購物車</TabPane>
</Tabs>
</div>
</div>
</div>
</template>
<script>
import { Tabs, TabPane } from "@/components/Tabs";
export default {
components: { Tabs, TabPane },
data() {
return {
tabName: "name1",
labelRender(h) {
return h("div", "購物車");
},
};
},
};
</script>
<style lang="less">
* {
margin: 0;
padding: 0;
}
.app {
padding: 20px;
button {
padding: 10px;
background-color: #008c8c;
color: #fff;
margin: 20px 0;
}
.container {
.operate {
text-align: center;
}
.aline {
width: 50%;
}
h2 {
font-weight: bold;
font-size: 20px;
}
.aline {
&:nth-child(1) {
margin-right: 20px;
}
}
display: flex;
justify-content: space-between;
}
}
.aline {
display: flex;
justify-content: center;
}
.item {
margin: 40px;
img {
width: 250px;
height: 200px;
}
ul {
margin: 0 auto;
li {
border: 1px solid red;
height: 200px;
width: 250px;
}
}
}
</style>
總結(jié)
通過上述組件的實(shí)現(xiàn),對于HTML5拖拽事件的應(yīng)用更加熟悉,關(guān)于拖拽請參考MDN文檔: developer.mozilla.org/zh-CN/docs/…
以上就是vue選項(xiàng)卡Tabs組件實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue選項(xiàng)卡Tabs組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue.js與 ASP.NET Core 服務(wù)端渲染功能整合
本文通過案例給大家詳細(xì)分析了ASP.NET Core 與 Vue.js 服務(wù)端渲染,需要的朋友可以參考下2017-11-11
前端vue3中的ref與reactive用法及區(qū)別總結(jié)
這篇文章主要給大家介紹了關(guān)于前端vue3中的ref與reactive用法及區(qū)別的相關(guān)資料,關(guān)于ref及reactive的用法,還是要在開發(fā)中多多使用,遇到響應(yīng)式失效問題,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
VUE3?Vite打包后動(dòng)態(tài)圖片資源不顯示問題解決方法
這篇文章主要給大家介紹了關(guān)于VUE3?Vite打包后動(dòng)態(tài)圖片資源不顯示問題的解決方法,可能是因?yàn)樵诓渴鸷蟮姆?wù)器環(huán)境中對中文文件名的支持不完善,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09
element?ui中el-form-item的屬性rules的用法示例小結(jié)
這篇文章主要介紹了element?ui中el-form-item的屬性rules的用法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-07-07
vue實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

