1. 程式人生 > 程式設計 >解決Mint-ui 框架Popup和Datetime Picker元件滾動穿透的問題

解決Mint-ui 框架Popup和Datetime Picker元件滾動穿透的問題

在移動端開發中使用到了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',{ 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',{ passive: false });
    this.$refs.picker.open();
  },confirm() { // 確認事件
    document.getElementsByTagName('body')[0].addEventListener('touchmove',{ 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',{ 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',{ 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',binding) {
  if (binding.value instanceof Array) {
   const visible = binding.value.some(e => e); // 當檢視所有控制彈層的變數存在一個是true,即可阻止touchmove事件
   if (visible) {
    document.getElementsByTagName('body')[0].addEventListener('touchmove',{ passive: false });
   } else {
    document.getElementsByTagName('body')[0].removeEventListener('touchmove',{ passive: false });
   }
  } else if (typeof binding.value === 'boolean') {
   if (binding.value) {
    document.getElementsByTagName('body')[0].addEventListener('touchmove',{ 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',{passive:false});//阻止預設事件
     console.log("closeTouch haved happened.");
    },openTouch:function(){
     document.getElementsByTagName("body")[0].removeEventListener('touchmove',{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元件滾動穿透的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。