今天给大家带来一款基于VueJS的3D Tab菜单,它跟我们之前分享的许多CSS3 Tab菜单不同的是,它可以随着鼠标移动呈现出3D立体的视觉效果,每个tab页面还可以通过CSS自定义封面照片。它的核心是基于vue和bootstrap实现,因此扩展起来非常方便,你可以任意添加或者减少tab页面数量,同时只要更新对应tab页面的CSS代码即可,无须修改js代码。
HTML代码:
<div id="app-container" data-tilt > <div id="app"> <vue-tabs id="tabs"> <v-tab title="First Tab" class="tab" :selected="true"> <div class="tab-content"> <div class="tab-image first-image"></div> <div class="tab-content-text"> <h1>First Header</h1> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt nu aliqua. Sollicit udin purus faucibus ornare aliquam ultrices sagittis orci a scelerisque a consectetur atna purus.</p> </div> </div> </v-tab> <v-tab title="Second Tab" class="tab"> <div class="tab-content"> <div class="tab-image second-image"></div> <div class="tab-content-text"> <h1>Second Header</h1> <p>Ac tortor vitae purus faucibus ornare suspendisse uis tristique sed nisi. Consectetur libero id ax faucibus in ornare faucibus nislt udin purus fi faucibus ac ornare aliquam ultrices in purus faucibu.</p> </div> </div> </v-tab> <v-tab title="Third Tab" class="tab"> <div class="tab-content"> <div class="tab-image third-image"></div> <div class="tab-content-text"> <h1>Third Header</h1> <p>Scelerisque fermentum dui faucibus in ornare id. Amet consectetur adipiscing elit duis tristique sollicitudin purus faucibus ornare aliquam ultrices sagittis nibh elit duis nubro tristique itae purus faucibus.</p> </div> </div> </v-tab> </vue-tabs> </div> </div>
当然,你要引入vue脚本库和bootstrap脚本库:
<script src='js/vue.min.js'></script> <script src='js/bootstrap.min.js'></script>
这个插件的核心代码如下:
/*! * vue-nav-tabs v0.5.7 * (c) 2018-present cristij <joracristi@gmail.com> * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.vueTabs = {}))); }(this, (function (exports) { 'use strict'; var nestRE = /^(attrs|props|on|nativeOn|class|style|hook)$/; var babelHelperVueJsxMergeProps = function mergeJSXProps(objs) { return objs.reduce(function (a, b) { var aa, bb, key, nestedKey, temp; for (key in b) { aa = a[key]; bb = b[key]; if (aa && nestRE.test(key)) { // normalize class if (key === 'class') { if (typeof aa === 'string') { temp = aa; a[key] = aa = {}; aa[temp] = true; } if (typeof bb === 'string') { temp = bb; b[key] = bb = {}; bb[temp] = true; } } if (key === 'on' || key === 'nativeOn' || key === 'hook') { // merge functions for (nestedKey in bb) { aa[nestedKey] = mergeFn(aa[nestedKey], bb[nestedKey]); } } else if (Array.isArray(aa)) { a[key] = aa.concat(bb); } else if (Array.isArray(bb)) { a[key] = [aa].concat(bb); } else { for (nestedKey in bb) { aa[nestedKey] = bb[nestedKey]; } } } else { a[key] = b[key]; } } return a; }, {}); }; function mergeFn(a, b) { return function () { a.apply(this, arguments); b.apply(this, arguments); }; } var VueTabs = { name: 'vue-tabs', props: { activeTabColor: String, activeTextColor: String, disabledColor: String, disabledTextColor: String, /** * Tab title position: center | bottom | top */ textPosition: { type: String, default: 'center' }, /** * Tab type: tabs | pills */ type: { type: String, default: 'tabs' }, direction: { type: String, default: 'horizontal' }, /** * Centers the tabs and makes the container div full width */ centered: Boolean, value: [String, Number, Object] }, data: function data() { return { activeTabIndex: 0, tabs: [] }; }, computed: { isTabShape: function isTabShape() { return this.type === 'tabs'; }, isStacked: function isStacked() { return this.direction === 'vertical'; }, classList: function classList() { var navType = this.isTabShape ? 'nav-tabs' : 'nav-pills'; var centerClass = this.centered ? 'nav-justified' : ''; var isStacked = this.isStacked ? 'nav-stacked' : ''; return 'nav ' + navType + ' ' + centerClass + ' ' + isStacked; }, stackedClass: function stackedClass() { return this.isStacked ? 'stacked' : ''; }, activeTabStyle: function activeTabStyle() { return { backgroundColor: this.activeTabColor, color: this.activeTextColor }; } }, methods: { navigateToTab: function navigateToTab(index, route) { this.changeTab(this.activeTabIndex, index, route); }, activateTab: function activateTab(index) { this.activeTabIndex = index; var tab = this.tabs[index]; tab.active = true; this.$emit('input', tab.title); }, changeTab: function changeTab(oldIndex, newIndex, route) { var oldTab = this.tabs[oldIndex] || {}; var newTab = this.tabs[newIndex]; if (newTab.disabled) return; this.activeTabIndex = newIndex; oldTab.active = false; newTab.active = true; this.$emit('input', this.tabs[newIndex].title); this.$emit('tab-change', newIndex, newTab, oldTab); this.tryChangeRoute(route); }, tryChangeRoute: function tryChangeRoute(route) { if (this.$router && route) { this.$router.push(route); } }, addTab: function addTab(item) { var index = this.$slots.default.indexOf(item.$vnode); this.tabs.splice(index, 0, item); }, removeTab: function removeTab(item) { var tabs = this.tabs; var index = tabs.indexOf(item); if (index > -1) { tabs.splice(index, 1); } }, getTabs: function getTabs() { if (this.$slots.default) { return this.$slots.default.filter(function (comp) { return comp.componentOptions; }); } return []; }, findTabAndActivate: function findTabAndActivate(tabNameOrIndex) { var indexToActivate = this.tabs.findIndex(function (tab, index) { return tab.title === tabNameOrIndex || index === tabNameOrIndex; }); if (indexToActivate === this.activeTabIndex) return; if (indexToActivate !== -1) { this.changeTab(this.activeTabIndex, indexToActivate); } else { this.changeTab(this.activeTabIndex, 0); } }, renderTabTitle: function renderTabTitle(index) { var position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top'; var h = this.$createElement; if (this.tabs.length === 0) return; var tab = this.tabs[index]; var active = tab.active, title = tab.title; var titleStyles = { color: this.activeTabColor }; if (position === 'center') titleStyles.color = this.activeTextColor; var simpleTitle = h( 'span', { 'class': 'title title_' + position, style: active ? titleStyles : {} }, [position === 'center' && this.renderIcon(index), title] ); if (tab.$slots.title) return tab.$slots.title; if (tab.$scopedSlots.title) return tab.$scopedSlots.title({ active: active, title: title, position: position, icon: tab.icon, data: tab.tabData }); return simpleTitle; }, renderIcon: function renderIcon(index) { var h = this.$createElement; if (this.tabs.length === 0) return; var tab = this.tabs[index]; var icon = tab.icon; var simpleIcon = h( 'i', { 'class': icon }, ['xA0'] ); if (!tab.$slots.title && icon) return simpleIcon; }, tabStyles: function tabStyles(tab) { if (tab.disabled) { return { backgroundColor: this.disabledColor, color: this.disabledTextColor }; } return {}; }, renderTabs: function renderTabs() { var _this = this; var h = this.$createElement; return this.tabs.map(function (tab, index) { if (!tab) return; var route = tab.route, id = tab.id, title = tab.title, icon = tab.icon, tabId = tab.tabId; var active = _this.activeTabIndex === index; return h( 'li', babelHelperVueJsxMergeProps([{ attrs: { name: 'tab', id: 't-' + tabId, 'aria-selected': active, 'aria-controls': 'p-' + tabId, role: 'tab' }, 'class': ['tab', { active: active }, { disabled: tab.disabled }], key: title }, { on: { 'click': function click($event) { for (var _len = arguments.length, attrs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { attrs[_key - 1] = arguments[_key]; } (function () { return !tab.disabled && _this.navigateToTab(index, route); }).apply(undefined, [$event].concat(attrs)); } } }]), [_this.textPosition === 'top' && _this.renderTabTitle(index, _this.textPosition), h( 'a', babelHelperVueJsxMergeProps([{ attrs: { href: '#', role: 'tab' }, style: active ? _this.activeTabStyle : _this.tabStyles(tab), 'class': [{ 'active_tab': active }, 'tabs__link'] }, { on: { 'click': function click($event) { for (var _len2 = arguments.length, attrs = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { attrs[_key2 - 1] = arguments[_key2]; } (function (e) { e.preventDefault(); return false; }).apply(undefined, [$event].concat(attrs)); } } }]), [_this.textPosition !== 'center' && !tab.$slots.title && _this.renderIcon(index), _this.textPosition === 'center' && _this.renderTabTitle(index, _this.textPosition)] ), _this.textPosition === 'bottom' && _this.renderTabTitle(index, _this.textPosition)] ); }); } }, render: function render() { var h = arguments[0]; var tabList = this.renderTabs(); return h( 'div', { 'class': ['vue-tabs', this.stackedClass] }, [h( 'div', { 'class': [{ 'nav-tabs-navigation': !this.isStacked }, { 'left-vertical-tabs': this.isStacked }] }, [h( 'div', { 'class': ['nav-tabs-wrapper', this.stackedClass] }, [h( 'ul', { 'class': this.classList, attrs: { role: 'tablist' } }, [tabList] )] )] ), h( 'div', { 'class': ['tab-content', { 'right-text-tabs': this.isStacked }] }, [this.$slots.default] )] ); }, watch: { tabs: function tabs(newList) { if (newList.length > 0 && !this.value) { if (newList.length <= this.activeTabIndex) { this.activateTab(this.activeTabIndex - 1); } else { this.activateTab(this.activeTabIndex); } } if (newList.length > 0 && this.value) { this.findTabAndActivate(this.value); } }, value: function value(newVal) { this.findTabAndActivate(newVal); } } }; var VTab = { name: 'v-tab', props: { title: { type: String, default: '' }, icon: { type: String, default: '' }, tabData: { default: null }, /*** * Function to execute before tab switch. Return value must be boolean * If the return result is false, tab switch is restricted */ beforeChange: { type: Function }, id: String, route: { type: [String, Object] }, disabled: Boolean, transitionName: String, transitionMode: String }, computed: { isValidParent: function isValidParent() { return this.$parent.$options.name === 'vue-tabs'; }, hash: function hash() { return '#' + this.id; }, tabId: function tabId() { return this.id ? this.id : this.title; } }, data: function data() { return { active: false, validationError: null }; }, mounted: function mounted() { this.$parent.addTab(this); }, destroyed: function destroyed() { if (this.$el && this.$el.parentNode) { this.$el.parentNode.removeChild(this.$el); } this.$parent.removeTab(this); }, render: function render() { var h = arguments[0]; return h( 'section', { 'class': 'tab-container', attrs: { id: 'p-' + this.tabId, 'aria-labelledby': 't-' + this.tabId, role: 'tabpanel' }, directives: [{ name: 'show', value: this.active }] }, [this.$slots.default] ); } }; var VueTabsPlugin = { install: function install(Vue) { Vue.component('vue-tabs', VueTabs); Vue.component('v-tab', VTab); } }; // Automatic installation if Vue has been added to the global scope. if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(VueTabsPlugin); window.VueTabs = VueTabsPlugin; } exports['default'] = VueTabsPlugin; exports.VueTabs = VueTabs; exports.VTab = VTab; Object.defineProperty(exports, '__esModule', { value: true }); })));
一切准备就绪后,我们可以在页面上调用插件的初始化代码,即可完成这个vue tab插件。
new Vue({ el:"#app", }); $('.js-tilt').tilt({ })
源码下载链接:
https://www.html5tricks.com/download/vuejs-3d-tab-menu.rar
扫描下方二维码关注公众号
发送消息 jymm 获取 解压密码