目录
完整代码 s-fullpage 和 s-fullpageItem
目标
实现类似插件 vue-fullpage.js 的全屏滚动翻页效果( vue-fullpage.js 的教程详见 https://blog.csdn.net/weixin_41192489/article/details/111104443)
实现原理(要点)
1. 使用 “子绝父相” 定位,实现上下滑动过渡动画翻页(滑动动画的实现原理,详见 https://blog.csdn.net/weixin_41192489/article/details/112271981 )
2. 通过插槽获取页面列表,根据下标变化完成翻页
this.$slots.default.forEach(item => {
this.pageList.push(item.componentOptions.propsData.page)
})
this.currentPage = this.pageList[this.currentIndex]
3. 绑定鼠标滑轮滚动事件,通过限定事件触发时间间隔,来避免连续滚动触发越级翻页
@wheel.prevent="mouseWheel"
// 鼠标滑轮滚动事件
mouseWheel(e) {
this.startTime = new Date().getTime()
// 每次滚动事件触发1s后,才会再次触发(避免鼠标滚动事件连续触发)
if (this.startTime - this.endTime > 1000) {
…… // 执行翻页事件,翻页完成后,执行 this.endTime = new Date().getTime()
}
}
4. 通过监听下标变化来切换向上翻页和向下翻页的过渡动画
watch: {
'$parent.currentIndex'(newIndex, oldindex) {
if (newIndex >= oldindex) {
// 向下翻页时,使用向下翻页的动画,默认为从下方滑入,从上方滑出
this.inClass = this.nextInClass
this.outClass = this.nextOutClass
} else {
// 向上翻页时,使用向上翻页的动画,默认为从上方滑入,从下方滑出
this.inClass = this.previousInClass
this.outClass = this.previousOutClass
}
}
},
完整代码 s-fullpage 和 s-fullpageItem
全屏滚动通过封装 父组件 s-fullpage 和 子组件 s-fullpageItem 来配合实现
s-fullpage.vue
<template>
<div @wheel.prevent="mouseWheel" class="fullpage">
<!--指示器-->
<div class="fixed__indicatorsBox" :style="indicatorsBoxStyle">
<div @click="goto(index)" :class="{'active_indicator':currentIndex === index}" class="indicator"
v-for="(item,index) in pageList" :key="index">
</div>
</div>
<slot></slot>
</div>
</template>
<script>
export default {
name: "s-fullpage",
props: {
// 默认显示页
activePage: String,
// 指示器的位置
indicatorPosition: {
type: String,
default: 'right'
}
},
mounted() {
if (this.indicatorPosition === 'right') {
this.$set(this.indicatorsBoxStyle, 'right', 0)
} else if (this.indicatorPosition === 'left') {
this.$set(this.indicatorsBoxStyle, 'left', 0)
}
this.$slots.default.forEach(item => {
this.pageList.push(item.componentOptions.propsData.page)
})
if (!this.activePage && this.pageList.length > 0) {
this.currentPage = this.pageList[0]
} else {
this.currentPage = this.activePage
this.pageList.forEach((item, index) => {
if (this.currentPage === item) {
this.currentIndex = index
}
})
}
},
data() {
return {
indicatorsBoxStyle: {},
// 所有页构成的列表
pageList: [],
// 当前显示页
currentPage: '',
// 当前显示页的下标
currentIndex: 0,
// 鼠标滑轮滚动的方向
direction: '',
// 鼠标滑轮事件开始时间
startTime: '',
// 鼠标滑轮指令执行结束时间
endTime: '',
}
},
methods: {
// 页面跳转
goto(index) {
this.currentIndex = index
this.currentPage = this.pageList[index]
},
// 鼠标滑轮滚动事件
mouseWheel(e) {
this.startTime = new Date().getTime()
// 每次滚动事件触发1s后,才会再次触发(避免鼠标滚动事件连续触发)
if (this.startTime - this.endTime > 1000) {
e = e || window.event;
if (e.wheelDelta) { //IE,谷歌浏览器滑轮事件
if (e.wheelDelta > 0) {
// 向上滚动
this.previousPage()
}
if (e.wheelDelta < 0) {
// 向下滚动
this.nextPage()
}
} else if (e.detail) { //Firefox滑轮事件
if (e.detail > 0) {
// 向上滚动
this.previousPage()
}
if (e.detail < 0) {
// 向下滚动
this.nextPage()
}
}
}
},
previousPage() {
this.direction = 'up'
this.currentIndex -= 1
// 第1页时,禁止继续向上翻页
if (this.currentIndex < 0) {
this.currentIndex = 0
}
this.currentPage = this.pageList[this.currentIndex]
this.endTime = new Date().getTime()
},
nextPage() {
this.direction = 'down'
this.currentIndex += 1
// 最后一页时,禁止继续向下翻页
if (this.currentIndex > this.pageList.length - 1) {
this.currentIndex = this.pageList.length - 1
}
this.currentPage = this.pageList[this.currentIndex]
this.endTime = new Date().getTime()
},
}
}
</script>
<style scoped>
/*指示器容器的样式*/
.fixed__indicatorsBox {
position: fixed;
top: 50%;
transform: translateY(-50%);
z-index: 999;
}
/*指示器的样式*/
.indicator {
height: 16px;
width: 16px;
border-radius: 50%;
background: white;
opacity: 0.3;
margin: 10px;
cursor: pointer;
}
/*当前指示器的样式*/
.active_indicator {
opacity: 1;
box-shadow: 0px 0px 8px 2px white;
}
.fullpage {
height: 100vh;
width: 100vw;
color: white;
background: black;
position: relative;
/*隐藏滚动条*/
overflow: hidden;
}
/*滑出——从上方*/
.slidOutFromTop {
animation: slidOutFromTop 1s;
}
@keyframes slidOutFromTop {
from {
top: 0;
}
to {
top: -100%;
}
}
/*滑出——从下方*/
.slidOutFromBottom {
animation: slidOutFromBottom 1s;
}
@keyframes slidOutFromBottom {
from {
top: 0;
}
to {
top: 100%;
}
}
/*滑入——从上方*/
.slidInFromTop {
animation: slidInFromTop 1s;
}
@keyframes slidInFromTop {
from {
top: -100%;
}
to {
top: 0;
}
}
/*滑入——从下方*/
.slidInFromBottom {
animation: slidInFromBottom 1s;
}
@keyframes slidInFromBottom {
from {
top: 100%;
}
to {
top: 0;
}
}
</style>
s-fullpageItem.vue
<template>
<transition
:enter-active-class="inClass"
:leave-active-class="outClass"
>
<div v-show="$parent.currentPage === page" class="s_fullpageItem">
<slot></slot>
</div>
</transition>
</template>
<script>
export default {
name: "s-fullpageItem",
props: {
// 页面标识,必传且不能与其他页重复!
page: {
type: String,
required: true
},
nextInClass: {
type: String,
default: 'slidInFromBottom'
},
nextOutClass: {
type: String,
default: 'slidOutFromTop'
},
previousInClass: {
type: String,
default: 'slidInFromTop'
},
previousOutClass: {
type: String,
default: 'slidOutFromBottom'
},
},
watch: {
'$parent.currentIndex'(newIndex, oldindex) {
if (newIndex >= oldindex) {
// 向下翻页时,使用向下翻页的动画,默认为从下方滑入,从上方滑出
this.inClass = this.nextInClass
this.outClass = this.nextOutClass
} else {
// 向上翻页时,使用向上翻页的动画,默认为从上方滑入,从下方滑出
this.inClass = this.previousInClass
this.outClass = this.previousOutClass
}
}
},
data() {
return {
inClass: '',
outClass: '',
}
},
}
</script>
<style scoped>
.s_fullpageItem {
position: absolute;
height: 100vh;
width: 100vw;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
/*滑出——从上方*/
.slidOutFromTop {
animation: slidOutFromTop 1s
}
@keyframes slidOutFromTop {
from {
top: 0;
}
to {
top: -100%;
}
}
/*滑出——从下方*/
.slidOutFromBottom {
animation: slidOutFromBottom 1s
}
@keyframes slidOutFromBottom {
from {
top: 0;
}
to {
top: 100%;
}
}
/*滑入——从上方*/
.slidInFromTop {
animation: slidInFromTop 1s
}
@keyframes slidInFromTop {
from {
top: -100%;
}
to {
top: 0;
}
}
/*滑入——从下方*/
.slidInFromBottom {
animation: slidInFromBottom 1s
}
@keyframes slidInFromBottom {
from {
top: 100%;
}
to {
top: 0;
}
}
</style>
使用范例
<template>
<div>
<s-fullpage activePage="2" indicatorPosition="left">
<s-fullpageItem page="1" >第1页</s-fullpageItem>
<s-fullpageItem page="2" style="background: blue">第2页</s-fullpageItem>
<s-fullpageItem page="3" style="background: green">第3页</s-fullpageItem>
</s-fullpage>
</div>
</template>
范例效果