js 页面滚动翻页效果并切换慢速查看

445 阅读2分钟

项目配置 Vue3 + vite

需求:页面滚动有翻页的效果,遇到不能一屏展示完的,需要可以慢慢滚动查看

参考文章:自己动手实现页面滚动动画效果 - 掘金 (juejin.cn)

感谢博主的视差效果代码,本篇稍做修改,在视差的基础上增加了不能以一屏展示时的默认滚动

HTML结构:

<div class="introduction_item_cls introduction_cls0" id="introduction00">
    // 内容自行添加
</div>

<div class="introduction_item_cls introduction_cls0" id="introduction01">
</div>

<div class="introduction_item_cls introduction_cls0" id="introduction02">
</div>

<div class="introduction_item_cls introduction_cls0" id="introduction03">
</div>

<div class="introduction_item_cls introduction_cls0" id="introduction04">
</div>

<div class="introduction_item_cls introduction_cls0" id="introduction05">
</div>

state数据

const state = reactive({
    // 存放每一个需要滚动效果的元素id
  divIdList: ['introduction00', 'introduction01', 'introduction02', 'introduction03', 'introduction04', 'introduction05',],
  // 动态计算每一个元素的高度
  positionList: [0, 530, 1490, 2150, 3000, 4250, 4700],
  debounce: false,    // 是否正在滚动
  pageNowIndex: -1,    // 当前页面元素下标
})

methods 方法

onMounted(() => {
      // 页面加载完成计算各个元素高度
      getDivHeight()
      // 滚动监听
      window.addEventListener('mousewheel', wheelFn, { passive: false })
})

const getDivHeight = () => {   // 计算各div高度
      state.positionList = [0]// 动态获取
  setTimeout(() => {// 转异步,等待页面元素加载完成,使用nextTick没效果,不知道为啥
    state.divIdList.forEach((el, idx) => {
      let dv = document.getElementById(el)
      let nowH = dv.offsetHeight
      let perH = state.positionList[idx]
      let newH = nowH + perH + 10 // 当前div的高度加上一个div的高度 +10去边
      state.positionList.push(newH)
    });
    console.log(state.positionList, 'state.state.positionListuserInfoP');
  }, 100);
}

/** 计算现在在哪个位置,是正好对准边界还是夹在中间 */
const calcNowPosition = (nowY) => {
  for (let i = 0; i < state.positionList.length; i++) {
    if (state.positionList[i] > nowY) {
      return { index: i - 1, between: state.positionList[i - 1] !== nowY }
    }
  }
  return { index: state.positionList.length - 1, between: false }
}

/** 缓动函数 https://easings.net#easeInOutCubic */
const easeInOutCubic = (x) => {
  // return 4 * x * x * x
  return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2
}

const wheelFn = (e) => {
  const nowY = window.scrollY
  if (e.deltaY > 0) { // 只有下拉的时候才会触发默认的 慢速滚动
    const { index, between } = calcNowPosition(nowY + 2)    // 
    if (index !== 0) {
      let nowEl = document.getElementById(state.divIdList[index])
      let isinv = isInViewPortOfOne(nowEl, 2)
      if (!isinv) { // 超出的元素id下标,除了第一个
        console.log('慢点');
        return 
      }
    }
  }
  // e.preventDefault()

  // 正在滚动中
  if (state.debounce) {
    return
  }
  // 滚轮是向下还是向上
  const down = e.deltaY > 0
  state.debounce = true
  let start = 0
  let scrollHeight = 0
  let nowx
  if (down) {
    const { index, between } = calcNowPosition(nowY + 2)    // 
    // 向下滚,滚动高度等于下一个位置与现在的差值
    scrollHeight = state.positionList[index + 1] - nowY
    nowx = index + 1
  } else {
    const { index, between } = calcNowPosition(nowY - 2)
    // 向上滚,夹在中间需滚动上一个边界与现在的差值,在边界就滚动一个完整距离
    scrollHeight = between ? (nowY - state.positionList[index]) : (state.positionList[index] - state.positionList[index - 1])
    nowx = index
  }

  let nowEl = document.getElementById(state.divIdList[nowx])
  let isinv = isInViewPortOfOne(nowEl, 1)
  if (!isinv && nowx !== 0) { // 超出的元素id下标,除了第一个。这个判断可根据实际业务判断,不要判断直接写 state.pageNowIndex = nowx即可
    state.pageNowIndex = nowx
  } else {
    state.pageNowIndex = -1
  }

  // 动画函数,需要闭包访问 start 就没有分离出来
  const step = (unix) => {
    if (!start) {
      start = unix
    }
    const duration = unix - start
    const y = easeInOutCubic(duration / 1000) * scrollHeight
    // 当调用getDivHeight()动态获取div高度时,会出现window.scrollY获取到的也买你滚动高度与 window.scrollTo设置的高度不一样的问题
    let toNum = down ? nowY + y : nowY - y
    window.scrollTo(0, toNum)
    if (duration <= 1001) {
      requestAnimationFrame(step)
    } else {
      state.debounce = false
    }
  }
  requestAnimationFrame(step)
}

const isInViewPortOfOne = (el, type) => { // 判断元素是否在视图之内
  // viewPortHeight 兼容所有浏览器写法
  if (!el) {  // 没有元素,在视图中
    return true
  }
  const rect = el.getBoundingClientRect();
  const windowHeight = window.innerHeight || document.documentElement.clientHeight;
  if (type == 1) {    // 计算元素能否在窗口中全部展示
    return (rect.height <= windowHeight)
  } else if (type == 2) { // 计算是否能看到该元素的底部
    return (
      rect.bottom <= windowHeight
    );
  }
}