近期項目遇到了vue頁面事件被帶到下一個頁面的問題,也就是我們常說的點透事件,主要表現在android機器上,花了不少時間折騰,簡單做下總結~
- vue頁面之間的切換通過Vue Router的router.push方法
- b.vue之前已經訪問過,數據通過vuex管理,從a.vue進入到b.vue不再請求數據,直接拿到b.vue數據展示頁面;
- a.vue頁面上點擊最底部的賬單后,不到100ms就打開b.vue頁面,此時最底部的賬單的觸摸事件並沒有消失,a.vue的觸摸事件直接平移到b.vue最底部位置,剛好最底部有個按鈕,導致直接打開c.vue。
驗證是否因為300ms延遲導致的問題
早在2013做移動端頁面時候就聽說過點透事件,由click事件引起的300ms的延遲導致:
移動設備上的web網頁是有300ms延遲的,往往會造成按鈕點擊延遲,引起頁面點透或是點擊失效。
2007年蘋果發布首款iphone上IOS系統搭載的safari為了將適用於PC端上大屏幕的網頁能比較好的展示在手機端上,使用了雙擊縮放(double tap to zoom)的方案,比如你在手機上用瀏覽器打開一個PC上的網頁,你可能在看到頁面內容雖然可以撐滿整個屏幕,但是字體、圖片都很小看不清,此時可以快速雙擊屏幕上的某一部分,你就能看清該部分放大后的內容,再次雙擊后能回到原始狀態。
雙擊縮放是指用手指在屏幕上快速點擊兩次,iOS 自帶的 Safari 瀏覽器會將網頁縮放至原始比例。
原因就出在瀏覽器需要如何判斷快速點擊上,當用戶在屏幕上單擊某一個元素時候,例如跳轉鏈接<a href="#"></a>,此處瀏覽器會先捕獲該次單擊,但瀏覽器不能決定用戶是單純要點擊鏈接還是要雙擊該部分區域進行縮放操作,所以,捕獲第一次單擊后,瀏覽器會先Hold一段時間t,如果在t時間區間里用戶未進行下一次點擊,則瀏覽器會做單擊跳轉鏈接的處理,如果t時間里用戶進行了第二次單擊操作,則瀏覽器會禁止跳轉,轉而進行對該部分區域頁面的縮放操作。那么這個時間區間t有多少呢?
在IOS safari下,大概為300毫秒。這就是延遲的由來。造成的后果用戶純粹單擊頁面,頁面需要過一段時間才響應,給用戶慢體驗感覺,對於web開發者來說是,頁面js捕獲click事件的回調函數處理,需要300ms后才生效,也就間接導致影響其他業務邏輯的處理。
事隔7年,瀏覽器廠商還沒修復這個問題么?樓主使用iPhone X、華為、vivo、小米等主流的手機來測試。
首先我們在移動端網頁使用雙擊縮放模式(不設置viewport)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>測試</title> <script type="text/javascript" class="library" src="https://cdn.bootcss.com/zepto/1.1.7/zepto.js"></script> <style type="text/css"> #btn{ margin: 50px 0; width: 200px; height: 40px; text-align: center; font-size: 16px; } </style> </head> <body> <button id="btn">點我查看事件響應時間</button> <script type="text/javascript"> let startTime; let log = function (msg) { let div = $('<div></div>'); div.html((new Date().getTime()) + ': ' + (new Date().getTime() - startTime) + ': ' + msg) $('body').append(div); }; let touchStart = function () { startTime = new Date().getTime(); log('touchStart'); }; let touchEnd = function () { log('touchEnd'); }; let click = function () { log('click'); }; let mouseUp = function () { log('mouseUp'); }; let mouseDown = function () { log('mouseDown'); }; let btn = $('#btn'); btn.bind('touchstart', touchStart); btn.bind('touchend', touchEnd); btn.bind('mousedown', mouseDown); btn.bind('mouseup', mouseUp); btn.bind('click', click); </script> </html>
對應日志如下,可以看到沒有添加viewport的情況,ios可以看到click事件300ms的延遲問題還是存在,Android表現正常。
接着,頁面添加viewport:<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">的情況,ios和android觸發事件后打印的日志並沒有300ms延遲。
從上面結果看出300ms的延遲主要表現在沒有設置viewport的ios手機上,H5頁面我們一般會設置viewport:<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
所以樓主認為我們正常的H5頁面中click事件是不存在300ms延遲的情況
那Android手機上點透事件到底怎么回事呢?
為了進一步驗證不是300ms延遲引起的問題,采用互聯網常見的防點透方案來解決問題:
比較流行的解決方案有v-tap或vue2-touch-events自定義的tap事件,原理是把三個基礎觸摸事件:touchstart、touchmove、touchend組合起來叫tap事件,不使用click。
然而,經過反復測試后不能解決問題,所以樓主猜想本次點透並不是300ms引起的。
還有一種解決點透的方案是添加透明遮罩層,通常在跳轉的下一個頁面上,有一個透明遮罩層置於最頂層,從上一個頁面平移的事件不能觸發當前頁面的事件,然后過300ms后再隱藏該遮罩層,以此來阻止點透,可以暴力解決點透問題。
檢查了這次項目中代碼的HTML結構,其中包含touchstart、touchmove、touchend 、click 4個事件,測試發現click事件的代碼並沒有起作用,
<template> <div class="item" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @click="clickItem" > <slot :data="item"></slot> </div> </template>
因為touchend觸發后有個路由的方法router.push讓頁面跳轉了,click事件並沒有生效,也就是touchend后頁面A把事件帶到了頁面B上,又剛好命中頁面B上的按鈕,從而導致頁面C被打開的點透效果!
touchEnd: function (event) { this.$emit('goToDetail')//跳轉detail頁面 }
發現原因:元素touchend之后觸發瀏覽器默認行為導致點透
猜測給div標簽綁定touchend事件后,元素有了瀏覽器默認的行為,具體的默認行為是什么呢,但它非常像click事件被帶到B頁面。為了驗證猜測,在touchEnd方法中最后一行前添加 event.preventDefault()
preventDefault是事件對象(Event)的一個方法,作用是取消瀏覽器事件的默認行為;
cancelable也是事件對象(Event)的一個方法, 表明該事件是否可以被取消默認行為,如果該事件可以用 preventDefault() 可以阻止與事件關聯的默認行為,則返回 true,否則為 false
touchEnd: function (event) { this.$emit('goToDetail') if(event.cancelable) { event.preventDefault() } }
解決方案:使用preventDefault()來阻止點透
測試發現 event.preventDefault() 能成功阻止了androids手機上vue頁面切換導致事件點透的問題(目前ios並沒有發現事件點透問題,可能在多個版本前修復了這個體驗),也驗證了猜想:div標簽綁定touchend事件后,元素有了瀏覽器默認的行為
vue頁面開發,在HTML結構上添加事件修飾符.prevent,即@touchend.prevent同樣可以調用 event.preventDefault()來阻止默認行為
<template> <div class="item" @touchstart="touchStart" @touchmove="touchMove" @touchend.prevent="touchEnd" > <slot :data="item"></slot> </div> </template>
使用click作為最終事件也可以防止點透
div標簽綁定touchend事件后,元素有了瀏覽器默認的行為,這個所謂的默認行為又觸發click事件,從而引起點透的問題。
那么,如果把this.$emit('goToDetail')的方法綁定到click上,是否可以解決點透問題?
<template> <div class="item" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @click="click" > <slot :data="item"></slot> </div> </template>
click: function () { this.$emit('goToDetail') }
經過測試,在click事件觸發this.$emit('goToDetail')方法,頁面跳轉成功后並不會引起點透的問題。
總結:
- Android設備中,div等標簽綁定touchend事件后,元素有了瀏覽器默認的行為,比如觸發click
- 移動端vue頁面點透事件可以使用事件修飾符.prevent或event.preventDefault() 來阻止瀏覽器默認行為
最后曬上家里2只貓,祝大家聖誕快樂~ 喵