目标
实现类似插件 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>
范例效果