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是什么? ...
随机推荐
- hdu2157 How many ways??
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission ...
- AtCoder Beginner Contest 181 E - Transformable Teacher (贪心,二分)
题意:有一长度为奇数\(n\)的数组\(a\),和长度为\(m\)的数组\(b\),现要求从\(b\)中选择一个数放到\(a\)中,并将\(a\)分成\((n+1)/2\)个数对,求最小的所有数对差的 ...
- c语言文件的读取和写入
文件打开类型: 文件打开输出就用: 1 #include <stdio.h> 2 3 int main() 4 { 5 FILE *fp = NULL; 6 7 fp = fopen(&q ...
- github host
更改hosts文件,地址:C:\Windows\System32\Drivers\etc 不能直接修改,将其拷贝到桌面,进行修改后,再复制到文件目录下(直接替换) 在hosts文件中添加: # git ...
- dart类详细讲解
dart 是一个面向对象的语言;面向对象有 (1)继承 (2)封装 (3)多态 dart的所有东西都是对象,所有的对象都是继承与object类 一个类通常是由属性和方法组成的哈: 在dart中如果你要 ...
- docker的网络-单主机(三种原生网络)none、host、bridge
docker的网络分为:单主机.跨主机 这篇先说:单主机 我们先说一下docker的原生网络模式 网络模式 简介 优点 使用场景 none 空网络,没有网络 此网络与外界隔离,安全度非常高 适合公司内 ...
- TCP协议与UDP协议的区别以及与TCP/IP协议的联系
先介绍下什么是TCP,什么是UDP. 1. 什么是TCP? TCP(Transmission Control Protocol,传输控制协议)是面向连接的.可靠的字节流服务,也就是说,在收发数据前,必 ...
- 操作系统 part5
1.线程安全 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者数据污染. 线程不安全就 ...
- 实现 MyBatis 流式查询的方法
基本概念流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询的好处是能够降低内存使用.如果没有流式查询,我们想要从数据库取 1000 万条记录而又没有足 ...
- 深入剖析JavaScript中的数据类型判断(typeof instanceof prototype.constructor)
关于JavaScript中的类型判断,我想大部分JavaScripter 都很清楚 typeof 和 instanceof,却很少有人知道 constructor,以及constructor与前面二 ...