Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen to click event on Android Chrome in WebVR/A-Frame?

I'm trying to listen to click events (To be exact, magnet pull from google cardboard) on Android Chrome, but this seems to not work if the device goes into VR mode. I'm using Samsung Galaxy S7 for testing:

JS:

window.addEventListener('click', function (evt) {
    console.log("test")
});

On Samsung's built in Android browser, the log is printed both in and out of VR mode. In Android Chrome, logging is only shown when the browser is not in VR mode.

HTML:

<a-entity camera="userHeight: 1.6" restrict-position look-controls>
    <a-entity cursor="fuse: true; fuseTimeout: 500"
                position="0 0 -1"
                geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
                material="color: black; shader: flat">
    </a-entity>
</a-entity>

I'm using A-Frame ver 0.7.0, but this issue is reproducible just from using native WebVR APIs as well.

I thought the canvas might be consuming the click events, so I tried to add the eventlistener to Canvas directly. This also did not work.

Is this a bug in Chrome? Is there any workaround? I just need to be able to listen to button presses.

like image 856
l46kok Avatar asked Mar 05 '18 03:03

l46kok


1 Answers

I've finally found an approach that works. We have to use native WebVR APIs:

function addVRClickListener(clickCallback) {
    let lastButtonState = [];
    let presentingDisplay = null;

    // Set up a loop to check gamepad state while any VRDisplay is presenting.
    function onClickListenerFrame() {
      // Only reschedule the loop if a display is still presenting.
      if (presentingDisplay && presentingDisplay.isPresenting) {
        presentingDisplay.requestAnimationFrame(onClickListenerFrame);
      }
      
      let gamepads = navigator.getGamepads();
      for (let i = 0; i < gamepads.length; ++i) {
        let gamepad = gamepads[i];
        // Ensure the gamepad is valid and has buttons.
        if (gamepad &&
            gamepad.buttons.length) {
          let lastState = lastButtonState[i] || false;
          let newState = gamepad.buttons[0].pressed;
          // If the primary button state has changed from not pressed to pressed 
          // over the last frame then fire the callback.
          if (newState && !lastState) {
            clickCallback(gamepad);
          }
          lastButtonState[i] = newState;
        }
      }
    }

    window.addEventListener('vrdisplaypresentchange', (event) => {
      // When using the polyfill, CustomEvents require event properties to
      // be attached to the `detail` property; native implementations
      // are able to attach `display` directly on the event.
      var display = event.detail ? event.detail.display : event.display;
      if (display.isPresenting) {
        let scheduleFrame = !presentingDisplay;
        presentingDisplay = display;
        if (scheduleFrame)
          onClickListenerFrame();
      } else if (presentingDisplay == display) {
        presentingDisplay = null;
      }
    });
  }

  

Then we can register the click:

  addVRClickListener(onClick);

The problem I see in A-frame's code is that it's not accounting for display change (non vr -> vr) for propagating the touch events.

This seems to be a bug in A-Frame.

like image 156
l46kok Avatar answered Sep 29 '22 02:09

l46kok