前端实战小案例--fullpage导航栏实现
想练习更多前端案例,请进个人主页,点击前端实战案例->传送门
觉得不错的记得点个赞?支持一下我0.0!谢谢了!
不积跬步无以至千里,不积小流无以成江海。
技术栈:vue + vue-fullpage
效果图如下:( 这是一个vue组件)
代码如下:
Fullpage.vue
<template>
<div class="content">
<!--导航菜单-->
<navigation :cur-index="curIndex" @select="selectItem"></navigation>
<!--右侧小导航菜单-->
<div id="menu" :class="playingStateCls">
<span class="active" data-menuanchor='/page1'><a href="#/page1"></a></span>
<span data-menuanchor='/page2'><a href="#/page2"></a></span>
<span data-menuanchor='/page3'><a href="#/page3"></a></span>
<span data-menuanchor='/page4'><a href="#/page4"></a></span>
<span data-menuanchor='/page5'><a href="#/page5"></a></span>
<span data-menuanchor='/page6'><a href="#/page6"></a></span>
<div class="play-btn" @click.stop="togglePlay" >
<i class="iconfont icon-play" v-show="!playingState"></i>
<i class="iconfont icon-pause" v-show="playingState"></i>
</div>
<audio loop ref="audio" src="../../assets/audio/wuyuetian.mp3"></audio>
</div>
<full-page :options="options" ref="fullpage">
<div class="section">
<home></home>
</div>
<div class="section">
<about-me></about-me>
</div>
<div class="section">
<div class="box3">
section3
</div>
</div>
<div class="section">
<div class="box4">
section4
</div>
</div>
<div class="section">
<div class="box5">
section5
</div>
</div>
<div class="section">
<div class="box6">
section6
</div>
</div>
</full-page>
</div>
</template>
<script>
import Home from "./Home/Home";
import Navigation from "../Navigation/Navigation";
import AboutMe from "./AboutMe/AboutMe";
export default {
name: "Fullpage",
data () {
return {
curIndex: 1,
playingState: false,
options: {
scrollOverflow: true,
scrollBar: false,
navigation: true,
anchors: ['/page1', '/page2', '/page3','/page4', '/page5', '/page6'],
menu: '#menu',
sectionsColor: ['#41b883', '#F3F2F3', '#0798ec', '#fec401', '#1bcee6', '#925B4B']
}
}
},
methods: {
selectItem(index){
// 当在大导航菜单选择某项时,让fullpage滚动到对应的page
this.curIndex = index
this.$refs.fullpage.api.moveTo(index)
},
togglePlay(){
// 切换歌曲播放状态
if(!this.playingState){
this.$refs.audio.play()
}else{
this.$refs.audio.pause()
}
this.playingState = !this.playingState
},
refreshPath(){
// 在页面刷新的时候,判断当前路径,并刷新 this.curIndex 的值
let path = this.$route.path
let index
if(path === '/'){
index = 0
}else{
index = this.options.anchors.findIndex((item) => {
return item === path
})
}
this.curIndex = index + 1
}
},
computed: {
playingStateCls(){
// 播放状态不同决定了音乐的背景图片是否处于旋转状态
if(this.playingState){
return 'running'
}else{
return 'paused'
}
}
},
watch: {
$route(newVal){
// 这个只有在页面路由变化时才触发,页面刷新时不触发,所以需要为页面刷新写一个处理函数 refreshPath()
// 路由变化时,也即滚动fullpage页面时,让大导航菜单跟着刷新到对应的导航菜单项
// 至于为什么要这样处理,主要是fullpage的事件回调函数有bug
let index = this.options.anchors.findIndex((item) => {
return item === newVal.path
})
this.curIndex = index + 1
}
},
created() {
this.refreshPath()
},
components: {
Home,
Navigation,
AboutMe
}
}
</script>
<style lang="less" scoped>
@keyframes breath {
0%{
box-shadow: 0 0 0 0 rgba(37, 143, 184, 1);
background: #fff;
}
100%{
box-shadow: 0 0 0 10px rgba(37, 143, 184, 0.1);
background: #dddedc;
}
}
@keyframes imgRotate {
0%{
transform: rotateZ(0);
}
100%{
transform: rotateZ(360deg);
}
}
.content{
#menu{
position: fixed;
top: 50%;
right: 3%;
transform: translateY(-50%);
z-index: 99;
&.running{
&::after{
animation-play-state: running;
}
}
&.paused{
&::after{
animation-play-state: paused;
}
}
&::after{
position: absolute;
bottom: -40px;
right: -13px;
width: 40px;
height: 40px;
background: url("../../assets/images/music.jpg") no-repeat;
background-size: contain;
border-radius: 50%;
transition: background 0.3s ease-in;
content: '';
animation: imgRotate 6s infinite linear;
}
&::before{
position: absolute;
top: 24px;
left: 0;
width: 6px;
height: 230px;
display: block;
border-right: 1px dashed #8c7576;
content: '';
}
span{
display: block;
position: relative;
width: 14px;
height: 14px;
border-radius: 50%;
background: #dddedc;
margin: 24px auto!important;
cursor: pointer;
z-index: 999;
&.active{
background: #fff;
animation: breath 2s infinite;
}
&::before{
width: 6px;
height: 6px;
content: '';
border-radius: 50%;
background: @color-blue;
display: block;
position: absolute;
left: 50%;
margin-left: -3px;
top: 50%;
margin-top: -3px;
}
a{
position: relative;
display: block;
width: 100%;
height: 100%;
z-index: 1000;
}
}
.play-btn{
position: absolute;
bottom: -40px;
right: -13px;
width: 40px;
height: 40px;
z-index: 100;
background: transparent;
border-radius: 50%;
cursor: pointer;
.iconfont{
width: 40px;
height: 40px;
font-size: 28px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
opacity: 0;
&:hover{
opacity: 1;
background: rgba(255,255,255,0.5);
}
}
}
}
}
</style>
Navigation.vue
<template>
<div class="box">
<div class="nav-wrapper">
<div class="nav-toggle" @click="toggleNav" :class="visitedCls">
<div class="icon"></div>
</div>
<div class="nav-text">导航</div>
</div>
<transition name="slide">
<div class="nav-menu-wrapper" v-show="showFlag" ref="NavMenuWrapper">
<div class="nav-menu">
<ul class="items">
<li class="item" :key="item.id" v-for="item in menuList">
<a href="javascript:;" :class="{'actived' : item.id === curIndex}" @click="selectItem(item.id)" >{{item.name}}<span class="line"></span></a>
</li>
</ul>
<div class="logo">
<img src="../../assets/images/logo.png" alt="">
<span class="text">朽木</span>
<div class="wrapper">
<span class="site-url">xmzd.wang</span>
<span class="qq">QQ:1061750360</span>
</div>
<span class="text">自雕</span>
</div>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "Navgation",
data(){
return {
showFlag: false
}
},
props: {
curIndex: {
type: Number,
default: 1
},
menuList: {
type: Array,
default: () => [
{id:1, name: "首页"},
{id:2, name: "关于我"},
{id:3, name: "技能掌握"},
{id:4, name: "我的经历"},
{id:5, name: "我的作品"},
{id:6, name: "联系我"},
]
}
},
methods: {
toggleNav(){
this.showFlag = !this.showFlag
if(this.showFlag){
this._disabledMouseWheel(this.$refs.NavMenuWrapper)
}
},
selectItem(id){
// 当某项被选中的时候,将当前的curIndex改为被选择的id,然后向父组件派发事件,并告诉哪一项被选择了,然后让父组件跳转到fullpage的第几页
this.$emit("select", id)
this.toggleNav()
},
_scrollFunc(evt) {
evt = evt || window.event;
if(evt.preventDefault) {
// Firefox
evt.preventDefault();
evt.stopPropagation();
} else {
// IE
evt.cancelBubble=true;
evt.returnValue = false;
}
return false;
},
_disabledMouseWheel(obj) {
// 禁用某个元素的鼠标滚轮事件
if (document.addEventListener) {
obj.addEventListener('DOMMouseScroll', this._scrollFunc, false);
}//W3C
obj.onmousewheel = this._scrollFunc;//IE/Opera/Chrome
}
},
computed: {
visitedCls(){
if(this.showFlag){
return 'visited'
}else{
return ''
}
}
}
}
</script>
<style lang="less" scoped>
.box{
.nav-wrapper{
position: fixed;
right: 2%;
top: 4%;
z-index: 999;
.nav-toggle{
width: 60px;
height: 60px;
border-radius: 50%;
background: transparent;
box-shadow: 0 3px 0 rgba(0,0,0,0.2);
cursor: pointer;
transition: all 0.5s ease;
&:hover{
background: @color-blue;
}
&:hover+.nav-text{
opacity: 1;
}
.icon{
position: absolute;
top: 28px;
left: 15px;
width: 30px;
height: 4px;
background-color: white;
border-radius: 2px;
transition: all 0.5s ease;
&::before{
content: '';
position: absolute;
top: -10px;
width: 30px;
height: 4px;
background-color: white;
border-radius: 2px;
transition: all 0.5s ease;
transform-origin: left center;
}
&::after{
content: '';
position: absolute;
top: 10px;
width: 30px;
height: 4px;
background-color: white;
border-radius: 2px;
transition: all 0.5s ease;
transform-origin: left center;
}
}
}
.visited{
background: @color-CCC;
.icon{
width: 0;
&::before{
transform: translate(3px,-1px) rotateZ(45deg);
}
&::after{
transform: translate(3px,1px) rotateZ(-45deg);
}
}
}
.nav-text{
opacity: 0;
width: 60px;
height: 30px;
background: @color-blue;
position: absolute;
top: 73px;
right: 0;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
text-align: center;
line-height: 30px;
color: #fff;
font-size: 12px;
transition: all 0.5s ease;
&::before{
content: "";
position: absolute;
right: 22px;
top: -8px;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-bottom: 8px solid @color-blue;
border-right: 8px solid transparent;
}
}
}
.nav-menu-wrapper{
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0,0,0,0.3);
z-index: 998;
box-sizing: border-box;
&.slide-enter-active,
&.slide-leave-active{
transition: all 0.5s ease;
.nav-menu{
transition: all 0.5s ease;
.items,
.logo{
transition: all 0.5s ease;
}
}
}
&.slide-enter,
&.slide-leave-to{
opacity: 0;
.nav-menu{
right: -18%;
.items,
.logo{
right: -13%;
}
}
}
.nav-menu{
position: absolute;
right: 0;
width: 18%;
border-bottom: @color-grey 100vh solid;
border-left: transparent 180px solid;
border-top: none;
border-right: none;
.items{
position: fixed;
top: 50%;
right: 1%;
transform: translateY(-50%);
.item{
width: 156px;
display: flex;
justify-content: flex-end;
a{
position: relative;
display: inline-block;
padding: 15% 30px;
color: #686967;
font-size: 24px;
transition: all 0.5s ease;
&.actived{
color: @color-blue;
.line{
width: 100%;
}
}
&:hover{
color: @color-blue;
.line{
width: 100%;
}
}
.line{
position: absolute;
top: 50%;
left: 0;
right: 0;
bottom: 0;
margin: 0 auto;
display: block;
width: 0;
height: 2px;
background: @color-blue;
transition: all 0.5s ease;
}
}
}
}
.logo{
position: fixed;
bottom: 5%;
right: 5%;
display: flex;
justify-content: center;
img{
width: 50px;
height: 50px;
}
.text{
font-size: 34px;
}
.wrapper{
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
text-align: center;
.site-url{
}
.qq{
font-weight: 600;
color: #fff;
background: black;
padding: 0 5px;
border-radius: 4px;
}
}
}
}
}
}
</style>
Home.vue
<template>
<div class="home">
<div class="header">
<p class="title">Hello,I'm Snake</p>
<p class="motto">Never, never, never, never give up</p>
<p class="name">我叫王越</p>
<p class="desc">一块自我雕刻的朽木</p>
<p class="email"><i class="iconfont icon-email"></i>m17364250251@163.com</p>
</div>
</div>
</template>
<script>
export default {
name: "Home",
}
</script>
<style lang="less" scoped>
/*font-family: Arial, Helvetica, sans-serif;*/
/*font-family: 'Shadows Into Light', cursive;*/
.home{
width: 100%;
height: 100%;
background: url("../../../assets/images/background1.jpg") no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
.header{
text-align: center;
color: #fff;
.title{
font-family: 'Bree Serif', 'Arial', 'Helvetica', 'sans-serif', serif;
font-size: 70px;
font-weight: 800;
margin: 20px;
}
.motto{
font-family: 'Bree Serif', 'Arial', 'Helvetica', 'sans-serif', serif;
font-size: 25px;
font-weight: 800;
margin: 20px;
}
.name{
font-size: 20px;
}
.desc{
font-size: 20px;
}
.email{
font-size: 20px;
.iconfont{
font-size: 18px;
font-weight: 800;
margin-right: 10px;
}
}
}
}
</style>