這篇博客有點長,若是你是高手請您讀一讀,能對其中的一些誤點提出來,以避免我誤人子弟,而且幫助我提升javascript
若是你是javascript菜鳥,建議您好好讀一讀,真的理解下來會有不同的收穫css
在下才疏學淺,文中不免會有不一樣程度的錯誤,請您指正留言 html
PS:事件階段一節請看最新博客,以前理解有誤前端
咱們的網頁之因此豐富多彩並具備交互功能,是由於咱們的javascript腳本語言,而javascript與HTML之間的交互又是經過事件機制實現的html5
因此,事件是javascript一大核心,深刻了解事件機制在咱們遇到較困難問題時候十分有幫助java
所謂事件,就是網頁發生的一些瞬間(好比點擊、滑動),在這些瞬間咱們使用事件監聽器(回調函數)去訂閱事件,在事件發生時候咱們的回調函數就會觸發jquery
觀察者模式的javascript事件機制的基石,這種異步事件編程模型,就是用戶產生特定的操做,瀏覽器就會產生特定的事件,咱們如果訂閱了事件,回調就會觸發android
好了,咱們下面就來研究下javascript事件機制的幾個關鍵點。ios
網頁上的佈局很複雜,咱們對頁面的單一操做有可能產生預計之外的影響:chrome
好比我點擊一個span,我可能就想點擊一個span,試試上他是先點擊document,而後點擊事件傳遞到span的,並且並不會在span停下,span有子元素就會繼續往下,最後會依次回傳至document,咱們這裏偷一張圖:
咱們這裏偷了一張圖,這張圖很好的說明了事件的傳播方式
事件冒泡即由最具體的元素(文檔嵌套最深節點)接收,而後逐步上傳至document 事件捕獲會由最早接收到事件的元素而後傳向最裏邊(咱們能夠將元素想象成一個盒子裝一個盒子,而不是一個積木堆積)
這裏咱們進入dom事件流,這裏咱們詳細看看javascript事件的傳遞方式
DOM2級事件規定事件包括三個階段:
① 事件捕獲階段
② 處於目標階段
③ 事件冒泡階段
這裏提及來不太明顯,咱們來一個例子吧:
http://sandbox.runjs.cn/show/l31ucooa
這個代碼比較簡單,咱們主要點擊child便可,這裏要證實的就是點擊事件是先捕獲再冒泡,因此咱們這裏來一個複雜點的關係:
http://sandbox.runjs.cn/show/ij4rih6x
如今這個傢伙就比較實在了,不注意就容易暈的,咱們來稍微理一理:
① 點擊parent,事件首先在document上而後parent捕獲到事件,處於目標階段而後event.target也等於parent,因此觸發捕獲事件
因爲target與currentTarget相等了,因此認爲到底了,開始冒泡,執行冒泡事件
② 點擊child狀況有所不一樣,事件由document傳向parent執行事件,而後傳向child最後開始冒泡,因此執行順序各位必定要清晰
至此,咱們事件傳輸結束,下面開始研究事件參數
所謂事件對象,是與特定對象相關,而且包含該事件詳細信息的對象。
事件對象做爲參數傳遞給事件處理程序(IE8以前經過window.event得到),全部事件對象都有事件類型type與事件目標target(IE8以前的srcElement咱們不關注了)
各個事件的事件參數不同,好比鼠標事件就會有相關座標,包含和建立他的特定事件有關的屬性和方法,觸發的事件不同,參數也不同(好比鼠標事件就會有座標信息),咱們這裏題幾個較重要的
PS:如下的兄弟所有是隻讀的,因此不要妄想去隨意更改,IE以前的問題咱們就不關注了
代表事件是否冒泡
代表是否能夠取消事件的默認行爲
某事件處理程序當前正在處理的那個元素
爲true代表已經調用了preventDefault(DOM3新增)
調用事件處理程序的階段:1 捕獲;2 處於階段;3 冒泡階段
這個屬性的變化須要在斷點中查看,否則你看到的老是0
事件目標(綁定事件那個dom)
true代表是系統的,false爲開發人員自定義的(DOM3新增)
事件類型
與事件關聯的抽象視圖,發生事件的window對象
取消事件默認行爲,cancelable是true時可使用
取消事件捕獲/冒泡,bubbles爲true才能使用
取消事件進一步冒泡,而且組織任何事件處理程序被調用(DOM3新增)
在咱們的事件處理內部,this與currentTarget相同
能夠在document對象上使用createEvent建立一個event對象
DOM3新增如下事件:
UIEvents
MouseEvents
MutationEvents,通常化dom變更
HTMLEvents通常dom事件
建立鼠標事件時須要建立的事件對象須要提供指定的信息(鼠標位置信息),咱們這裏提供如下參數:
1 var type = 'click'; //要觸發的事件類型 2 var bubbles = true; //事件是否能夠冒泡 3 var cancelable = true; //事件是否能夠阻止瀏覽器默認事件 4 var view = document.defaultView; //與事件關聯的視圖,該屬性默認便可,無論 5 var detail = 0; 6 var screenX = 0; 7 var screenY = 0; 8 var clientX = 0; 9 var clientY = 0; 10 var ctrlKey = false; //是否按下ctrl 11 var altKey = false; //是否按下alt 12 var shiftKey = false; 13 var metaKey = false; 14 var button = 0;//表示按下哪個鼠標鍵 15 var relatedTarget = 0; //模擬mousemove或者out時候用到,與事件相關的對象 16 17 var event = document.createEvent('MouseEvents'); 18 event.initMouseEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY, 19 ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
如此,咱們就本身建立了一個event對象,而後能夠傳給咱們本身建立的事件,這個知識點,咱們下面再說
PS:值得注意的是,咱們本身建立的event對象能夠有一點不同的東西,好比咱們的事件對象可能多了一個這種屬性:
event.flag = '葉小釵'
事件模擬是javascript事件機制中至關有用的功能,理解事件模擬與善用事件模擬是判別一個前端的重要依據,因此各位必定要深刻理解(我理解較水)
事件通常是由用戶操做觸發,其實javascript也是能夠觸發的,比較重要的是,javascript的觸發事件還會冒泡哦!!!
意思就是,javascript觸發的事件與瀏覽器自己觸發實際上是同樣的(並不徹底一致)
如此,咱們這裏來經過鍵盤事件觸發剛剛的點擊事件吧,咱們這裏點擊鍵盤便觸發child的點擊,看看他的表現如何
PS:因爲是鍵盤觸發,便不具備相關參數了,咱們能夠捕捉event參數,這對咱們隊事件傳輸的理解有莫大的幫助:
咱們這裏先建立事件參數,而後給鍵盤註冊事件,在點擊鍵盤時候便觸發child的點擊事件,各位試試看:
PS:這個可能須要打開網頁點擊空格測試了
http://sandbox.runjs.cn/show/pesvelp1
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 <style type="text/css"> 5 #p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; } 6 #c { width: 100px; height: 100px; border: 1px solid red; } 7 </style> 8 </head> 9 <body> 10 <div id="p"> 11 parent 12 <div id="c"> 13 child 14 </div> 15 </div> 16 <script type="text/javascript"> 17 alert = function (msg) { 18 console.log(msg); 19 } 20 21 var p = document.getElementById('p'), 22 c = document.getElementById('c'); 23 c.addEventListener('click', function (e) { 24 console.log(e); 25 alert('子節點捕獲') 26 }, true); 27 c.addEventListener('click', function (e) { 28 console.log(e); 29 alert('子節點冒泡') 30 }, false); 31 32 p.addEventListener('click', function (e) { 33 console.log(e); 34 alert('父節點捕獲') 35 }, true); 36 37 p.addEventListener('click', function (e) { 38 console.log(e); 39 alert('父節點冒泡') 40 }, false); 41 42 document.addEventListener('keydown', function (e) { 43 if (e.keyCode == '32') { 44 var type = 'click'; //要觸發的事件類型 45 var bubbles = true; //事件是否能夠冒泡 46 var cancelable = true; //事件是否能夠阻止瀏覽器默認事件 47 var view = document.defaultView; //與事件關聯的視圖,該屬性默認便可,無論 48 var detail = 0; 49 var screenX = 0; 50 var screenY = 0; 51 var clientX = 0; 52 var clientY = 0; 53 var ctrlKey = false; //是否按下ctrl 54 var altKey = false; //是否按下alt 55 var shiftKey = false; 56 var metaKey = false; 57 var button = 0; //表示按下哪個鼠標鍵 58 var relatedTarget = 0; //模擬mousemove或者out時候用到,與事件相關的對象 59 var event = document.createEvent('Events'); 60 event.myFlag = '葉小釵'; 61 event.initEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY, 62 ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget); 63 64 console.log(event); 65 c.dispatchEvent(event); 66 } 67 }, false); 68 </script> 69 </body> 70 </html>
各位,這裏看到了與以前的相同或者不一樣嗎???這些都是很關鍵的哦,其實主要不一樣就是咱們的事件參數沒了鼠標位置,多了一個屬性:
這裏有兩點容易讓各位形成錯覺:
① firefox並不會將myFlag顯示到console下面
② chrome若是使用原生alert會阻止第一次父元素捕獲,因此各位必定要注意
而後這裏還有一個小小知識點:
使用dom.dispatchEvent(event)觸發模擬事件
有了以上知識點,其實對PC端來講基本夠用了,若是再稍微研究下jquery源碼就善莫大焉了,可是在移動端卻有所不一樣,咱們這裏還得來理一理
PS:我這裏主要針對點擊事件
首先,在移動端mouse事件好像就有點不那麼適用了,倒不是說touch事件要比mouse事件好,其實他們底層原理相距不大,主要不一樣點就是:
移動端會多點觸屏
多點觸屏就帶來了事件對象參數的差別,好比說:
touches:爲屏幕上全部手指的信息
PS:由於手機屏幕支持多點觸屏,因此這裏的參數就與手機有所不一樣
targetTouches:手指在目標區域的手指信息
changedTouches:最近一次觸發該事件的手指信息
好比兩個手指同時觸發事件,2個手指都在區域內,則容量爲2,若是是前後離開的的話,就會先觸發一次再觸發一次,這裏的length就是1,只統計最新的
PS:通常changedTouches的length都是1
touchend時,touches與targetTouches信息會被刪除,changedTouches保存的最後一次的信息,最好用於計算手指信息
這裏要使用哪一個數據各位本身看着辦吧,我也不是十分清晰(我這裏仍是使用changedTouches吧)
以上就是mouse與touch主要不一樣點,但這些並非太影響咱們的操做,由於到如今爲止,咱們通常仍是使用的是單擊
國內SPA網站模式較少,目前爲止仍是以單個網頁爲主,spa模式對javascript技術要求較高不說,首次加載量大也是不可避免的問題
加之移動端設備今年才普及,並且各自爭奪領地、爭奪入口,還有其餘緣由,反正現況是有時作移動端的兼容比作IE的兼容還難
就拿簡單的CSS3動畫來講,在ios下就有閃動現象,並且仍是iPhone4s,就現今更新換代來講,此種狀況並不會獲得明顯好轉,並且CSS3動畫狀態保存問題亦是一大難題
另外,網頁想要檢測手機是否安裝APP也是有很大缺陷,移動端的fixed更不要說,這些問題都須要咱們乃至開發商解決
PS:這裏扯得有點遠,咱們繼續下面的話題
click自己在移動端響應是沒有問題的,可是咱們點擊下來300ms 的延遲倒是事實,這種事實形成的緣由就是
手機須要知道你是否是想雙擊放大網頁內容
因此click點擊響應慢,而touch卻不會有這樣的限制,因而移動端的touch至關受歡迎,至於鼠標慢,他究竟有多慢,咱們來看看:
如今咱們在手機上同時觸發二者事件看看區別:
測試地址:(使用手機)
http://sandbox.runjs.cn/show/ey54cgqf
此處手機與電腦有很是大的區別!!!
document.addEventListener('mousedown', mouseDown); document.addEventListener('click', mouseClick); document.addEventListener('mouseup', mouseUp); document.addEventListener('touchstart', touchStart); document.addEventListener('touchend', touchEnd);
這個樣子,在手機上不會觸發click事件,click事件要綁定到具體元素
PS:此處在ios與android上有不同的表現,咱們後面會涉及
通過測試,電腦上touch與click事件的差距不大,可是手機上,當咱們手觸碰屏幕時,要過300ms左右纔會觸發mousedown事件
因此click事件在手機上響應就是慢一拍,咱們前面說過爲何click慢了
能夠看到,在手機上使用click事件其實對用戶體驗並很差,因此咱們可能會逐步使用touch事件,可是真正操做時候你就會知道click的好
好了,此處內容暫時到這,咱們先看看zepto的事件機制,下面會提到如何使用touch提高click的響應速度
zepto是以輕巧的dom庫,這傢伙能夠說是jquery的html5版本,並且在移動端有媲美jqueryPC端的趨勢,若是jquery不予以回擊,可能移動端的份額就不行了
咱們這裏不討論zepto的其餘地方了,咱們單獨講他的事件相關提出來看看
事件註冊是咱們項目開發中用得最多的一塊,咱們通常會使用如下幾種方式綁定事件:
el.on(type, function () {});//力推 el.bind(function(){}); el.click(function() {});//不推薦 dom.onclick = function() {};//須要淘汰
以上幾種方式用得較多,最後一種在真實的項目中基本不用,單數第二也極少使用,那麼zepto內部是怎麼實現的呢?
PS:這裏,我就不詳細說明zepto事件機制了,這裏點一下便可
zepto事件機制其實比較簡單,他具體流程以下:
① 事件註冊時在全局保存事件句柄(handlers = {})
② 提供全局的事件註冊點
③ 提供全局的事件註銷點
④ 提供簡便寫法
$.fn.click = function (fn) { this.bind('click', callback) }
若是須要詳細瞭解的朋友請看此篇博客:
http://www.cnblogs.com/yexiaochai/p/3448500.html
我這裏就不詳細說明了,這裏須要說明的是,zepto提供了兩個語法糖:
這兩個方法,徹底是咱們上面代碼的縮寫,固然他更加健壯,咱們後面就可使用他了
1 $.fn.trigger = function (event, data) { 2 if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event) 3 fix(event) 4 event.data = data 5 return this.each(function () { 6 // items in the collection might not be DOM elements 7 // (todo: possibly support events on plain old objects) 8 if ('dispatchEvent' in this) this.dispatchEvent(event) 9 }) 10 }
1 specialEvents = {} 2 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' 3 4 //根據參數建立一個event對象 5 $.Event = function (type, props) { 6 //當type是個對象時 7 if (typeof type != 'string') props = type, type = props.type 8 //建立一個event對象,若是是click,mouseover,mouseout時,建立的是MouseEvent,bubbles爲是否冒泡 9 var event = document.createEvent(specialEvents[type] || 'Events'), 10 bubbles = true 11 //確保bubbles的值爲true或false,並將props參數的屬性擴展到新建立的event對象上 12 if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) 13 //初始化event對象,type爲事件類型,如click,bubbles爲是否冒泡,第三個參數表示是否能夠用preventDefault方法來取消默認操做 14 event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null) 15 //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法. 16 event.isDefaultPrevented = function () { 17 return this.defaultPrevented 18 } 19 return event 20 }
前面,咱們提到過,咱們移動端的點擊響應很慢,可是touch不會有這種限制,因此zepto爲咱們封裝了一個touch庫:
這個touch庫我的以爲寫得不行,雖然我寫不出來......
拋開其餘東西,咱們將其中點擊的核心給剝離出來
1 tapTimeout = setTimeout(function () { 2 3 // trigger universal 'tap' with the option to cancelTouch() 4 // (cancelTouch cancels processing of single vs double taps for faster 'tap' response) 5 var event = $.Event('tap') 6 event.cancelTouch = cancelAll 7 touch.el.trigger(event) 8 9 // trigger double tap immediately 10 if (touch.isDoubleTap) { 11 touch.el.trigger('doubleTap') 12 touch = {} 13 } 14 15 // trigger single tap after 250ms of inactivity 16 else { 17 touchTimeout = setTimeout(function () { 18 touchTimeout = null 19 touch.el.trigger('singleTap') 20 touch = {} 21 }, 250) 22 } 23 24 }, 0)
拋開其餘問題,這裏5-7行就是觸發TAP事件的核心,咱們這裏簡單說下流程:
① 咱們在程序過程當中爲dom(包裝過的)tap事件(使用addEventListener方式註冊/zepto使用bind便可)
② 點擊目標元素,觸發document的touchstart與touchend,在end時候判斷是否爲一次點擊事件(是否touchmove過多)
③ 若是是便觸發tap事件,因而咱們的事件監聽器便會觸發了
以程序邏輯來講,他這個是沒問題的,他甚至考慮了雙擊與滑動事件,結合前面的知識點,這裏應該很好理解
但就是這段代碼卻帶來了這樣那樣的問題,這些問題就是移動端兼容的血淚史,且聽我一一道來
咱們看看咱們的touch事件的綁定點
$(document.body).bind(......)
這段代碼自己沒什麼問題,在PC端毫無問題,但就是這樣的代碼在手機端(多個手機/多個瀏覽器)下產生了一些區域不可點擊的現象
這其實不徹底是兼容問題,是由於咱們在手機端時候每每喜歡將body設置爲height: 100%,因而這樣會產生一個問題
若是咱們的view長度過程那麼body區域事實上不會增長,因此咱們點擊下面區域時候手機就不認爲咱們點擊的是body了......
這個BUG只能說無語,可是min-height雖然能夠解決點擊BUG卻會帶來全局佈局的問題,因此這個問題依然糾結
好在後面zepto意識到了這個問題將事件綁定改爲了這個:
$(document).bind(......)
因而修復了這個問題
若是說第一個問題致使點是咱們本身的佈局的話,第二個問題的引起點我就以爲是開發人員的問題了
PS:zepto多數是抄寫jquery,touch是本身寫的,就是這個touch就搞了不少問題出來......
這裏咱們先不忙看tap代碼自己帶來的問題,我這裏出一個題各位試試:
1 var sum1 = 0, sum2 = 0, sum3 = 0; len = 2; 2 var arr = []; 3 for (var i = 0; i < len; i++) { 4 arr.push(i) 5 } 6 for (var i = 0; i < len; i++) { 7 setTimeout(function () { 8 sum1 += arr[i]; 9 }, 0); 10 } 11 $.each(arr, function (i, v) { 12 setTimeout(function () { 13 sum2 += v; 14 }, 0); 15 }); 16 for (var i = 0; i < len; i++) { 17 sum3++; 18 } 19 //sum3無論,答出len=2與len=200000時,sum1,sum2的值 20 console.log(sum1); 21 console.log(sum2); 22 console.log(sum3);
各位仔細觀察這個題,會有不同的感受,在sum很大的時候第三個循環確定會耗費超過一秒的時間
按道理說這裏的sum1/sum2會進行相關計算,事實倒是:
settimeout將優先級降到了最低,他會在主幹流程執行結束後纔會執行
因而咱們這裏引出了一個很是有趣的問題,且看zepto源碼:
1 tapTimeout = setTimeout(function () { 2 var event = $.Event('tap') 3 event.cancelTouch = cancelAll 4 touch.el.trigger(event) 5 }, 0)
各位小夥伴,你認爲咱們在第四行後執行e.preventDefault()等操做會有效麼???
或者說,咱們在觸發tap事件後,會執行咱們的回調函數咱們在咱們的回調函數中執行e.preventDefault()等操做會有效麼???
各位小夥伴能夠去試試,我這裏就不作說明了
PS:標題好像泄露了個人行蹤......
其實上面的問題是致使點透的因素之一,所謂點透就是:
這個頁面有三個元素
① 父容器div,咱們爲他綁定了一個tap事件,會打印文字
② 在上的div,咱們爲其綁定了一個tap事件,點擊便消失
③ input,主要用於測試focus問題
如今開啓touch事件的狀況下,咱們點擊上面的div,他會消失,因而:
div消失會觸發div(list)的tap事件
div消失會觸發input獲取焦點事件
表單提交頁,用戶提交時若是信息有誤,會彈出一個提示,而且爲蒙版添加click的關閉事件
可是有tap在的狀況效果就不同了,咱們極有可能點擊提交,彈出提示層,觸發蒙版點擊事件,蒙版關閉!!!
咱們可能遇到這種狀況,咱們在彈出層上作了一些操做後,點擊彈出層關閉彈出層,可是下面有一個input(div有事件也行)
因而觸發了div事件,因而input獲取了焦點,某明奇妙的彈出來鍵盤!!!
以上都屬於touch事件致使的點透現象,有問題就有解決方案,因而咱們就來講針對zepto如何解決點透現象
此方案只針對zepto的tap事件
其實並非全部的tap事件都會產生點透,只不過在頁面切換/有彈出層時候容易出現這個問題
根據zepto事件註冊機制我這裏作了一點修改即可以解決zepto點透問題:因而這裏便引進一個新的事件lazyTap
lazyTap只是名字變了,其實他仍是tap,首先咱們說事件註冊:
el.on('lazyTap', function () { });
如此咱們就註冊了一個lazyTap事件,可是咱們的zepto並不會所以而買帳,並且我也說了他就是tap事件,因而咱們進入事件註冊入口:
1 function add(element, events, fn, selector, getDelegate, capture) { 2 var id = zid(element), 3 set = (handlers[id] || (handlers[id] = [])) //元素上已經綁定的全部事件處理函數 4 eachEvent(events, fn, function (event, fn) { 5 if (event == 'lazyTap') event = 'tap'; 6 element.lazyTap = true; 7 var handler = parse(event) 8 //保存fn,下面爲了處理mouseenter, mouseleave時,對fn進行了修改 9 handler.fn = fn 10 handler.sel = selector 11 // 模仿 mouseenter, mouseleave 12 if (handler.e in hover) fn = function (e) { 13 /* 14 relatedTarget爲事件相關對象,只有在mouseover和mouseout事件時纔有值 15 mouseover時表示的是鼠標移出的那個對象,mouseout時表示的是鼠標移入的那個對象 16 當related不存在,表示事件不是mouseover或者mouseout,mouseover時!$.contains(this, related)當相關對象不在事件對象內 17 且related !== this相關對象不是事件對象時,表示鼠標已經從事件對象外部移入到了對象自己,這個時間是要執行處理函數的 18 當鼠標從事件對象上移入到子節點的時候related就等於this了,且!$.contains(this, related)也不成立,這個時間是不須要執行處理函數的 19 */ 20 var related = e.relatedTarget 21 if (!related || (related !== this && !$.contains(this, related))) return handler.fn.apply(this, arguments) 22 } 23 //事件委託 24 handler.del = getDelegate && getDelegate(fn, event) 25 var callback = handler.del || fn 26 handler.proxy = function (e) { 27 var result = callback.apply(element, [e].concat(e.data)) 28 //當事件處理函數返回false時,阻止默認操做和冒泡 29 if (result === false) e.preventDefault(), e.stopPropagation() 30 return result 31 } 32 //設置處理函數的在函數集中的位置 33 handler.i = set.length 34 //將函數存入函數集中 35 set.push(handler) 36 element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) 37 }) 38 }
這裏五、6行,咱們對咱們傳入的事件類型進行了出來,將它改爲了tap事件,而且在dom上打了一個標記
PS:zepto記錄事件句柄的zid也是記錄至dom屬性的
因而咱們在觸發的時候能夠這樣幹:
1 $.showLazyTap = function (e) { 2 var forTap = $('#forTap'); 3 if (!forTap[0]) { 4 forTap = $('<div id="forTap" style="background: black;color: White; display: none; border-radius: 60px; position: absolute; 5 z-index: 99999; width: 60px; height: 60px"></div>'); 6 $('body').append(forTap); 7 } 8 forTap.css({ 9 top: (e.changedTouches[0].pageY - 30) + 'px', 10 left: (e.changedTouches[0].pageX - 30) + 'px' 11 }) 12 forTap.show(); 13 setTimeout(function () { 14 forTap.hide(); 15 }, 350); 16 } 17 18 tapTimeout = setTimeout(function () { 19 var event = $.Event('tap') 20 event.cancelTouch = cancelAll 21 touch.el.trigger(event) 22 if (touch.el.lazyTap) { 23 $.showLazyTap(e); 24 } 25 }, 0)
如此一來,在咱們tap事件執行後,咱們會彈出一朵菊花,阻止咱們與下面的元素觸碰,而後350ms後消失
這裏去掉菊花的背景就徹底沒有影響了,而後咱們就解決了tap事件帶來的點透問題
最後咱們開始評估,評估後的結果是放棄tap事件,放棄他主要有如下緣由:
① 兼容問題,使用tap事件在電腦上操做不便,自動化測試沒法進行
② 兼容問題,IE內核的手機會完蛋
③ 點透解決方案不完美,蒙版形式不是全部人能接受,而且憑空多出一個lazyTap事件更是不應
因此咱們放棄了這一方案,開始從根本上追尋問題,這正是咱們最初的知識點的交合了
程序界是一個神奇的地方,每當方案不夠完美時便會更加靠近真相,但當你真的想對真相着手時候,卻發現已經有人幹了!
前面已經說過tap的種種弊端,因此原生的click事件依舊是最優方案,因而咱們能夠在click上面打主意了
依舊使用touch事件模擬點擊,卻在tap觸發那一步本身建立一個click的Event對象觸發之:
PS:這裏須要手機測試了
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 <style type="text/css"> 7 #p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; } 8 #c { width: 100px; height: 100px; border: 1px solid red; } 9 </style> 10 </head> 11 <body> 12 <input id="tap1" type="button" value="我是tap" /><br /> 13 <input id="click1" type="button" value="我是click" /> 14 15 <script type="text/javascript"> 16 var tap1 = document.getElementById('tap1'); 17 var click1 = document.getElementById('click1'); 18 var t = 0, el; 19 document.addEventListener('touchstart', function (e) { 20 t = e.timeStamp; 21 el = e.target; 22 }); 23 //注意,此處鼠標信息我沒有管他 24 function createEvent(type) { 25 var bubbles = true; //事件是否能夠冒泡 26 var cancelable = true; //事件是否能夠阻止瀏覽器默認事件 27 var view = document.defaultView; //與事件關聯的視圖,該屬性默認便可,無論 28 var detail = 0; 29 var screenX = 0; 30 var screenY = 0; 31 var clientX = 0; 32 var clientY = 0; 33 var ctrlKey = false; //是否按下ctrl 34 var altKey = false; //是否按下alt 35 var shiftKey = false; 36 var metaKey = false; 37 var button = 0; //表示按下哪個鼠標鍵 38 var relatedTarget = 0; //模擬mousemove或者out時候用到,與事件相關的對象 39 var event = document.createEvent('MouseEvents'); 40 event.initMouseEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget); 41 return event; 42 } 43 document.addEventListener('touchend', function (e) { 44 t = e.timeStamp; 45 var event = createEvent('tap') 46 //觸發tap事件 47 el.dispatchEvent(event); 48 //觸發click 49 var cEvent = createEvent('click'); 50 el.dispatchEvent(cEvent); 51 }); 52 function fnDom(el, msg, e) { 53 el.value = msg + '(' + (e.timeStamp - t) + ')'; 54 } 55 tap1.addEventListener('tap', function (e) { 56 fnDom(this, '我是tap,我響應時間:', e); 57 }); 58 click1.addEventListener('click', function (e) { 59 fnDom(this, '我是click,我響應時間:', e); 60 }); 61 </script> 62 </body> 63 </html>
http://sandbox.runjs.cn/show/8ruv88rb
這裏咱們點擊按鈕後就明顯看到了按鈕開始響應時間是80左右,立刻變成了300多ms,由於click事件被執行了兩次
一次是touchend咱們手動執行,一次是系統自建的click,這就是傳說中的鬼點擊,因而咱們接下來講一說這個鬼點擊
所謂鬼點擊,就是一次點擊執行了兩次,以程序來講,他這個是正常的現象,沒有問題的,可是咱們的業務邏輯不容許這個事情存在
初步解決鬼點擊是比較容易的,直接在touchend處阻止瀏覽器默認事件便可:
1 document.addEventListener('touchend', function (e) { 2 t = e.timeStamp; 3 var event = createEvent('tap') 4 //觸發tap事件 5 el.dispatchEvent(event); 6 //觸發click 7 var cEvent = createEvent('click'); 8 el.dispatchEvent(cEvent); 9 e.preventDefault(); 10 });
按道理來講,這個代碼是沒有問題的(並且同時能夠解決咱們的點透問題),可是在android上狀況有所不一樣
咱們的click依舊執行了兩次!!!!!由此又引入了下一話題,android與ios鼠標事件差別
PS:此點還要作詳細研究,今天淺淺的說幾點
在android上得到的結果是驚人的,這個勞什子android裏面moveover事件偶然比尼瑪touchstart還快!!!
而ios壓根就不理睬mouseover事件,這是主要問題產生緣由!!!
而android在movedown時候,開開心心觸發了input的focus事件,而後鍵盤就彈起來了!!!
因此針對android,咱們還得將mousedown幹掉才行!!!!
而事實上,咱們input獲取焦點,就是經過mousedown觸發的,ios也是,因此要解決android下面的問題還得從其它層面抓起
如今回到咱們最初的知識點:
http://sandbox.runjs.cn/show/muk6q2br
最後追尋好久找到一個解決方案,該方案將上述知識點所有聯繫起來了:
① 咱們程序邏輯時先觸發touch事件,在touchend時候模擬click事件
② 這時咱們給click事件對象一個屬性:
1 var event = document.createEvent('Events'); 2 event.initEvent('click', true, true, window, 1, e.changedTouches[0].screenX, 3 e.changedTouches[0].screenY, e.changedTouches[0].clientX, e.changedTouches[0].clientY, false, false, false, false, 0, null); 4 event.myclick = true; 5 touch.el && touch.el.dispatchEvent(event);
③ 而後按照咱們基礎篇的邏輯,咱們事實上會先執行document上的click事件
咱們這裏作了一個操做,判斷是否包含myclick屬性,有就直接跳出(事件會向下傳遞),若是沒有就阻止傳遞
到此,咱們就解決了鬼點擊問題,固然,不夠完善