webvr-polyfill icon indicating copy to clipboard operation
webvr-polyfill copied to clipboard

Cardboard UI does not work if application prevents translation of touch events into mouse events

Open wrr opened this issue 8 years ago • 6 comments

Cardboard UI relies on the browser automatic translation of touch events into mouse click events. If the application touchstart handler calls event.preventDefault(), this translation is disabled and Cardboard UI buttons do not function.

A fix could be to also listen for touchstart in CardboardUI.prototype.listen and take client(X|Y) from event.touches[0].client(X|Y) if it is defined.

wrr avatar Nov 15 '16 10:11 wrr

You could also bind to a particular DOM element instead of the window.

On Tue, Nov 15, 2016 at 2:33 AM Jan Wrobel [email protected] wrote:

Cardboard UI relies on the browser automatic translation of touch events into mouse click events. If the application touchstart handler calls event.preventDefault(), this translation is disabled and Cardboard UI buttons do not function.

A fix could be to also listen for touchstart in CardboardUI.prototype.listen and take client(X|Y) from event.touches[0].client(X|Y) if it is defined.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/googlevr/webvr-polyfill/issues/174, or mute the thread https://github.com/notifications/unsubscribe-auth/AAQ8gNiHOS_bl1aLE_8nPiUJUuygR1q1ks5q-YplgaJpZM4KyXfc .

borismus avatar Nov 15 '16 17:11 borismus

I'm not sure this is possible, CardboardUI buttons are rendered in WebGL and do not have corresponding DOM elements, so CardboardUI needs to listen on canvas for click events. Application with custom touch navigation also needs to listen on the same canvas.

wrr avatar Nov 15 '16 21:11 wrr

如果 canvas 的容器 添加了 pointerEvents: "none" CardboardUI 也不会添加事件响应

MooLee89 avatar May 16 '17 09:05 MooLee89

I think @borismus meant that the touch events that stop propagation be bound to particular DOM elements rather than interfering with the canvas element's events.

If I understand this correctly, there are a few work-arounds applications can implement, like not disrupting events while in VR mode. I think it's reasonable that the application is responsible for not interfering with these events making it to the canvas.

Thank you for filing!

jsantell avatar May 31 '17 21:05 jsantell

@jsantell Please consider reopening this issue.

As @wrr pointed out, the standard way handle both touch and mouse events together is to add handlers for both events, and to call preventDefault on touch events (see https://hacks.mozilla.org/2013/04/detecting-touch-its-the-why-not-the-how/).

More so, if an app is relying on third party libraries for processing touch/mouse events which are following this standard practice, it is not necessarily easy to make sure that preventDefault is not called while in VR mode. In my case, I am using a gesture library that processes touch/mouse events on a parent element of the canvas, and calls preventDefault without stopping propagation (so all events propagate to the canvas, while I handle the derived gesture events, and I want the gesture library to continue working while presenting with webvr). Also, preventDefault on touch events is necessary to avoid several undesired browser side effects (such as scrolling or zooming), in addition to avoiding the compatibility 'click' events. I don't see a good way to work around this issue aside from updating webvr-polyfill to support touch + mouse events.

Here is a solution:

var kButtonWidthDp = 28;
var kTouchSlopFactor = 1.5;
CardboardUI.prototype.listen = function(optionsCallback, backCallback) {
  var canvas = this.gl.canvas;
  var hasTouchEventClass = typeof TouchEvent !== 'undefined';
  this.listener = function(event) {
    var midline = canvas.clientWidth / 2;
    var buttonSize = kButtonWidthDp * kTouchSlopFactor;
    var e = hasTouchEventClass && event instanceof TouchEvent ? 
      (event.preventDefault(), event.changedTouches[0]) : event;
    // Check to see if the user clicked on (or around) the gear icon
    if (e.clientX > midline - buttonSize &&
        e.clientX < midline + buttonSize &&
        e.clientY > canvas.clientHeight - buttonSize) {
      event.preventDefault();
      event.stopImmediatePropagation();
      optionsCallback(event);
    }
    // Check to see if the user clicked on (or around) the back icon
    else if (e.clientX < buttonSize && e.clientY < buttonSize) {
      event.preventDefault();
      event.stopImmediatePropagation();
      backCallback(event);
    }
  };
  canvas.addEventListener('click', this.listener, false);
  canvas.addEventListener('touchstart', this.listener, false);
};

speigg avatar Jun 02 '17 22:06 speigg

I'll reopen this and give it a further look and follow up next week -- thanks for the additional info, @speigg

jsantell avatar Jun 02 '17 22:06 jsantell