为移动端网页构造快速响应按钮

摘要:
按钮的响应时间比普通HTML按钮快得多。允许按钮响应touchEnd事件而不是单击事件。1.如果用户触摸屏幕的一部分并导致按钮的touchEnd事件,我们还可以通过向按钮添加onclick处理程序来解决第三个问题。我们的touchEnd处理程序仍然可以确保按钮快速响应。删除重单击以再次向按钮添加onclick处理程序。

背景

在谷歌,我们不断地推测手机网页应用的可能性。像HTML5这样的技术使我们网页版的应用以及运行在手机设备上的原生应用。而这些技术的成就之一就是我们开发了一种新的创建按钮的方法,使按钮的响应时间远远快于一般的HTML按钮。在此之前的按钮或者其他响应事件,我们可能会设计一个点击事件。例如:

<button onclick='signUp()'>Sign Up!</button>
这种方法的问题是,当你开始点击按钮开启点击事件时,浏览器会停留大约300毫秒的时间。这是因为浏览器在等待,看你是否双击按钮。对于大多数的按钮,我们在开发的时候就知道不会执行双击事件,所以点击后等待的这段时间是在浪费用户的时间。我们在Google Voice手机网页应用上第一次使用这种技术,目的是想让用户拨号时有更快的相应速度。

处理触摸事件

这个技术涉及到一点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来阻止他。

为移动端网页构造快速响应按钮第1张为移动端网页构造快速响应按钮第2张
(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/call
                    var 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');
                });
            }
View Code

总结

基于这一点,你应该很容易就可以创建快速响应的按钮。通过一些奇特的方式,你能够使这些按钮看起来像是基于你的开发平台的本地按钮一样。已经有一些解决同样问题的移动javascript库可以使用了,但是我们还从未见过任何一个能够提供click事件的优雅降级或者幽灵点击事件的解决方案的js库。我们希望浏览器的开发者们能够在将来的版本中,通过当网站的缩放被禁止(通过使用viewport的meta标签)时,能直接触发click事件的方式解决这个问题。实际上,这已经是姜饼版安卓浏览器要解决的事情了。

英文原文:Creating Fast Buttons for Mobile Web Applications

免责声明:文章转载自《为移动端网页构造快速响应按钮》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇js实现四舍五入Math.round与toFixed的比较虹软人脸识别SDK接入Milvus实现海量人脸快速检索下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

pwa介绍学习

1.PWA简介: Progressive Web App 是由谷歌提出推广的,在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验。 2.PWA到底有哪些过人之处(特性)? 1、Installability(可安装性):添加到主屏2、App Shell:第一次渲染渲个壳、等异步数据来了再填充3、Offline Web App (离线能力)4...

JavaScript核心之事件详解(EventTarget接口,js事件传播,Event对象)

事件是一种异步编程的实现方式,本质上是程序各个组成部分之间传递的特定消息。DOM支持大量的事件,本节介绍DOM的事件编程。 1 EventTarget接口DOM的事件操作(监听和触发),都定义在EventTarget接口。Element节点、document节点和window对象,都部署了这个接口。此外,XMLHttpRequest、AudioNode、A...

UniAPP 利用sqlite保存数据

背景:利用uniapp开发一个APP,APP需要在断网的情况下,临时保存数据,把数据保存在uniapp的sqlite里面,这样可以随时的取到所保持的数据。 1.在uniapp的工程中需要添加sqlite数据库,如下图   2.在uniapp的共同组件中添加下面文件。 function openComDB(name, path, callback) {...

Android浏览器软键盘中的回车(确认)会触发表单提交的问题解决办法

     最近在公司里做项目的时候遇到一个问题,开发出来的网页需要在Android手机中的浏览器中打开,当在表单中的文本框内输入内容时,如果这时点软键盘中的回车会导致表单被提交,而不是正常的点击按钮提交。     经上网查询了相关资料与自己的测试,下面发出解决的代码:     在表单的onSubmit事件中加入以下代码:     Javascript...

百度地图在项目中的使用(JS)

废话先: 这个项目是使用ASP.NET MVC 写的,而关于百度地图在项目中的应用不限于ASP.NET MVC 因为我大部分的API的使用是通过Javascript,想在项目中使用百度地图,你得先成为百度的开发者,具体的步骤,在本篇博文中不多叙述。 主题: 在使用百度地图的时候,你得先要获得一个ak 这里就是点击创建应用,它提供了几个类别1.for...

v-model的实现原理

基础用法 v-model 本质上不过是语法糖,可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。v-model 会忽略所有表单元素的 ...