Vue解决移动端弹窗滚动穿透问题


Posted in Vue.js onDecember 15, 2020

一、问题描述

  1. 在移动端的H5页面中,我们经常会遇到 “点击按钮-->弹窗-->选择选项” 这样的场景。而在选项过多出现滚动条时,滚动滚动条至容器的底部或者顶部。再往上或往下拖动滚动条时,滚动动作会出现穿透,这时候底部的body也会一起滚动。
  2. 问题总结:内容在滚动到容器的顶部或者底部时,再向上或向下 强行滚动 ,则出现滚动穿透

Vue解决移动端弹窗滚动穿透问题

二、解决方案思考

参考了网上一大堆的解决方法,大可分为三类方法。经过认真的思考和分析,个人的总结如下:

  • 使用js去控制和改变css
1. 弹窗出现
	1.1. 记录点击出现弹窗按钮位置的scrollTop
	1.2. 给body样式{'overflow': 'hidden'}
2. 弹窗关闭
	2.1. 取消body样式{'overflow': 'hidden'}
	2.2. 给body样式{'top': scrollTop}
  
优点:实现简单快捷
缺点:在弹窗一开一关的时间段,如果弹窗不是沾满整个窗口,则会看到body闪烁
  • 使用js去控制弹窗内容区的默认滚动事件
1. 弹窗出现
  1.1. 监听内容容器layoutBox的touchstart和touchmove事件
  1.2. 监听touchstart事件,得知手指开始滚动内容区的起始位置targetY
  1.3. 监听touchmove事件,得知滚动内容区的过程中,变化的位置newTargetY
  1.4. 拿到 内容滚动到容器顶部的距离 scrollTop / 内容可滚动的高度 scrollHeight / 当前容器的高度 clientHeight
  1.5. 在滚动到顶部和滚动到底部时,阻止内容容器的默认行为。(关键点)
2. 弹窗正常关闭

优点:从出现滚动穿透问题的源头出发,把问题解决,js实现不存在ios兼容问题
缺点:实机验证,个别品牌的机型存在兼容性问题
  • 弹窗内容区禁止滚动,使用js模拟滚动条
1. 弹窗出现 
 1.1. 监听touchmove事件,全程阻止默认行为 
 1.2. 监听touchstart和touchmove事件记录手指的移动距离,使用transform: translate3d()属性,实现内容滚动 
2. 弹窗正常关闭

优点:js实现不存在ios兼容问题
缺点:ios上丢失了原生滚动条的回弹体验

三、初步实现

写成一个mixin

/**
 * @author cunhang_wei
 * @description 解决弹窗内容区滚动穿透到body的问题
 * @param $refs.layoutBox 需要事先指定 内容容器
 */

export default {
  data () {
    return {
      targetY: 0
    }
  },

  mounted () {
    if (this.$refs.layoutBox) {
      this.$el.addEventListener('touchstart', this.handleTouchstart)
      this.$el.addEventListener('touchmove', this.handleTouchmove, false)
    }
    
  },

  methods: {
    handleTouchstart (e) {
      this.targetY = Math.floor(e.targetTouches[0].clientY) // 手指起始触摸位置
      console.log('handleTouchstart', this.targetY)
    },
    handleTouchmove (e) {
      let layoutBox = this.$refs.layoutBox // 内容容器
      let newTargetY = Math.floor(e.targetTouches[0].clientY) // 手指滑动中触摸位置
      let sTop = layoutBox.scrollTop // 内容滚动到容器顶部的高度
      let sHeight = layoutBox.scrollHeight // 内容的可滚动高度
      let cliHeight = layoutBox.clientHeight // 当前内容容器的高度
      if (sTop <= 0 && newTargetY - this.targetY > 0 && e.cancelable) {
        console.log('下拉到页面顶部')
        e.preventDefault()
      } else if (sTop >= sHeight - cliHeight && newTargetY - this.targetY < 0 && e.cancelable) {
        console.log('上翻到页面底部')
        e.preventDefault()
      }
    }
  },
  
  beforeDestroy () {
    if (this.$refs.layoutBox) {
      this.$el.removeEventListener('touchstart', this.handleTouchstart)
      this.$el.removeEventListener('touchmove', this.handleTouchmove)  
    }
  }
}

写完后发现每一次控制弹窗的滚动穿透,都需要引入这个 mixin 文件,未免有些累赘,查看了Vue的官方文档,发现了一种更好的办法,那就是 全局指令

四、写法优化

写成一个全局指令 no-through

/**
 * @author cunhang_wei
 * @description 解决弹窗内容区滚动穿透到body的问题(覆盖率90%)
 **/

// @description 用法
// <ul v-no-through>
//  <li></li>
//  <li></li>
//</ul>

// 使用vuex的保存一个全局变量
import state from 'src/vuex/modules/home/state'
export default {
  name: 'no-through',

  bind: function (el, binding) {
    const handleTouchstart = function (event) {
      state.targetY = Math.floor(event.targetTouches[0].clientY) // 手指起始触摸位置
    }
    const handleTouchmove = function (event) {
      let newTargetY = Math.floor(event.targetTouches[0].clientY) // 手指滑动中触摸位置
      let sTop = el.scrollTop // 内容滚动到容器顶部的高度
      let sHeight = el.scrollHeight // 内容的可滚动高度
      let cliHeight = el.clientHeight // 当前内容容器的高度
      if (sTop <= 0 && newTargetY - state.targetY > 0 && event.cancelable) {
        console.log('下拉到页面顶部')
        event.preventDefault()
      } else if (sTop >= sHeight - cliHeight && newTargetY - state.targetY < 0 && event.cancelable) {
        console.log('上翻到页面底部')
        event.preventDefault()
      }
    }
    el.addEventListener('touchstart', handleTouchstart)
    el.addEventListener('touchmove', handleTouchmove, false)
  },

  unbind: function (el, binding) {
    // 重置全局变量 targetY
    state.targetY = 0
  }
}

// 最后再去 main.js 注册为全局指令,即可使用。

实机测试

  • ios 测试通过 ios13
  • 小米、红米手机 测试通过 安卓10
  • 一加手机 测试通过 安卓10
  • 华为手机测试通过 emui11 安卓10
  • 三星S8上存在兼容问题 (初略估计和 Samsung webView的底层实现有关)

总结

  1. 解决问题关键在于:要清楚的知道 什么情况下才会发生滚动穿透
  2. 写法的优化,可以简洁代码,让代码更优雅

以上就是Vue解决移动端弹窗滚动穿透问题的详细内容,更多关于vue 解决弹窗滚动穿透的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
如何正确解决VuePress本地访问出现资源报错404的问题
Dec 03 Vue.js
vue中父子组件的参数传递和应用示例
Jan 04 Vue.js
vue+echarts实现中国地图流动效果(步骤详解)
Jan 27 Vue.js
vue Element-ui表格实现树形结构表格
Jun 07 Vue.js
Vue3中的Refs和Ref详情
Nov 11 Vue.js
如何用vue实现网页截图你知道吗
Nov 17 Vue.js
Vue elementUI表单嵌套表格并对每行进行校验详解
Feb 18 Vue.js
vue 实现弹窗关闭后刷新效果
Apr 08 Vue.js
vue里使用create, mounted调用方法
Apr 26 Vue.js
详解Vue3使用axios的配置教程
Apr 29 Vue.js
vue如何清除浏览器历史栈
May 25 Vue.js
vue实现input输入模糊查询的三种方式
Aug 14 Vue.js
8个非常实用的Vue自定义指令
Dec 15 #Vue.js
vue从后台渲染文章列表以及根据id跳转文章详情详解
Dec 14 #Vue.js
Vue在H5 项目中使用融云进行实时个人单聊通讯
Dec 14 #Vue.js
vue的hash值原理也是table切换实例代码
Dec 14 #Vue.js
Vue如何跨组件传递Slot的实现
Dec 14 #Vue.js
VUE中鼠标滚轮使div左右滚动的方法详解
Dec 14 #Vue.js
vue3.0实现插件封装
Dec 14 #Vue.js
You might like
获取PHP警告错误信息的解决方法
2013/06/03 PHP
ThinkPHP中的常用查询语言汇总
2014/08/22 PHP
PHP针对多用户实现更换头像功能
2016/09/04 PHP
一个简单的js动画效果代码
2010/07/20 Javascript
javascript放大镜效果的简单实现
2013/12/09 Javascript
Javascript中设置默认参数值示例
2014/09/11 Javascript
原生javascript获取元素样式
2014/12/31 Javascript
jQuery 回调函数(callback)的使用和基础
2015/02/26 Javascript
javascript实现倒计时(精确到秒)
2015/06/26 Javascript
jQuery原生的动画效果
2015/07/10 Javascript
bootstrap vue.js实现tab效果
2017/02/07 Javascript
javascript事件的绑定基础实例讲解(34)
2017/02/14 Javascript
Vue-resource拦截器判断token失效跳转的实例
2017/10/27 Javascript
jQuery实现的简单无刷新评论功能示例
2017/11/08 jQuery
微信小程序icon组件使用详解
2018/01/31 Javascript
vue 2.x 中axios 封装的get 和post方法
2018/02/28 Javascript
vue 进阶之实现父子组件间的传值
2019/04/26 Javascript
vue安装遇到的5个报错及解决方法
2019/06/12 Javascript
elementui之el-tebs浏览器卡死的问题和使用报错未注册问题
2019/07/06 Javascript
vue中的.$mount('#app')手动挂载操作
2020/09/02 Javascript
JavaScript实现音乐导航效果
2020/11/19 Javascript
使用js获取身份证年龄的示例代码
2020/12/11 Javascript
JavaScript 实现继承的几种方式
2021/02/19 Javascript
tesserocr与pytesseract模块的使用方法解析
2019/08/30 Python
python定间隔取点(np.linspace)的实现
2019/11/27 Python
经验丰富程序员才知道的8种高级Python技巧
2020/07/27 Python
详解css3中的伪类before和after常见用法
2020/11/17 HTML / CSS
实例教程 利用html5和css3打造一款创意404页面
2014/10/20 HTML / CSS
施华洛世奇加拿大官网:SWAROVSKI加拿大
2018/06/03 全球购物
巴西服装和鞋子购物网站:Marisa
2018/10/25 全球购物
C++如何引用一个已经定义过的全局变量
2014/08/25 面试题
《记承天寺夜游》教学反思
2014/02/16 职场文书
学生党员一帮一活动总结
2014/07/08 职场文书
学校创先争优活动总结
2014/08/28 职场文书
民族精神月活动总结
2014/08/28 职场文书
土地转让协议书
2014/09/27 职场文书