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 相关文章推荐
vue在图片上传的时候压缩图片
Nov 18 Vue.js
vue使用exif获取图片经纬度的示例代码
Dec 11 Vue.js
如何在vue中使用kindeditor富文本编辑器
Dec 19 Vue.js
Vue 修改网站图标的方法
Dec 31 Vue.js
如何封装Vue Element的table表格组件
Feb 06 Vue.js
vue脚手架项目创建步骤详解
Mar 02 Vue.js
vue路由实现登录拦截
Mar 24 Vue.js
vue引入Excel表格插件的方法
Apr 28 Vue.js
使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)
Jun 09 Vue.js
vue3使用vue-router的完整步骤记录
Jun 20 Vue.js
关于Vue中的options选项
Mar 22 Vue.js
el-table-column 内容不自动换行的解决方法
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开发中常用的三个表单验证函数使用小结
2010/03/03 PHP
PHP获取和操作配置文件php.ini的几个函数介绍
2013/06/24 PHP
PHP中使用socket方式GET、POST数据实例
2015/04/02 PHP
PHP编程中的__clone()方法使用详解
2015/11/27 PHP
PHP使用strrev翻转中文乱码问题的解决方法
2017/01/13 PHP
javascript数组使用调用方法汇总
2007/12/08 Javascript
js中Image对象以及对其预加载处理示例
2013/11/20 Javascript
Jquery解析json数据详解
2013/12/26 Javascript
JavaScript通过字典进行字符串翻译转换的方法
2015/03/19 Javascript
Adapter适配器模式在JavaScript设计模式编程中的运用分析
2016/05/18 Javascript
Bootstrap 实现查询的完美方法
2016/10/26 Javascript
javascript数据类型详解
2017/02/07 Javascript
详解nodeJS中读写文件方法的区别
2017/03/06 NodeJs
bootstrap的常用组件和栅格式布局详解
2017/05/02 Javascript
JavaScript仿微信打飞机游戏
2020/07/05 Javascript
详解react-router4 异步加载路由两种方法
2017/09/12 Javascript
JS性能优化实现方法及优点进行
2020/08/30 Javascript
针对Vue路由history模式下Nginx后台配置操作
2020/10/22 Javascript
[01:25]DOTA2自定义游戏灵园鬼域等你踏足
2015/10/30 DOTA
Python读写Excel文件的实例
2013/11/01 Python
python通过线程实现定时器timer的方法
2015/03/16 Python
Python字符串详细介绍
2015/05/09 Python
Python3.6 Schedule模块定时任务(实例讲解)
2017/11/09 Python
python numpy 按行归一化的实例
2019/01/21 Python
Python使用Pandas库实现MySQL数据库的读写
2019/07/06 Python
django创建最简单HTML页面跳转方法
2019/08/16 Python
使用keras实现densenet和Xception的模型融合
2020/05/23 Python
Python QT组件库qtwidgets的使用
2020/11/02 Python
.NET笔试题(20个问题)
2016/02/02 面试题
《尊严》教学反思
2014/02/11 职场文书
欢迎横幅标语
2014/06/17 职场文书
我的中国梦演讲稿高中篇
2014/08/19 职场文书
幼师自荐信范文
2015/03/06 职场文书
药品开票员岗位职责
2015/04/15 职场文书
CSS3实现的水平标题菜单
2021/04/14 HTML / CSS
windows下快速安装nginx并配置开机自启动的方法
2021/05/11 Servers