安卓浏览器在快速连续两次点击事件时出现错乱问题

9
我正在尝试使用JavaScript和jQuery来捕获触摸事件。但是在Android 2.3.2的Web浏览器中,我看到了一些非常奇怪的行为:每当我轻触屏幕,然后快速在屏幕上其他地方轻触时,浏览器会:
  • 短暂地在整个屏幕上显示橙色边框和高亮,并且
  • 发送给我错误的事件。
橙色边框似乎只是同一基本问题的相关症状,所以我不太担心它——实际上它很方便,可以告诉我浏览器何时出了问题。我真正想知道的是:如何始终获取两次快速轻触的正确触摸事件? 我相信,当这个问题得到解决时,橙色边框也会消失。
以下是我迄今为止解决的所有痛苦细节。 这里是一个显示问题并显示有关接收到的每个事件的详细信息和时间的页面。如果您在蓝色矩形内轻敲,然后快速在黑色矩形内轻敲,您肯定会看到橙色闪光/错误事件。
我的jQuery代码非常标准。 log 函数的实现并不重要;问题在于浏览器没有在应该调用它时调用它。
el = $('#battle');
el.on('touchstart', function(event) {
  log(event);
  return event.preventDefault();
});
el.on('touchend', function(event) {
  return log(event);
});
el.on('touchcancel', function(event) {
  return log(event);
});
el.mousedown(function(event) {
  log(event);
  return event.preventDefault();
});
return el.mouseup(function(event) {
  return log(event);
});

关于我最初描述的现象的更多细节:

橙色边框和高亮:这是浏览器在单击超链接时绘制的相同橙色边框和高亮。但页面上没有超链接,浏览器会在整个屏幕周围--或更具体地说,在我通过jQuery挂钩事件的外部<div id="battle">周围绘制这个橙色边框。

错误的事件:在我的touchstart事件处理程序中,我调用了event.preventDefault(),告诉浏览器不要滚动、不要合成鼠标事件等等。因此,我只期望得到touchstarttouchend事件。对于第一次轻拍,我确实得到了这些事件。但是对于第二次轻拍,我得到了所有数量的组合的触摸事件、合成的鼠标事件,以及偶尔的touchcancel,甚至是第一次轻拍的重复事件。详情见下文。

这种行为也只发生在非常特定的情况下:

  • 第一次轻拍必须很短(小于约200毫秒)。
  • 第二次轻拍必须紧随其后(在第一次touchstart后不到约450毫秒)。
  • 第二次轻拍必须与第一次轻拍的坐标相距至少150像素(沿第一次touchstart的对角线测量)。
  • 如果我删除挂钩mousedownmouseup的代码,则橙色矩形不再出现。但是触摸事件有时仍然混乱。

至于我所说的事件混乱是什么意思,这是我看到的情况。当我写“1:”时,表示事件是为第一次轻拍的坐标;“2:”表示第二次轻拍的坐标。我看到了以下事件模式(百分比表示每个事件在100次试验中出现的次数):

  • (50%) 1:touchstart 1:touchend 1:mousedown 1:mouseup(短暂延迟)2:mousedown 2:mouseup
  • (35%) 1:touchstart 1:touchend 2:touchstart 1:mousedown 1:mouseup 2:touchend
  • (10%) 1:touchstart 1:touchend 2:touchstart 1:mousedown 1:mouseup 2:touchcancel(短暂延迟)2:mousedown 2:mouseup
  • (3%) 1:touchstart 1:touchend 2:touchstart 2:touchend(短暂延迟)1:mousedown 1:mouseup
  • (2%) 1:touchstart 1:touchend 1:mousedown 1:mouseup (第二次轻拍根本没有任何事件)
有些事件组合似乎取决于我点击的速度,但我还没有完全掌握模式。(两次快速而清晰的敲击似乎更有可能出现在上面的第二个项目中,而更快速但不太清晰的方法似乎更有可能是第一个项目。但我还没有确定导致每个事件的具体时间数字。)同样,上面指示的“短延迟”可以从〜150ms到〜400ms的任何位置;我也没有逆向工程整个模式。

如果我不挂钩mousedownmouseup,分布大致如下:

  • (40%) 1:touchstart 1:touchend 2:touchstart 2:touchcancel
  • (35%) 1:touchstart 1:touchend 2:touchstart 2:touchend (实际期望的行为)
  • (25%) 1:touchstart 1:touchend (第二次轻敲没有反应)

因此,如果我不挂钩鼠标事件,则它将有三分之一的成功率;如果我愿意假装touchcanceltouchend的含义相同,我可以将其提高到75%。但这仍然很糟糕。

我已经尝试过的替代方法:

  • 我尝试使用jQuery Mobilevmousedownvmouseup事件,但它们并不总是为第二次轻敲触发,我怀疑是由于这种潜在的事件异常。
  • 我可能只需完全忘记触摸事件,并仅使用合成的鼠标事件,但物理轻敲和传递合成的鼠标事件之间通常会有约半秒钟的延迟,而触摸事件是即时的,因此我可以更具响应性。我还想防止滚动-这是一个全屏游戏,我宁愿不让用户意外滚动地址栏回到视图并阻挡游戏的一部分-在touchstart上执行preventDefault通常可以实现这一点(尽管偶尔第二次轻敲实际上能够滚动屏幕,尽管我进行了preventDefault…这就是我想解决整个事件混乱的原因之一)。
  • 我尝试过第三方Web浏览器(Dolphin),但它也存在事件问题。因此,我猜测这可能是底层WebView将事件传递给脚本的方式存在问题。

有人能建议一种方法来更改我的HTML、事件处理程序或其他任何内容,以可靠地获取连续两次快速轻敲的触摸事件吗?


它在我的 Galaxy Tablet(3.0)上运行良好。我在小的 GingerBread 手机(2.3.4)上遇到了这个问题。我认为我能够使用自己的 webView 解决它,但问题是我不能读取表格,因为手机太小了;但可以肯定的是,橙色不再出现了。您是否打算使用 WebView 或者那不是一个选项? - Sherif elKhatib
@Sherif,目前我只是将文件放在Web服务器上,但我有一些模糊的计划,打算在某个时候尝试将其制作成PhoneGap应用程序。这回答了你对WebView的问题吗?(我现在基本上对Android开发一无所知——我只是想让它在浏览器中工作。但我总是可以学习的。) - Joe White
5个回答

3
尝试在Android浏览器(以及其他兼容的Android浏览器)中开发多点触控HTML5游戏后,我认为Android 2.x的浏览器并不完全支持触摸输入。首先,它不支持多点触控,这使得某些类型的游戏无法玩。 (显然手机支持多点触控,因为可以捏缩等操作,但是浏览器不支持。)接下来有很多延迟问题,触摸“粘滞”等问题。我模糊地记得读过一些关于手机触摸输入驱动程序实际上不能使用真正的多点触控(即它只能检测单点触控或捏缩操作),但我没有任何参考资料来证实这一点...

显然,Android 4(冰激凌三明治)修复了这个问题。所以你可能只需要等待Android 4版本的发布,它应该很快就会出来,然后再试一试。此外,Google已经宣布计划用Chrome的移动版取代Android浏览器,因此希望至少到那时我们的浏览器触摸输入问题将得到解决。


我曾经认为,除非设备制造商发布了自己的操作系统版本,包括他们的自定义驱动程序等,否则你甚至无法升级 Android。那么期望人们升级实际上有多现实呢?(诚实提问,不知道答案) - Joe White
多点触控取决于具体的手机型号,但是大部分手机都支持真正的多点触控。 - stealthcopter
@JoeWhite,你不能期望人们升级他们的Android版本。旧手机不会从制造商那里收到更新,因此入侵是唯一的可能性。那不是新手用户所能做的! - daan
所以基本上这个答案的意思是“你没有办法,你得等几年直到没有人再使用Android 2设备了”?如果真的是这样那就太糟糕了。 (尽管问题的第一个评论建议我的特定问题可能在Android 3中得到解决,这比等待Android 4进入市场要好一点。) - Joe White

1

这是一个理论:无法进行广泛测试

根据 webview源代码中的init函数,在webview初始化期间,语句setFocusable(true)总是被调用。

当我尝试使用setFocusable(false)使视图不再可聚焦时,错误没有再次发生。似乎橙色框不再出现。我在运行2.3.4操作系统的小型三星手机上进行了测试。我确定这个橙色框没有再次出现。

如果证明这是真的,那么我们很有可能可以解决这个问题,而不需要自己的webview。更复杂的是,如果将focusable属性设置为false会触发其他问题。

最后,我认为我们无法从javascript中控制此属性(或者我们可以吗?)。也许您可以声明特定控件或整个文档不是输入或类似的东西?我只是推断,所以这可能是错误的。

编辑:关于您在问题中的评论 我刚刚创建了一个空白应用程序,只包含一个Webview,将其焦点属性设置为false后加载您的URL。如果您有更多资源来测试它,请告诉我,我会上传给您尝试。这是应用程序链接


好消息是,您的应用程序确实可以防止橙色矩形出现。坏消息是,它无法防止事件混乱。事件模式看起来与我在Web浏览器中的情况差不多,包括第二次轻击偶尔不会生成任何事件的问题。唉。不过,侧面思考值得肯定。 - Joe White
最终,我认为这将是最有效的答案。根据我为这个问题所做的研究,我认为我可能能够同时监听触摸和鼠标事件,并忽略那些与触摸事件重复的鼠标事件。然后最大的问题是当鼠标事件被合成时出现的橙色矩形,而你的答案给了我一种处理的方法。 - Joe White

0

你尝试过使用jQuery Mobile事件吗?你可以在这里找到解耦的小部件/插件:

https://github.com/jquery/jquery-mobile/tree/master/js

你可能需要jquery.mobile.event.js和jquery.mobile.vmouse.js

现在你可以在jQuery中简单地绑定tap、swipe等事件。或者有必要区分tap的开始和结束吗?


1
正如我在问题的结尾提到的那样(“我已经尝试过的替代方法:”),我已经尝试过jQuery Mobile的vmousedownvmouseup事件,它们遇到了相同的问题 - 有时第二次轻触根本不会触发任何事件。我认为tap事件也会因为同样的原因而失败(而且,除了jQuery Mobile提供的一些自定义手势外,我还需要支持一些自定义手势)。 - Joe White

0
你尝试过直接附加事件吗?
$(document).ready(function(){
    var el, hypot, log, prev, resetTimeout, start, prevTouchX, prevTouchY;
    var resetTimeout = null;
    var start = null;
    var prev = null;
    var resetTimeout;

    var hypot = function(x1, y1, x2, y2) {
        var dx, dy, h;
        dx = x2 - x1;
        dy = y2 - y1;
        h = Math.sqrt(dx * dx + dy * dy);
        return Math.round(h * 10) / 10;
    };


    var logf = function(event) {
        var div, table, values, x, y, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
        if (event.type === "touchend"){
            x = prevTouchX;
            y = prevTouchY;
        } else {
            x = event.touches[0].pageX;
            y = event.touches[0].pageY;
            prevTouchX = x;
            prevTouchY = y;
        }
        div = $('.log :first');
        table = div.find('table');
        if (table.length === 0) {
            $('.log div:gt(1), .log hr:gt(0)').remove();
            table = $('<table border style="border-collapse: collapse;"><tr>\n<th rowspan="2">Event</th>\n<th rowspan="2">X</th>\n<th rowspan="2">Y</th>\n<th colspan="4">From start</th>\n<th colspan="4">From prev</th>\n</tr><tr>\n<th>&Delta;T (ms)</th>\n<th>&Delta;X</th>\n<th>&Delta;Y</th>\n<th>Distance</th>\n<th>&Delta;T (ms)</th>\n<th>&Delta;X</th>\n<th>&Delta;Y</th>\n<th>Distance</th>\n</tr></table>');
            div.append(table);
        }
        values = {
            time: event.timeStamp,
            x: x,
            y: y
        };
        if (start == null) {
            start = values;
        }
        if (prev == null) {
            prev = values;
        }
        table.append("<tr>\n<td>" + event.type + "</td>\n<td>" + x + "</td>\n<td>" + y + "</td>\n<td>" + (event.timeStamp - start.time) + "</td>\n<td>" + (x - start.x) + "</td>\n<td>" + (y - start.y) + "</td>\n<td>" + (hypot(x, y, start.x, start.y)) + "</td>\n<td>" + (event.timeStamp - prev.time) + "</td>\n<td>" + (x - prev.x) + "</td>\n<td>" + (y - prev.y) + "</td>\n<td>" + (hypot(x, y, prev.x, prev.y)) + "</td>\n</tr>");
        prev = values;

        if(resetTimeout !== null){
            window.clearTimeout(resetTimeout)
        }
        resetTimeout = window.setTimeout(function(){
            start = null;
            prev = null;
            $('.log').prepend('<hr/>');
        }, 1000);
    };
    var battle = document.getElementById("battle");
    battle.addEventListener("touchstart",logf,  false);
    battle.addEventListener("touchmove",function(e){logf(e);e.preventDefault();},  false);
    battle.addEventListener("touchend",logf,  false);
    battle.addEventListener("touchcancel",logf,  false);
});

(抱歉,如果代码很糟糕,我并没有真的关注日志功能,但我进行了一些微小的更改,因为在我的触摸结束事件中,event.touches [0] .pageX 在那时是未定义的。另外,我将其包装在一个ready函数中,因为我只是懒得去做 :-))

由于这仅跟踪第一个触摸(event.touches [0]),因此您可以通过向下移动touches数组来进行一些调整以测试多点触控。我在我的Android设备上(姜饼)发现,如果您同时在屏幕上放下两个手指,则当最后一个触摸释放时才会触发touchend事件;即第二个手指释放。

此外,当我附加mousedown/mouseup事件侦听器时,我得到了与您完全相同的橙色高亮显示问题。

我测试的设备是带有OTA姜饼更新的三星Droid Charge。


这并没有解决原来的问题,反而引入了一个新问题。偶尔会生成预期的事件,但通常第二次点击要么根本不生成事件,要么(在您的更改中出现的新行为)会生成一个没有相应的touchendtouchcanceltouchstart - Joe White
测试页面,将您的JavaScript替换为我的:http://sandbox.excastle.com/stackoverflow/android-tap-twice-2.html 在我的Vizio VTAB1008上运行Android 2.3.2时,重现了我提到的问题。 - Joe White

0

不要使用 on 来附加所有事件,你可以尝试这个方法,也许它适用于你

 $("...").bind("mousedown touchstart MozTouchDown", function(e) {
   if(e.originalEvent.touches && e.originalEvent.touches.length) {
    e = e.originalEvent.touches[0];
    } else if(e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
    e = e.originalEvent.changedTouches[0];
    }

// Handle mouse down
 });

那你的意思是,只需要使用 bind 而不是 on 吗?我刚试了一下,但没有帮助。 - Joe White

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接