AlexZ33 / lessions

自己练习的各种demo和课程
12 stars 2 forks source link

【vue使用】利用Vue指令实现弹窗中内容滚动 #112

Open AlexZ33 opened 3 years ago

AlexZ33 commented 3 years ago

实现效果:为了防止滑动穿透,需要在弹框的touchmove事件中添加e.preventDefault()阻止默认的行为,但是如果弹窗的内容太长需要滚动,在需要滚动的内容的父级元素上添加 v-scroll即可

基本思路:利用Vue.directive()中的bind钩子,在钩子中对需要滚动的元素绑定touchstart,touchmove和touchend事件,之后利用e.touches判断滚动方向及距离

需要html元素结构满足要求:

1.绑定v-scroll的元素要给高度。 因为为了让内部滚动。。外层元素一定要有高度

2.绑定v-scroll的内部必须是类名为vscroller的节点 在v-scroll的代码中通过这个类来定位需要滚动的元素

<div v-scroll>
    <div class="vscroller">
        内容
        内容
        内容
    </div>
</div>

v-scroll的实现代码

import Vue from 'vue'

Vue.directive('scroll', {
  bind: function(el, binding) {
    let needScroll = false,
    scroller = null,
    oldY = null,
    totalDetaY = 0,
    containerH,
    scrollerH,
    pageY,
    detaY,
    oldTime = null
    // 获取需要绑定元素及需要滚动的元素
    el.style['overflow'] = 'hidden' // 为了避免内容溢出
    scroller = el.querySelector('.vscroller') || null
    // 为滚动内容添加touch事件的监听器
    if (scroller) {
      scroller.addEventListener('touchstart', function(e) {
        needScroll = isNeedScroll()
      })

      scroller.addEventListener('touchmove', function(e) {
        e.preventDefault();
        if(needScroll) {
          computePosition(e)
          handleScroll()
        }
      })

      scroller.addEventListener('touchend', function(e) {
        oldY = null
      })
    }

    // 内部函数
    function isNeedScroll() {
      containerH = el.getBoundingClientRect().height
      scrollerH = scroller.getBoundingClientRect().height
      if (containerH >= scrollerH) return false
      return true
    }
    function computePosition(e) {
      pageY = e.touches[0].pageY
      detaY = 0
      // 计算移动距离, 更新旧的位置坐标
      if (!oldY) oldY = pageY
      detaY = pageY - oldY
      oldY = pageY
    }
    function handleScroll() {
      totalDetaY += detaY
      // 判断是否是滑动边界
      if (totalDetaY > 0) totalDetaY = 0
      if (containerH - totalDetaY > scrollerH) totalDetaY = containerH - scrollerH
      translate(scroller, 0, totalDetaY)
    }
    function translate(target, x, y) {
      target.style['transform'] = `translate(${x}px, ${y}px)`
    }
  }
})

实现细节:

1.如果在bind中直接调用 elemetn.getBoundingClientRect() 方法可能会获取不到位置信息,因为bind钩子执行时元素还没被渲染到页面中。所以选择在touch事件的监听器中调用

  1. isNeedScroll() 方法主要用于判断是否需要滚动,如果内容高度不足够的话,那么不需要滚动。同时,这个方法在touchstart中调用,而不在touchmove中,每次滑动只调用一次,有利于提高性能。

  2. 通过css translate方法进行位置的更改,性能更好 (相关知识点:合成层?---需要学习一波)

  3. 将逻辑拆分成不同方法,以便后续功能扩展(目前只能竖着滑,应该可以通过v-scroll="'h'|'v'"判断滑动方向)