背景在谷歌,我們不斷地推測手機網頁應用的可能性。像HTML5這樣的技術使我們網頁版的應用以及運行在手機設備上的原生應用。而這些技術的成就之一就是我們開發了一種新的創建按鈕的方法,使按鈕的響應時間遠遠快于一般的HTML按鈕。在此之前的按鈕或者其他響應事件,我們可能會設計一個點擊事件。例如: |
處理觸摸事件這個技術涉及到一點javascript,允許按鈕對touchEnd事件響應而不是click事件。touchEnd事件的觸發是沒有延遲的,所以能夠明顯的比click事件要快,但是仍有一些問題值得考慮: 1.如果用戶輕觸了屏幕的某個地方然后引起了一個按鈕的touchEnd事件,而我們不應該由此觸發一個click事件。 2.如果用戶按下了按鈕,然后在屏幕上拖動了一段距離,然后引起按鈕的touchEnd事件,此時我們也不應該觸發click事件。 3.我們希望當用戶按下按鈕時,能夠給這個按鈕一個按下的狀態,從而使得其突出顯示。 |
我們能夠通過監聽touchStart和touchMove事件解決前兩個問題。如果在按鈕上之前有touchStart事件,那么我們才會考慮在按鈕上的touchEnd事件。同樣,如果有一個touchMove事件且同touchStart的位置相比移動超過了某個閾值,那么我們就不應該把這個touchEnd事件當做click事件來處理。 我們也可以通過給按鈕添加一個onclick處理函數來解決第三個問題。那么做會恰好讓瀏覽器于把他當做按鈕,而我們的touchEnd處理函數仍能夠確保這個按鈕響應很快。同樣,一個onclick處理函數的存在,也能讓那些不支持touch事件的瀏覽器優雅降級。 |
消除幽靈點擊重新添加onclick處理函數給按鈕,會引發最后一個令人討厭的問題。但你輕觸按鈕時,一個click事件仍然會在300ms后被觸發。現在這個click處理函數就有被運行兩次的危險。這個可以通過在touchStart事件中調用preventDefault?很容易被解決。在touchStart事件中調用preventDefault方法將會阻止當前的輕觸所引發的的click和scrolling。我們希望用戶可以滾動頁面,即使他們從按鈕的位置開始滾動,所以我們不認為這是一個可接受的解決方案。我們想出的能解決幽靈點擊的方法叫做click buster(點擊破壞者)。我們所做的只是在頁面body中添加一個click的監聽器,在捕獲階段監聽。當我們的監聽器被觸發,我們就會嘗試判定這個click事件是不是我們已經當做tap事件來處理的結果。如果是的話,我們就可以調用preventDefault和stopPropagation來阻止他。 |


(function(){/** * From: http://code.this.com/mobile/articles/fast_buttons.html* Also see: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button*//** For IE8 and earlier compatibility: https://developer.mozilla.org/en/DOM/element.addEventListener */function addListener(el, type, listener, useCapture){if (el.addEventListener) {el.addEventListener(type, listener, useCapture);return {destroy: function(){el.removeEventListener(type, listener, useCapture);}};}else {var handler = function(e){listener.handleEvent(window.event, listener);}el.attachEvent('on' + type, handler);return {destroy: function(){el.detachEvent('on' + type, handler);}};}}var isTouch = "ontouchstart" in window;/* 構建fastbutton與元素的引用并單擊處理程序. */this.FastButton = function(element, handler, useCapture){// 收集功能調用清除事件 this.events = [];this.touchEvents = [];this.element = element;this.handler = handler;this.useCapture = useCapture;if (isTouch) this.events.push(addListener(element, 'touchstart', this, this.useCapture));this.events.push(addListener(element, 'click', this, this.useCapture));};/* 移除事件處理時,不再需要這個按鈕 */this.FastButton.prototype.destroy = function(){for (i = this.events.length - 1; i >= 0; i -= 1) this.events[i].destroy();this.events = this.touchEvents = this.element = this.handler = this.fastButton = null;};/* 作為一個事件調度 */this.FastButton.prototype.handleEvent = function(event){switch (event.type) {case 'touchstart':this.onTouchStart(event);break;case 'touchmove':this.onTouchMove(event);break;case 'touchend':this.onClick(event);break;case 'click':this.onClick(event);break;}};/* 保留對touchStart位置的引用,然后開始監聽touchMove和touchEnd事件。調用stopPropagation來保證另一個動作不會再次處理同樣的點擊事件. */this.FastButton.prototype.onTouchStart = function(event){event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);this.touchEvents.push(addListener(this.element, 'touchend', this, this.useCapture));this.touchEvents.push(addListener(document.body, 'touchmove', this, this.useCapture));this.startX = event.touches[0].clientX;this.startY = event.touches[0].clientY;};/* 當一個touchMove事件被觸發,檢查用戶是否推動超過10px這個閾值. */this.FastButton.prototype.onTouchMove = function(event){if (Math.abs(event.touches[0].clientX - this.startX) > 10 || Math.abs(event.touches[0].clientY - this.startY) > 10) {this.reset(); //如果ture,然后取消觸摸事件 }};/*觸發一個實際的click處理函數,如果有touchEnd事件,就阻止幽靈點擊事件. */this.FastButton.prototype.onClick = function(event){event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);this.reset();// Use .call to call the method so that we have the correct "this": https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/callvar result = this.handler.call(this.element, event);if (event.type == 'touchend') clickbuster.preventGhostClick(this.startX, this.startY);return result;};this.FastButton.prototype.reset = function(){for (i = this.touchEvents.length - 1; i >= 0; i -= 1) this.touchEvents[i].destroy();this.touchEvents = [];};this.clickbuster = function(){}/* 調用preventGhostClick來消除掉所有的在2.5s內且在不超過保留的x,y坐標周圍25px的點擊事件 */this.clickbuster.preventGhostClick = function(x, y){clickbuster.coordinates.push(x, y);window.setTimeout(clickbuster.pop, 2500);};this.clickbuster.pop = function(){clickbuster.coordinates.splice(0, 2);};/*如果我們 在給定的范圍和時間閾值里捕捉到一個click事件,我們調用stopPropagation和preventDefault。調用preventDefault能夠阻止鏈接變為activated狀態。 */this.clickbuster.onClick = function(event){for (var i = 0; i < clickbuster.coordinates.length; i += 2) {var x = clickbuster.coordinates[i];var y = clickbuster.coordinates[i + 1];if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) {event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);event.preventDefault ? event.preventDefault() : (event.returnValue = false);}}};if (isTouch) {// 不需要使用我們的自定義功能,因為我們只需要觸摸設備上點擊document.addEventListener('click', clickbuster.onClick, true);clickbuster.coordinates = [];}})(this);window.onload = function(){new FastButton(document.getElementById('id2'), function(){alert('click');});}
總結基于這一點,你應該很容易就可以創建快速響應的按鈕。通過一些奇特的方式,你能夠使這些按鈕看起來像是基于你的開發平臺的本地按鈕一樣。已經有一些解決同樣問題的移動javascript庫可以使用了,但是我們還從未見過任何一個能夠提供click事件的優雅降級或者幽靈點擊事件的解決方案的js庫。我們希望瀏覽器的開發者們能夠在將來的版本中,通過當網站的縮放被禁止(通過使用viewport的meta標簽)時,能直接觸發click事件的方式解決這個問題。實際上,這已經是姜餅版安卓瀏覽器要解決的事情了。 |
英文原文:Creating Fast Buttons for Mobile Web Applications