aframevr / aframe

:a: Web framework for building virtual reality experiences.
https://aframe.io/
MIT License
16.69k stars 3.98k forks source link

Using WASD controls not working when scene is embedded (and body isn't the document.activeElement) #2304

Open weddingdj opened 7 years ago

weddingdj commented 7 years ago

Description:

The scene is embedded inside a bootstrap modal dialog and the shouldCaptureKeyEvent method ( https://github.com/aframevr/aframe/blob/master/src/utils/index.js#L176 ) returns false because, in my case document.activeElement is the modal dialog div which does not equal to the document.body.

return document.activeElement === document.body;

What is the reason for this statement?

donmccurdy commented 7 years ago

That check is in place so that WASD controls won't move when the player is typing into an <input/> or <textarea/> — say, a chatbox or signup form. Other ideas? How are you using the modal dialog?

weddingdj commented 7 years ago

I see. In my case the modal-body contains the scene and I show the modal dialog when the users clicks a button (http://getbootstrap.com/javascript/#modals). It is a standard use. The console shows that document.activeElement is <div class="modal fade" tabindex="-1" role="dialog"> instead of body, because the focus is set on the modal div and not the body.

Would it be possible to just take WASD user input when the focus is on the canvas?

weddingdj commented 7 years ago

I will play with it, either we leave the impl. as it is, or I propose a change. I just saw that I can manually set the focus to an element so I could force the focus to the body, even if the modal is open. Let's see how it goes.

I think my use case is specific and I guess that not having WASD navigation in a scene which is embedded is ok for most people.

weddingdj commented 7 years ago

I had no success in setting the focus on body manually, after opening the bootstrap modal dialog. My personal solution would be to patch aframe.js and override the method in utils.js unless you say that WASD navigation must work for embedded scenes, no matter where they are embedded. But I have no idea how this could be achieved, without eg. triggering input in textfields.

weddingdj commented 7 years ago

One possible solution would be to add tabindex="1" ( http://stackoverflow.com/questions/29992688/how-to-keep-the-focus-on-a-canvas-always ) to each injected canvas which forces the focus on the canvas and not on the body. If I could reference the current canvas in https://github.com/aframevr/aframe/blob/master/src/utils/index.js#L176 we could do return document.activeElement === canvas

How do I get the current canvas in utils?

I could setup a codepen with several embedded canvas including one in a bootstrap modal and see if the behaviour is as expected.

dmarcos commented 7 years ago

@chriscar you should have access to the canvas by doing sceneEl.canvas. We can maybe create a specific method in wasd-controls instead of using the utils one to test the solution

dmarcos commented 7 years ago

@cvan might have some ideas.

cvan commented 7 years ago

@chriscar: @dmarcos: @donmccurdy: if the iframe is on the same origin, then you should be able to do this:

window.addEventListener('DOMContentLoaded', function () {
  // Find an iframe already on the page.
  var iframe = document.querySelector('iframe');
  if (iframe) {
    iframe.addEventListener('load', function () {
      try {
        iframe.contentWindow.focus();
      } catch (err) {
        console.warn('Could not focus iframe', err);
      }
    });
    if (iframe.contentWindow) {
      iframe.contentWindow.focus();
    }
  }
});

Or, ideally, if you're dynamically creating an <iframe> from JS:

// Create an iframe.
var iframe = document.createElement('iframe');
iframe.src = 'my_scene.html';
iframe.allowfullscreen = 'true';
iframe.enable = 'vr';  // See https://github.com/w3c/webvr/issues/86
iframe.addEventListener('load', function () {
  try {
    iframe.contentWindow.focus();
  } catch (err) {
    console.warn('Could not focus iframe', err);
  }
});
document.body.appendChild(iframe);

Let me know if that solves your problems.

weddingdj commented 7 years ago

@cvan Thank you for your code examples, I am sure it works that way. In my case I am dynamically loading a template partial from an REST endpoint depending on a certain type of content (photo sphere, video sphere, models, etc.) and I append the loaded template code (a-frame scene) to an element within the bootstrap modal dialog. I don't like the idea of loading a template which represents an html document (and I don't like iframes). But that's just my personal taste and my special use case. ;-)

I think for all other people wanting to load an a-frame scene into any sort of modal dialog window (which takes the focus) your solution should be fine. Although the canvas tabindex="1" solution is not too bad in my opinion (http://stackoverflow.com/questions/29992688/how-to-keep-the-focus-on-a-canvas-always).