In most browsers, tucked away behind a feature flag, is an exciting new API for creating VR experiences on the web. However, creating content for this new medium could potentially see you at the bottom of a steep learning curve — but does it have to?

There are a couple of options available for creating VR content on the web. The first requires a working knowledge of both the WebVR and WebGL APIs. The WebVR API is used to communicate with the VR hardware, querying for device capabilities, the users pose and exposing information such as projection matrices, which are required to render a frame to a WebGL context. Once you've probed the hardware, you'll need to handle all the actual scene rendering yourself through the WebGL API.

If that sounds too much like hard work, there are frameworks — such as a-frame — that abstract most of this complexity away. These solutions allow developers to create VR content with more familiar techniques, using markup and JavaScript.

The issue I have with these options is the need to author completely separate content to create a VR experience. Shouldn't we be supporting VR in a progressive manner, enhancing our content to render on these devices using existing technology and techniques?

CSS and VR

CSS seems like a natural fit for VR. It provides much of what we need to create an interactive 3D experience. Content can be positioned in 3D space using transforms, media queries allow styles to be conditionally applied based on device configuration and pseudo-classes provide the means to style elements based on user interaction.

Unfortunately, it's not possible to take a DOM tree and render it directly to a VR headset using any available APIs. The WebVR API requires a WebGL context and, as I mentioned earlier, the developer is responsible for drawing to it using the WebGL API.

I'd like to see browsers handling the VR hardware communication and rendering the DOM internally, reducing complexity and allowing developers to create virtual experiences for their content using @media rules and transforms — something along the lines of:

@media vr {

  /* Position the element in the distance */
  .element {
    transform: translate3d(-50%, -50%, -500px);
    transition: transform .25s;
  }

  /* Bring the element closer when we look at it */
  .element:hover {
    transform: translate3d(-50%, -50%, -100px);
  }

}

Although rendering a DOM tree to a VR device isn't possible, I wanted to use JavaScript to simulate browser support so I could explore the feasibility of creating a VR experience using HTML and CSS.


Simulating VR

We've already established that CSS can render a 3D scene using transforms and respond to user interactions with psuedo-classes. The things CSS can't handle can either be implemented using existing APIs, or they can be emulated. For example, head tracking can be achieved by listening for the deviceorientation event and stereo projection can be simulated by syncing two DOM trees with a MutationObserver — it's even possible to implement a camera with the right DOM structure and some CSS.

Here's a full list of things that need to be implemented to create a simple VR experience:

  • cssvr media type for VR specific styles and apply them when needed.
  • A camera so the user can look around in the virtual space.
  • Head-tracking to sync the camera to the users physical pose.
  • Allow :hover to work in 3D space so the user can interact with content by looking at it.
  • Dispatching click events when the user hovers over an element for a predetermined time.
  • A stereoscopic viewport so users can experience the VR prototype in 3D.

I've published the CSSVR project source to GitHub. If you're interested, you can find detailed information about the implementations listed above.

Screenshot of the CSSVR simulator running the Skybox demo

Before we go any further, I want to emphasise that this is a proof-of-concept — and a relatively crude one at that! It's only compatible with VR headsets that hold a mobile device, like Google Cardboard, and lacks some of the features expected from a full VR experience, such as lens distortion. Also, some of the implementations in this prototype are only just good enough to test if the concept works.

Try the CSSVR demos CSSVR on GitHub

The boilerplate

Building a VR ready page is really simple; define some VR-specific styles and drop in the CSSVR simulator script:

<head>
  <style>
    @media screen {
      /* screen styles go here */
    }
    @media cssvr {
       /* vr styles go here */
    }
  </style>
</head>

<body>
  <div id="root">
    <!-- site content goes here -->
  </div>
  <script src="cssvr.js"></script>
</body>

If this page were loaded in a browser, any styles declared in the cssvr media block would ignored until the simulator is started:

CSSVR.start({
  root: document.getElementById('root'),
  fov: 70,
  projection: 'stereo',
  stereoPitch: 15
});

Note: I recommend the use of something like Rich Tibbett's no-sleep to prevent the screen from sleeping while the device is docked in a headset.

Once the simulator is up and running, the browser will ignore any styles that target screen media and only apply those in a cssvr blocks. You can read more about the start method and configuring the simulator in the project readme. When the simulator is stopped, the page will return to it's original state:

CSSVR.stop();

That's the basics covered. Next step is to populate the page with content and style the VR version of the content using a @media block. Please see the following demo for a bare-bones example:

Try the "hello world" demo

Going Interactive

Building a VR experience is pretty pointless if users can't interact with the content you've created; once a VR headset is strapped on, traditional input methods (mice and fingers) are all but useless.

CSSVR attempts to implement user interaction by allowing users to "look at" content. The simulator calculates the vertex data for each element and then casts a ray from the centre of the viewport directly into the scene. Any elements that intersect with the ray are marked as a potential selection. If multiple elements are in the line of sight, the element closest to the user is used as the final selection. To assist with selecting elements, a cursor is rendered over the viewport.

Screenshot of the CSSVR simulator running the interaction demo

CSSVR emulates the CSS :hover pseudo-class so — just as you would when the mouse pointer is hovering over an element — you can style content differently when the user is looking at it:

@media cssvr {
  .my-element:hover {
    background: green;
  }
}

It's also possible to trigger click events by looking at elements. If the user looks at the same element for long enough, the simulator will indicate that an element is "clickable" by incrementally colouring the cursor segments. If the user remains on the element until all segments are coloured, a click event is dispatched. For the CSSVR UI to work, the vr-action attribute must be added to any elements that have click event handlers. This is because it's impossible to determine if an element has any active event listeners.

<div id="root">
  <button type="button" onclick="doSomething()" vr-action>Click me!</button>
</div>

<script>
  function doSomething() {
    this.innerText = 'Clicked';
  }
</script>

You can see both these examples in action in the following demo:

Try the interactive demo

What now?

If you'd like to try creating your own CSS based VR experiences, you can grab the code for this project from GitHub.

Personal Achievements

  • 2015 JS1k – winner
  • 2014 Net Awards: Demo of the year – winner
  • 2014 Net Awards: Developer of the year – longlist
  • 2013 Public speaking for the first time
  • 2011 .net Magazine innovation of the year – shortlist

Referenced in…

Smashing CSS, CSS3 for web designers, Programming 3D Applications with HTML5 and WebGL and more.

I am very proud to have had my work referenced in a number of publications, written by some very talented people.