Nuxt3 实现类 fullpage 效果在Nuxt3 项目中遇到一个整屏滚动翻页效果的需求,类似fullpage.js
在Nuxt3 项目中遇到一个整屏滚动翻页效果的需求,类似fullpage.js 可实现的功能,可惜fullpage 是收费软件且在网上搜索的更多是将fullpage低版本本地化使用,实践下来稍微显得有些麻烦,fullpage 似乎依赖jQuery,可能并不是一个很好的方案。
本文记录用 js + css 实现的一种方案,也算是个求助帖,各位有没有更优的实现方案。
业务需求
页面中有 5 个模块,鼠标滚轮操作控制当前模块的上下整屏滚动翻页。导航栏固定高度 76px,底部栏固定高度 635px。当滚动到底部栏时,底部栏和最后一个模块同时出现在视口内。
实现思路
将整个页面设置 relative 定位,每个模块的高度固定设置为 calc(100vh - 76px),监听每个模块的 wheel 事件,通过判断滚动方向修改整个页面的 translateY 属性,例如,在第一个模块上先禁止默认的滚动事件,在向下滚动时设置整个页面 transform: translateY(calc(-100vh + 76px)) 。
页面代码
项目中用了 tailwindcss,postcss-px-to-viewport 插件做适配方案,因为postcss-px-to-viewport 无法对内联样式中属性自动转换 vw,所以没有在style 中修改属性值,只能动态修改 clasList 。
<div ref="homePageRef" class="w-full relative main page1">
<section ref="section1Ref" class="w-full h-[calc(100vh_-_76px)]">
<div
class="w-full h-full flex flex-col justify-between bg-slate-300 text-white text-2xl font-medium"
>
<span>SECTION1</span>
<span>SECTION1</span>
</div>
</section>
<section ref="section2Ref" class="w-full h-[calc(100vh_-_76px)]">
<div
class="w-full h-full flex flex-col justify-between bg-slate-700 text-white text-2xl font-medium"
>
<span>SECTION2</span>
<span>SECTION2</span>
</div>
</section>
<section ref="section3Ref" class="w-full h-[calc(100vh_-_76px)]">
<div
class="w-full h-full flex flex-col justify-between bg-slate-300 text-white text-2xl font-medium"
>
<span>SECTION3</span>
<span>SECTION3</span>
</div>
</section>
<section ref="section4Ref" class="w-full h-[calc(100vh_-_76px)]">
<div
class="w-full h-full flex flex-col justify-between bg-slate-700 text-white text-2xl font-medium"
>
<span>SECTION4</span>
<span>SECTION4</span>
</div>
</section>
<section ref="section5Ref" class="w-full h-[calc(100vh_-_76px)]">
<div
class="w-full h-full flex flex-col justify-between bg-slate-300 text-white text-2xl font-medium"
>
<span>SECTION5</span>
<span>SECTION5</span>
</div>
</section>
<section ref="footerRef" class="w-full h-[635px]">
<footer
class="w-full h-full bg-lime-600 flex flex-col justify-between text-white text-2xl font-medium"
>
<span>footer</span>
<span>footer</span>
</footer>
</section>
</div>
CSS 代码
<style scoped>
.main {
transition: all 1s cubic-bezier(0.825, 0, 0.5, 1);
}
.page1 {
transform: translateY(0px);
}
.page2 {
transform: translateY(calc(-100vh + 76px));
}
.page3 {
transform: translateY(calc(-200vh + 152px));
}
.page4 {
transform: translateY(calc(-300vh + 228px));
}
.page5 {
transform: translateY(calc(-400vh + 304px));
}
.page6 {
transform: translateY(calc(-400vh - 331px));
}
</style>
其中,page1 是默认状态,translateY 为 0,整个页面没有偏移,默认展示第一个模块。翻到第二个模块,此时整个页面向上偏移第一个模块的高度,即calc(-100vh + 76px)。page3 时就是偏移了两个模块的高度,即 -100vh + 76px - 100vh + 76px,以此类推,直到顶部栏,偏移计算只需要在第五个模块的基础上减去footer的高度,即 -400vh + 76px + 76px + 76px + 76px - 635px 。
JS 代码
<script setup lang="ts">
import { ref, onMounted } from "vue";
const homePageRef = ref<HTMLElement | null>(null);
const section1Ref = ref<HTMLElement | null>(null);
const section2Ref = ref<HTMLElement | null>(null);
const section3Ref = ref<HTMLElement | null>(null);
const section4Ref = ref<HTMLElement | null>(null);
const section5Ref = ref<HTMLElement | null>(null);
const footerRef = ref<HTMLElement | null>(null);
function section1Wheel(deltaY: number) {
if (deltaY > 0) {
homePageRef.value?.classList.add("page2");
}
homePageRef.value?.classList.remove("page1");
}
function section2Wheel(deltaY: number) {
if (deltaY < 0) {
homePageRef.value?.classList.add("page1");
} else if (deltaY > 0) {
homePageRef.value?.classList.add("page3");
}
homePageRef.value?.classList.remove("page2");
}
function section3Wheel(deltaY: number) {
if (deltaY < 0) {
homePageRef.value?.classList.add("page2");
} else if (deltaY > 0) {
homePageRef.value?.classList.add("page4");
}
homePageRef.value?.classList.remove("page3");
}
function section4Wheel(deltaY: number) {
if (deltaY < 0) {
homePageRef.value?.classList.add("page3");
} else if (deltaY > 0) {
homePageRef.value?.classList.add("page5");
}
homePageRef.value?.classList.remove("page4");
}
function section5Wheel(deltaY: number) {
if (deltaY < 0) {
const list = Array.from(homePageRef.value?.classList);
if (list.includes("page6")) {
homePageRef.value?.classList.add("page5");
homePageRef.value?.classList.remove("page6");
} else {
homePageRef.value?.classList.add("page4");
homePageRef.value?.classList.remove("page5");
}
} else if (deltaY > 0) {
homePageRef.value?.classList.add("page6");
homePageRef.value?.classList.remove("page5");
}
}
function footerWheel(deltaY: number) {
if (deltaY < 0) {
homePageRef.value?.classList.add("page5");
homePageRef.value?.classList.remove("page6");
}
}
onMounted(() => {
// 禁用 scrollRestoration 功能 该功能旨在在页面刷新或导航后恢复之前的滚动位置
window.history.scrollRestoration = "manual";
section1Ref.value?.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault();
section1Wheel(event.deltaY);
});
section2Ref.value?.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault();
section2Wheel(event.deltaY);
});
section3Ref.value?.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault();
section3Wheel(event.deltaY);
});
section4Ref.value?.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault();
section4Wheel(event.deltaY);
});
section5Ref.value?.addEventListener("wheel", wheelEvent);
footerRef.value?.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault();
footerWheel(event.deltaY);
});
});
const timer = ref();
function wheelEvent(event: WheelEvent) {
event.preventDefault();
if (timer.value) clearTimeout(timer.value);
timer.value = setTimeout(function () {
var delta = 0;
if (event.wheelDelta) {
delta = -(event.wheelDelta / 120);
}
if (delta) {
section5Wheel(delta);
}
}, 250);
}
</script>
其中有点特殊处理了section5 模块,向上滑动的时候并不是全模块都离开视口,在section5 和 footer 同屏时,在section5 上向上滑动想回到 section4,会重复触发两次 section5Wheel 事件,导致sectio5 一闪而过最终显示了 section4 。因此这里加入setTimeout 延迟处理避免快速滚动。
总结
此前调研了市面上很多类似效果的网站,由于扒不到源码,只能从dom 结构变化上观察。有的网站使用过margin-top 将上一个全屏模块移出视口,也有修改模块的高度… 但感觉殊途同归,应该内部都是基于 wheel 事件的处理,但是性能上没有客观的比较,欢迎大家留言交流,你有什么想法呢?
转载自:https://juejin.cn/post/7410599171121512487