解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题


Posted in Javascript onNovember 04, 2020

在移动端开发中使用到了Mint-ui组件库,其中有两个组件Popup组件和Datetime Picker存在滚动性穿透问题,官方文档最新版并没有解决这个问题。

现象还原

官方地址

手机扫码查看demo,查看两个组件Popup组件和Datetime的例子演示。

问题原因

HTML5触摸事件touchmove事件:当手指在屏幕上滑动的时候连续地触发

所以当激活出组件Popup组件和Datetime Picker的弹出层时,我们在弹层选择内容时上下连续滑动是会触发该事件

解决思路

在弹出层出现之后阻止body的touchmove事件,在弹层消失之后移除阻止事件

使用mint-ui组件库的解决方式

Popup组件

// 官方实例

<mt-popup
 v-model="popupVisible"
 position="bottom">
 ...
</mt-popup>

// 解决方式,通过监听popupVisible变量,在弹窗出现后禁止bode节点touchMove事件,弹窗消失后恢复body节点的touchMove事件

const handler = function(e) {
  e.preventDefault();
}

// vue实例内
watch: {
  popupVisible: function (val) {
   if(val) {
     document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
   } else {
     document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
   }
  }
}

Datetime Picker

// 官方实例

<mt-datetime-picker
  ref="picker"
  type="time"
  v-model="pickerValue">
</mt-datetime-picker>

// 解决方式:这个组件比较坑,由于Datetime Picker没有提供弹窗显示和隐藏的绑定变量,所以我们无采用解决popup的方式解决问题,只能通过打开事件,确认事件、取消事件,点击蒙层弹窗消失这几个时间点去解决。官方给出的属性方法只支持确认事件,打开事件。没有明文给出取消事件的回调函数,更不支持点击蒙层弹窗消失事件,所以很坑。

<mt-datetime-picker
  ref="picker"
  type="time"
  v-model="pickerValue"
  @confirm="confirm">
</mt-datetime-picker>

const handler = function(e) {
  e.preventDefault();
}

// vue实例内

methods: {
  openPicker() { // 打开事件
    document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
    this.$refs.picker.open();
  },
  confirm() { // 确认事件
    document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
  }
}

此时还缺一个取消回调还有蒙层点击事件,然后看源码其实mint-ui源码里是支持取消回调事件的,另外面,惊喜的是在2.0版本之后的mint-ui给Datetime picker组件 新加了visible-change事件和closeOnClickModal属性(传送门),但官方文档依旧没更新出来这些属性。现在就很好的解决了上述问题。

而且有了visible-change事件就可以不用按上述思路解决了。组件部分源码如下:

props: {
  ...,
  closeOnClickModal: {
    type: Boolean,
    default: true
  }
},
watch: {
  ...,
 visible(val) {
  this.$emit('visible-change', val);
 }
},
...
<span class="mint-datetime-action mint-datetime-cancel" @click="visible = false;$emit('cancel')">{{ cancelText }}</span>

直接一个visible-change方法搞定

<mt-datetime-picker
  ref="picker"
  type="time"
  v-model="pickerValue"
  @confirm="confirm"
  @visible-change=""handleValueChange>
</mt-datetime-picker>

const handler = function(e) {
  e.preventDefault();
}

// vue实例内
methods: {
  handleValueChange: function (val) {
   if(val) {
     document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
   } else {
     document.getElementsByTagName('body')[0].addEventListener('touchmove', this.handler, { passive: false });
   }
  }
}

上面的方法已经可以解决项目遇到到的坑了,但是每个页面用到组件Popup组件和Datetime Picker都需要去加上这段代码,当页面存在多个Popup组件就需要监听多个变量。

更好的解决方式(上述方式在网上也有类似解决方式,自己补充了visible-change方案)

在项目中按上面那样解决太麻烦了,所以又想了一个体验比较好的解决方案,因为弹层的出现和消失会触发组件更新,所以想到和可以全局注册一个指令v-roll,运用指令的componentUpdated钩子来实现这个功能。代码如下

// 全局注册指令
const handler = (e) => {
 e.preventDefault();
};
Vue.directive('roll', {
 componentUpdated(el, binding) {
  if (binding.value) {
   document.getElementsByTagName('body')[0].addEventListener('touchmove', handler, { passive: false });
  } else {
   document.getElementsByTagName('body')[0].removeEventListener('touchmove', handler, { passive: false });
  }
 }
});

这样一来精简了很多代码,其他开发者用到时只需要给对应组件加一个v-roll指令即可。

// popup组件处理方式
<mt-popup
 v-model="popupVisible"
 v-roll:visible=popupVisible>
 ...
</mt-popup>

// mt-datetime-picker组件处理方式
<mt-datetime-picker 
 ref="datePicker" 
 v-model="date"
 @visible-change="handleVisibleChange"
 v-roll:visible=pVisible
 ...>
</mt-datetime-picker>
...
data: {
  pVisible: false 
},
methods: {
  handleVisibleChange (isVisible) {
    this.pVisible = isVisible;
  }
}

接着还遇到一个坑,当同一个视图页面存在多个组件Popup组件和Datetime Picker使用指令时,会集体触发指令。所以会相互覆盖,如打开A弹层,阻止页面touchMove事件,但另外一个popuu组件也触发钩子函数,又取消阻止touchMove事件,导致没效果。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。// 这里目前自己的理解是由于弹层出现和消失导致所在视图界面节点更新,所以其他节点绑定了v-roll指令的钩子也被触发了

解决方案:对于一个视图内使用多个多个组件Popup组件和Datetime Picker的,给指令传入数组类型

// 全局注册指令
const handler = (e) => {
 e.preventDefault();
};
Vue.directive('roll', {
 componentUpdated(el, binding) {
  if (binding.value instanceof Array) {
   const visible = binding.value.some(e => e); // 当视图所有控制弹层的变量存在一个是true,即可阻止touchmove事件
   if (visible) {
    document.getElementsByTagName('body')[0].addEventListener('touchmove', handler, { passive: false });
   } else {
    document.getElementsByTagName('body')[0].removeEventListener('touchmove', handler, { passive: false });
   }
  } else if (typeof binding.value === 'boolean') {
   if (binding.value) {
    document.getElementsByTagName('body')[0].addEventListener('touchmove', handler, { passive: false });
   } else {
    document.getElementsByTagName('body')[0].removeEventListener('touchmove', handler, { passive: false });
   }
  }
 }
});

// popup组件处理方式
<mt-popup
 v-model="popupVisible"
 v-roll:visible=[popupVisible, pVisible]>
 ...
</mt-popup>

// mt-datetime-picker组件处理方式
<mt-datetime-picker 
 ref="datePicker" 
 v-model="date"
 @visible-change="handleVisibleChange"
 v-roll:visible=[popupVisible, pVisible]
 ...>
</mt-datetime-picker>
...
data: {
  pVisible: false 
},
methods: {
  handleVisibleChange (isVisible) {
    this.pVisible = isVisible;
  }
}

目前mint-ui还未修复该问题,所以暂且使用上述方案解决。一开始打算改源码,但是不现实,因为以后可能需要更新mint-ui版本。大家也可以帮忙看下以上解决方式是否有坑。

补充知识:Vue中使用mint-ui的日期插件时在ios上会有滚动穿透问题

问题:在ios上选择日期上下滑动时,整个页面会跟着滚动,安卓是正常的

解决方法就是在日期弹出层出现的时候禁止页面的默认滚动机制,日期弹出层消失的时候解除禁止页面的默认滚动机制

1.调用日期组件

<div class="datePicker" style="z-index: 9999">
   <mt-datetime-picker
    type="date"
    ref="picker"
    v-model="nowTime"
    year-format="{value} 年"
    month-format="{value} 月"
    date-format="{value} 日"
    @confirm="handleConfirm"
    :startDate="startDate"
    :endDate="endDate"
   >
   </mt-datetime-picker>
  </div>

2.设置监听函数

data () {
    return {
     birthday:"", //出生日期
     startDate: new Date('1952'),
     endDate:new Date(),
     nowTime:'1992-09-15',
    
     /*---------监听函数--------------*/
     handler:function(e){e.preventDefault();}
    }
   },
   methods:{
    /*解决iphone页面层级相互影响滑动的问题*/
    closeTouch:function(){
     document.getElementsByTagName("body")[0].addEventListener('touchmove',
      this.handler,{passive:false});//阻止默认事件
     console.log("closeTouch haved happened.");
    },
    openTouch:function(){
     document.getElementsByTagName("body")[0].removeEventListener('touchmove',
      this.handler,{passive:false});//打开默认事件
     console.log("openTouch haved happened.");
    },
 }

然后监听,弹窗出现消失的时候调用相应的方法

//侦听属性
watch:{
  signReasonVisible:function(newvs,oldvs){//picker关闭没有回调函数,所以侦听该属性替代
    if(newvs){
      this.closeTouch();
    }else{
      this.openTouch();
    }
  }
},

以下为datetime-picker的处理:(openPicker1为触发打开选择器的事件, handleConfirm (data)是选中日期后的回调函数)

openPicker () {
      this.$refs.picker.open();
      this.closeTouch();//关闭默认事件
 
     },
     handleConfirm (data) {
      let date = moment(data).format('YYYY-MM-DD')
      this.birthday = date;
      this.openTouch();//打开默认事件
 
     },

然后就解决了这个问题!

以上这篇解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript—window对象使用示例
Dec 09 Javascript
一个非常全面的javascript URL解析函数和分段URL解析方法
Apr 12 Javascript
JavaScript前端开发之实现二进制读写操作
Nov 04 Javascript
JS实现网页上随机产生超链接地址的方法
Nov 09 Javascript
JS实现隐藏同级元素后只显示JS文件内容的方法
Sep 04 Javascript
用jquery快速解决IE输入框不能输入的问题
Oct 04 Javascript
微信小程序 教程之wxapp视图容器 swiper
Oct 19 Javascript
基于JavaScript实现的折半查找算法示例
Apr 14 Javascript
深入理解vue.js中$watch的oldvalue与newValue
Aug 07 Javascript
微信小程序radio组件使用详解
Jan 31 Javascript
VueCli3.0中集成MockApi的方法示例
Jul 05 Javascript
vue-路由精讲 二级路由和三级路由的作用
Aug 06 Javascript
基于js实现的图片拖拽排序源码实例
Nov 04 #Javascript
在vant中使用时间选择器和popup弹出层的操作
Nov 04 #Javascript
vue 判断两个时间插件结束时间必选大于开始时间的代码
Nov 04 #Javascript
vant 时间选择器--开始时间和结束时间实例
Nov 04 #Javascript
Vue绑定用户接口实现代码示例
Nov 04 #Javascript
vant picker+popup 自定义三级联动案例
Nov 04 #Javascript
解决vue-pdf查看pdf文件及打印乱码的问题
Nov 04 #Javascript
You might like
第十四节 命名空间 [14]
2006/10/09 PHP
详细介绍PHP应用提速面面观
2006/10/09 PHP
PHP5 安装方法
2007/01/15 PHP
Excel数据导入Mysql数据库的实现代码
2008/06/05 PHP
微信开发之网页授权获取用户信息(二)
2016/01/08 PHP
PHP清除缓存的几种方法总结
2017/09/12 PHP
PHP fprintf()函数用法讲解
2019/02/16 PHP
php中数组最简单的使用方法
2020/12/27 PHP
javascript开发随笔一 preventDefault的必要
2011/11/25 Javascript
javascript事件冒泡详解和捕获、阻止方法
2014/04/12 Javascript
JQuery boxy插件在IE中边角图片不显示问题的解决
2015/05/20 Javascript
利用JS实现数字增长
2016/07/28 Javascript
轻松掌握JavaScript中介者模式
2016/08/26 Javascript
浅谈jQuery中的eq()与DOM中element.[]的区别
2016/10/28 Javascript
[原创]javascript typeof id==='string'?document.getElementById(id):id解释
2016/11/02 Javascript
JS二叉树的简单实现方法示例
2017/04/05 Javascript
gulp解决跨域的配置文件问题
2017/06/08 Javascript
浅谈vue-router 路由传参的方法
2017/12/27 Javascript
JS实现十分钟倒计时代码实例
2018/10/18 Javascript
微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)
2019/01/24 Javascript
Angular中使用ng-zorro图标库部分图标不能正常显示问题
2019/04/22 Javascript
使用Vue.observable()进行状态管理的实例代码详解
2019/05/26 Javascript
Node使用Nodemailer发送邮件的方法实现
2020/02/24 Javascript
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
[08:54]DOTA2-DPC中国联赛 正赛 Aster vs LBZS 选手采访
2021/03/11 DOTA
Python加pyGame实现的简单拼图游戏实例
2015/05/15 Python
详解python使用Nginx和uWSGI来运行Python应用
2018/01/09 Python
pyqt5 获取显示器的分辨率的方法
2019/06/18 Python
Python 获取ftp服务器文件时间的方法
2019/07/02 Python
Python pandas自定义函数的使用方法示例
2019/11/20 Python
python运用pygame库实现双人弹球小游戏
2019/11/25 Python
增大python字体的方法步骤
2020/07/05 Python
python3实现名片管理系统(控制台版)
2020/11/29 Python
HTML5中新标签和常用标签详解
2014/03/07 HTML / CSS
Ellos丹麦:时尚和服装在线
2016/09/19 全球购物
怎样在 Applet 中建立自己的菜单(MenuBar/Menu)?
2012/06/20 面试题