vue選項(xiàng)卡Tabs組件實(shí)現(xiàn)示例詳解
概述
前端項(xiàng)目中,多數(shù)頁(yè)面涉及到選項(xiàng)卡切換,包括路由切換,指令v-if等,本質(zhì)上其實(shí)和選項(xiàng)卡切換思想差不多,如果是個(gè)簡(jiǎn)單的選項(xiàng)卡,還是很簡(jiǎn)單的,我們也不需要什么組件庫(kù)的組件,自己也能幾行代碼寫出來(lái),但是涉及到動(dòng)畫,尺寸計(jì)算,拖拽的功能的時(shí)候,多數(shù)情況下,自己寫還是要花點(diǎn)時(shí)間的,組件庫(kù)就提供了現(xiàn)成的,拿來(lái)改改樣式就行,為了對(duì)這個(gè)組件更加深入的理解,這里自己實(shí)現(xiàn)一個(gè)帶拖拽,過(guò)渡的tabs組件。
效果圖
實(shí)現(xiàn)過(guò)程
組件分析
- 組件包含兩部分:Tabs組件和TabPane組件,參考絕大多數(shù)組件庫(kù)的習(xí)慣
- 組件主要分為需要點(diǎn)擊的tab欄和下面對(duì)應(yīng)的內(nèi)容塊
- 我們需要對(duì)內(nèi)容區(qū)和選項(xiàng)卡點(diǎn)擊區(qū)分別加上過(guò)渡動(dòng)畫,提升用戶體驗(yàn)
- 最后需要加上拖拽調(diào)整選項(xiàng)卡順序的功能
所需的前置知識(shí)
- 熟悉vue內(nèi)置transition組件
- 深入掌握vue父子組件通信,除開(kāi)emit和props,還需要掌握inject,emit和props,還需要掌握inject,emit和props,還需要掌握inject,parent,vnode,渲染函數(shù)等等,這些業(yè)務(wù)開(kāi)發(fā)中用的不多,但是組件庫(kù)里面比較常見(jiàn)。
- 了解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, // 拖拽開(kāi)始的哪項(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; // 頁(yè)面渲染任務(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)于拖拽請(qǐng)參考MDN文檔: https://developer.mozilla.org/zh-CN/docs/Web/API/DragEvent,實(shí)現(xiàn)拖拽需要清楚關(guān)于拖拽相關(guān)的幾個(gè)事件 */ // 開(kāi)始拖拽 handelDragstart(item, event, index) { // 說(shuō)明是拖拽的當(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(); // 說(shuō)明拖動(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]; // 由于直接通過(guò)索引修改數(shù)組,無(wú)法觸發(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ù)通過(guò)轉(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="首頁(yè)" name="name1">首頁(yè)</TabPane> <TabPane label="圖書詳情頁(yè)" name="name2" disabled>圖書詳情頁(yè)</TabPane> <TabPane label="個(gè)人主頁(yè)" name="name3">個(gè)人主頁(yè)</TabPane> <TabPane :label="labelRender" name="name4">購(gòu)物車</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", "購(gòu)物車"); }, }; }, }; </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é)
通過(guò)上述組件的實(shí)現(xiàn),對(duì)于HTML5拖拽事件的應(yīng)用更加熟悉,關(guān)于拖拽請(qǐng)參考MDN文檔: developer.mozilla.org/zh-CN/docs/…
以上就是vue選項(xiàng)卡Tabs組件實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue選項(xiàng)卡Tabs組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue.js與 ASP.NET Core 服務(wù)端渲染功能整合
本文通過(guò)案例給大家詳細(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的用法,還是要在開(kāi)發(fā)中多多使用,遇到響應(yīng)式失效問(wèn)題,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08VUE3?Vite打包后動(dòng)態(tài)圖片資源不顯示問(wèn)題解決方法
這篇文章主要給大家介紹了關(guān)于VUE3?Vite打包后動(dòng)態(tài)圖片資源不顯示問(wèn)題的解決方法,可能是因?yàn)樵诓渴鸷蟮姆?wù)器環(huán)境中對(duì)中文文件名的支持不完善,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09Vue.directive 自定義指令的問(wèn)題小結(jié)
這篇文章主要介紹了Vue.directive 自定義指令的問(wèn)題小結(jié),需要的朋友可以參考下2018-03-03element?ui中el-form-item的屬性rules的用法示例小結(jié)
這篇文章主要介紹了element?ui中el-form-item的屬性rules的用法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-07-07vue實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03