likes
comments
collection
share

Nuxt3 实现类 fullpage 效果在Nuxt3 项目中遇到一个整屏滚动翻页效果的需求,类似fullpage.js

作者站长头像
站长
· 阅读数 49

在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
评论
请登录