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);
});

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

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

错误的事件:在我的touchstart事件处理程序中,我调用event.preventDefault(), 告诉浏览器不要滚动,不要合成鼠标事件等。因此,我希望只获取touchstarttouchend事件。我这样做了,第一次点击。但是,第二次点击时,我得到的不是touchstart/ ,而是触摸事件、合成鼠标事件的所有组合,以及第二次点击的偶然事件,甚至是第一次点击的重复事件。详情如下。touchendtouchcancel

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

  • 第一个抽头必须很短(小于~200ms)。
  • 此后第二次轻敲必须很快到来(在第一次轻敲之后小于~450ms touchstart)。
  • 第二个抽头必须距离第一个抽头至少 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的任何地方;我也没有对整个模式进行逆向工程。

如果我不钩mousedownand mouseup,分布大致是这样的:

  • (40%) 1:touchstart 1:touchend 2:touchstart 2:touchcancel
  • (35%) 1:touchstart 1:touchend 2:touchstart 2:touchend (实际期望的行为)
  • (25%) 1:touchstart 1:touchend (第二次点击什么都没有)

因此,如果我不挂钩鼠标事件,它会在三分之一的时间内起作用;如果我愿意假装这touchcancel意味着与 相同的东西touchend,我可以得到高达 75% 的时间。但这仍然很糟糕。

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

  • 我尝试过使用jQuery Mobileevents vmousedownvmouseup但它们并不总是在第二次点击时触发,我怀疑是因为同样的潜在事件怪异。
  • 我可以完全忘记触摸事件,只使用合成的鼠标事件,但是在物理点击和合成鼠标事件的传递之间通常有大约半秒的延迟,而触摸事件是即时的,所以我可以更快地响应. 我还想防止滚动——这是针对全屏游戏的,我不希望用户不小心将地址栏滚动回视图并阻止游戏的一部分——并且preventDefault通常touchstart可以实现这一点(尽管偶尔尽管我的第二个水龙头实际上能够滚动屏幕preventDefault......我想解决整个事件混乱的另一个原因)。
  • 我尝试过第三方 Web 浏览器 ( Dolphin ),但它与事件有同样的问题。所以我猜这可能是底层 WebView 将事件传递给脚本的方式存在问题。

任何人都可以建议一种方法来更改我的 HTML、我的事件处理程序或其他任何东西,以便连续两次快速点击可靠地获取触摸事件?

4

5 回答 5

3

尝试在 Android 浏览器中开发多点触控 HTML5 游戏(并在其他兼容 Android 的浏览器中也尝试过),我认为 Android 2.x 的浏览器根本无法正确支持触摸输入。对于初学者来说,它不支持多点触控,这使得某些游戏无法玩。(显然手机支持多点触控,因为你可以捏缩放等,但浏览器不支持。)然后有很多延迟问题,触摸“粘”等等。我隐约记得读过一些关于触摸输入的手机驱动程序的内容,这些驱动程序并不能真正与真正的多点触控一起使用(即它可以检测单点触摸或捏缩放,仅此而已),但我没有任何参考资料来支持这一点......

显然,Android 4(冰淇淋三明治)修复了它。所以你可能只需要等待Android 4,无论如何它应该很快就会出来,然后再试一次。除此之外,谷歌已经宣布他们计划在未来用移动版本的 Chrome 取代 Android 浏览器,所以希望至少到那时我们的浏览器触摸输入问题将会结束。

于 2011-12-20T14:01:03.557 回答
1

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

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

当我尝试使用该错误使视图不再可聚焦时,setFocusable(false)该错误不再发生。似乎没有出现橙色框。我在运行 os 2.3.4 的小型三星手机上对此进行了测试。我可以肯定的是,这个橙色框并没有重新出现。

如果事实证明这是真的,我们很有可能可以在没有自己的 webview 的情况下解决这个问题。让事情变得更复杂的是,如果将 focusable 属性设置为 false 会触发其他问题。

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

编辑:关于您对问题的评论, 我刚刚创建了一个空白应用程序,其中只有一个 Webview 在将其可聚焦属性设置为 false 后加载您的 url。拜托,如果你有更多的资源来测试它,我会上传给你试试。这是应用程序

于 2011-12-20T14:11:38.217 回答
0

您是否尝试过使用 jQuery Mobile 事件?您可以在此处找到解耦的小部件/插件:

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

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

现在您可以简单地绑定到 jQuery 中的点击、滑动等事件。或者是否有必要区分水龙头的开始和结束?

于 2011-12-20T14:14:06.857 回答
0

而不是附加所有使用你的事件尝试这可能对你有用

 $("...").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
 });
于 2011-12-21T04:22:32.710 回答
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);
});

(对不起,如果代码真的很草率,我并没有真正关注日志功能,但我做了一些小的改动,因为它在我的 touchend 事件中没有正确触发,如 event.touches[0].pageX在那一点上是未定义的。另外,我把它包装在一个准备好的函数中,因为我只是懒惰:-P)

由于这仅跟踪第一次触摸(event.touches[0]),您可能可以通过向下触摸数组进行一些调整以测试多点触摸。我在我的 android 设备(姜饼)上发现的是,如果您同时在屏幕上按下两根手指,则 touchend 事件只会在最后一次触摸松开时触发;即第二根手指释放。

此外,当我附加 mousedown/mouseup 事件侦听器时,我得到了与你对整个橙色突出显示所做的完全相同的事情。

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

于 2011-12-23T00:43:19.787 回答