Vue.js 实现的 3D Tab菜单
今天给大家带来一款基于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 获取 解压密码

Vue.js 实现的 3D Tab菜单的更多相关文章
- Vue.js 递归组件实现树形菜单
最近看了 Vue.js 的递归组件,实现了一个最基本的树形菜单. 项目结构: main.js 作为入口,很简单: import Vue from 'vue' Vue.config.debug = tr ...
- Vue.js学习 — 微信公众号菜单编辑器(一)
学习里一段时间Vue.js,于是想尝试着做一个像微信平台里那样的菜单编辑器,在这里分享下 具体样式代码查看项目github 创建一个vue实例 <!DOCTYPE html> <ht ...
- vue.js 利用SocketCluster实现动态添加数据及排序
直接上代码 html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...
- Vue.js实现tab切换效果
利用Vue实现简易tab切换效果 1.1 在我们平时浏览网站的时候,经常看到的特效有图片轮播.导航子菜单的隐藏.tab标签的切换等等.这段时间学习了vue后,开始要写出一些简单的特效. 1.2 实现思 ...
- zepto.js swipe实现触屏tab菜单
今天我们来说下zepto.js,有兴趣的朋友可以先进这个网站“http://zeptojs.com/” ,这个可以说是手机里的jquery,但是它取消了hover,加上了swipe及tap这两个触屏功 ...
- 用Vue.js递归组件构建一个可折叠的树形菜单
在Vue.js中一个递归组件调用的是其本身,如: Vue.component('recursive-component', { template: `<!--Invoking myself! ...
- (vue.js)Vue element tab 每个tab用一个路由来管理?
(vue.js)Vue element tab 每个tab用一个路由来管理? 来源:网络整理 时间:2017/5/13 0:24:01 关键词: 关于网友提出的“ (vue.js) ...
- Vue.js实现后台菜单切换
因为刚初学Vue.js,暂时遇到以下问题,待之后解决 点击父节点,怎么隐藏显示子节点 点击父节点或子节点,切换li的class="active" iframe怎么自适应高度 思路 ...
- 【Vue】转-Vue.js经典开源项目汇总
版权声明:本文为EnweiTech原创文章,未经博主允许不得转载. https://blog.csdn.net/English0523/article/details/88694219 Vue是什么? ...
随机推荐
- Selenium刚玩一会儿,就感受了私人秘书的体验
学习python的过程中,少不了接触第三方库,毕竟作为胶水语言python的强大之处也就是第三方库体量庞大,无疑大大增强了python的战斗力. 有时候想完成网页自动化操作,这时候Selenium进入 ...
- Docker运行时资源限制
Docker 运行时资源限制Docker 基于 Linux 内核提供的 cgroups 功能,可以限制容器在运行时使用到的资源,比如内存.CPU.块 I/O.网络等. 内存限制概述Docker 提供的 ...
- Redis Cluster 分布式集群(下)
Redis Cluster 搭建(工具) 环境准备 节点 IP 端口 节点① 172.16.1.121 6379,6380 节点② 172.16.1.122 6379,6380 节点③ 172.16. ...
- Keepalived+LVS实现LNMP网站的高可用部署
Keepalived+LVS实现LNMP网站的高可用部署 项目需求 当我们访问某个网站的时候可以在浏览器中输入IP或者域名链接到Web Server进行访问,如果这个Web Server挂了, ...
- 牛客网多校第4场 A.Ternary String 【欧拉降幂】
题目:戳这里 学习博客:戳这里 欧拉函数的性质: ① N是不为0的整数.φ(1)=1(唯一和1互质的数就是1本身) ② 除了N=2,φ(N)都是偶数. ③ 小于N且与N互质的所有数的和是φ(n)*n/ ...
- AF_INET与套接字SOCKET
一.SOCKET--套接字 套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信.这就是所谓的进程间通信(Inter Process Commu ...
- Sentry & React
Sentry & React https://docs.sentry.io/platforms/javascript/guides/react/ https://docs.sentry.io/ ...
- i18n 和 L10n (internationalization and localization) 国际化与本地化(具有全球战略眼光的公司企业的必由之路)
i18n 和 L10n (internationalization and localization) 国际化与本地化(具有全球战略眼光的公司企业的必由之路) 1 1 https://zh.wiki ...
- Fullscreen API All In One
Fullscreen API All In One 全屏显示 https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API https ...
- auto switch HTTP protocol Chrome Extension
auto switch HTTP protocol Chrome Extension HTTPS auto switch to HTTP VPN https://chrome.google.com/w ...