VUE全屏滚动效果

413 阅读2分钟

先看一下官网效果:ys.mihoyo.com/main/map

概念

每次当滚动鼠标滚轮的时候,页面会进行一整页的滚动,这就是全屏滚动。

要求

当窗口大小变化时,全屏滚动效果不会发生变化,即需要做到自适应,不受到高度宽度的影响。

当点击指示器也可以进行页面切换。

原理

image.png 最外层容器:就是我们的窗口,视角能看到的。

内层容器: 用来存放我们需要滚动内容的容器。

滚动元素: 就是我们看到的一张张照片。

首先对最外侧容器的overflow:hidden;这样保证不会溢出。

其次对内层容器进行滚动,每次滚动的高度就是窗口的高度,但是需要动态计算窗口的高度。

最后只需要判断上一页还是下一页来计算index,通过index ✖ 页面高度,来进行滚动即可。

代码实现

HTML代码

大家可以结合html结构,去理解 js 代码 ` ` ## JS代码

给内层容器添加鼠标滚动事件

当滚动事件触发时判断是向上还是向下滚动,以此来控制滚动方向。

另外由于鼠标滚动事件触发过于频繁,我们需要增加节流,即保证在单位时间内只执行一次即可。

设置canRun为节流标志,每500毫秒执行一次。

    function mousewheel(e) {
      isTranstion.value = false
      if (canRun.value) {
        canRun.value = false
        goScroll(e)
        setTimeout(() => {
          canRun.value = true
        }, 500)
      }
    }

开始滚动元素

通过上一页下一页增加减少$index,通过索引 ✖ 页面高度,来决定页面滚动的距离,也就是当前要显示的页面。 ` function goScroll(e) { //e.wheelDelta 用来判断上一个下一个 <0 下一个 >0上一个 if (e.wheelDelta < 0) { next() } else { last() } }

//$INDEX
const $index = ref(0) //索引控制第几个显示
// 下一个
function next() {
  if ($index.value < 4) {
    $index.value++
  }
}
// 上一个
function last() {
  if ($index.value > 1 || $index.value === 1) {
    $index.value--
  }
}

`

 动态计算页面高度

由于页面高度发生变化时,我们需要动态响应高度的变化。

使用 VueUse 库提供的 useWindowSize() 函数计算vueuse.org/core/useWin…

当页面高度变化时height会相应变化

const { height } = useWindowSize()

值得注意的是,当页面高度变化时,我们需要关闭动画效果。 

    const windowHeight = computed(() => {
      isTranstion.value = true
      return height.value
    })

通过索引计算滚动高度

    const transformScroll = computed(() => {
      return `translateY(-${$index.value * windowHeight.value}px)`
    })

将滚动高度应用到滚动容器上面

watchEffect: 会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。官方:cn.vuejs.org/guide/essen…

    // ELEMENT
    const element = ref('element')
    watchEffect(() => {
      if (element.value.style) {
        element.value.style.transform = transformScroll.value
      }
    })

点击指示器

点击指示器的时候,跳转到相应页面,只需设置相应的index即可。

    // 点击切换
    function changeBac(index) {
      // 点击切换时需要开启动画
      isTranstion.value = false
      $index.value = index
    }

全部源码


<script setup>
// IMAGE DATA
const ysImage = ref([
  {
    backgroundImage: 'https://ys.mihoyo.com/main/_nuxt/img/5c125a1.png',
  },
  {
    backgroundImage: 'https://uploadstatic.mihoyo.com/contentweb/20200319/2020031921550320292.jpg',
  },
  {
    backgroundImage: 'https://uploadstatic.mihoyo.com/contentweb/20200319/2020031921552395638.jpg',
  },
  {
    backgroundImage: 'https://uploadstatic.mihoyo.com/contentweb/20210719/2021071918001232800.jpg',
  },
  {
    backgroundImage:
      'https://webstatic.mihoyo.com/upload/contentweb/2022/08/15/8969f683b92839ac427c875d0d742be2_4825576482548821743.jpg',
  },
])

const asideData = ref([
  {
    title: '蒙德',
  },
  {
    title: '璃月',
  },
  {
    title: '稻妻',
  },
  {
    title: '须弥',
  },
  {
    title: '枫丹',
  },
])
// ELEMENT
const element = ref('element')
watchEffect(() => {
  if (element.value.style) {
    element.value.style.transform = transformScroll.value
  }
})

//HEIGHT
const { height } = useWindowSize()
const windowHeight = computed(() => {
  // 高度变化时需要关闭动画
  isTranstion.value = true
  return height.value
})
const transformScroll = computed(() => {
  return `translateY(-${$index.value * windowHeight.value}px)`
})

// ISTRANSTION  CANRUN
const isTranstion = ref(false) //控制是否显示动画效果
const canRun = ref(true) //节流控制器

function mousewheel(e) {
  isTranstion.value = false
  if (canRun.value) {
    canRun.value = false
    goScroll(e)
    setTimeout(() => {
      canRun.value = true
    }, 500)
  }
}

function goScroll(e) {
  //e.wheelDelta 用来判断上一个下一个 <0 下一个 >0上一个
  if (e.wheelDelta < 0) {
    next()
  } else {
    last()
  }
}

//$INDEX
const $index = ref(0) //索引控制第几个显示
// 下一个
function next() {
  if ($index.value < 4) {
    $index.value++
  }
}
// 上一个
function last() {
  if ($index.value > 1 || $index.value === 1) {
    $index.value--
  }
}

// 点击切换
function changeBac(index) {
  // 点击切换时需要开启动画
  isTranstion.value = false
  $index.value = index
}
</script>
<template>
  <!-- 最外层容器 -->
  <div class="outer-box" ref="fullPage">
    <!-- 内层容器 -->
    <div ref="element" :class="{ activeTranstion: isTranstion }" class="inner-box" @mousewheel="mousewheel">
      <!-- 滚动显示的元素 -->
      <div
        v-for="item in ysImage"
        :style="{ backgroundImage: `url(${item.backgroundImage})` }"
        class="scroll-element"
      ></div>
    </div>
    <!-- 指示器 -->
    <ul class="aside">
      <li v-for="(item, index) in asideData" @click="changeBac(index)">
        <span :class="{ active: index === $index }"></span>
        <div class="show-dec">{{ item.title }}</div>
      </li>
    </ul>
  </div>
</template>
<style lang="scss" scoped>
.activeTranstion {
  transition: all 0ms ease 0s !important;
}
.active {
  width: 12px !important;
  height: 12px !important;
}
.outer-box {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;

  .inner-box {
    width: 100%;
    height: 100%;
    transition: all ease-in-out 1s;
    .scroll-element {
      height: 100%;
      background-size: cover !important;
      background-position: center;
      background-repeat: no-repeat;
    }
  }

  .aside {
    list-style: none;
    position: fixed;
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
    li {
      height: 14px;
      width: 14px;
      margin: 7px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      .show-dec {
        position: absolute;
        font-size: 10px;
        width: 45px;
        right: 20px;
        opacity: 0;
        color: #fff;
        transition: all linear 0.1s;
      }
      span {
        border-radius: 100%;
        border: #fff solid 1px;
        width: 4px;
        height: 4px;
        display: inline-block;
        background-color: #fff;
        transition: all ease-in-out 0.2s;
      }
      &:hover span {
        width: 10px;
        height: 10px;
        background-color: #fff;
        cursor: pointer;
      }
      &:hover .show-dec {
        opacity: 1;
      }
    }
  }
}
</style>

链接:juejin.cn/post/726812…