UI and Custom Events

To make AR.js based Web App look better and add UI capabilities, it's possible to treat is as common website. Here you will learn how to use Raycaster, Custom Events and Interaction with overlayed DOM elements.

Handle clicks on AR content

It's now possible to use AR.js (marker based or image tracking) with a-frame latest versions (1.0.0 and above) in order to have touch gestures to zoom and rotate your content! Disclaimer: this will work for your entire a-scene, so it's not a real option if you have to handle different interactions for multiple markers. It will work like charm if you have one marker/image for scene.

Check Fabio Cortès great walkthrough in order to add this feature on your AR.js web app.

You can use this exact approach for Image Tracking a-nft and Marker Based a-entity elements. The clickhandler name can be customized, you can choose the one you like most, it's just a reference.

Keep in mind that this click/touch interaction is not handled by AR.js at all, it is all A-Frame based. Always look on the A-Frame documentation for more details.

Check out the tutorial

Interaction with Overlayed DOM content

You can add interations by adding DOM HTML elements on the body. For example, starting from this example:

<!DOCTYPE html>
<html>
  <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
  <!-- we import arjs version without NFT but with marker + location based support -->
  <script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
  <body style="margin : 0px; overflow: hidden;">
    <a-scene embedded arjs>
      <a-marker preset="hiro">
        <!-- we use cors proxy to avoid cross-origin problems -->

        <!--
          ⚠️⚠️⚠️
          https://arjs-cors-proxy.herokuapp.com/ is now offline, Heroku has dismissed all his free plans from November 2022.
          You need to host your own proxy and use it instead. The proxy is based on CORS Anywhere (see https://github.com/Rob--W/cors-anywhere).
          ⚠️⚠️⚠️
        -->
        <a-entity
          position="0 0 0"
          scale="0.05 0.05 0.05"
          gltf-model="https://arjs-cors-proxy.herokuapp.com/https://raw.githack.com/AR-js-org/AR.js/master/aframe/examples/image-tracking/nft/trex/scene.gltf"
        ></a-entity>
      </a-marker>
      <a-entity camera></a-entity>
    </a-scene>
  </body>
</html>

We can add on the body, outside the a-scene:

<div class="buttons">
  <button class="say-hi-button"></button>
</div>

Then, we need to add some CSS to absolute positioning the DIV and BUTTON, and also some scripting to listen to click events.

You can customize your a-scene or content, like 3D models, play video, and so on. See on A-Frame Docs on how to change entity properties and work with events: https://aframe.io/docs/1.0.0/introduction/javascript-events-dom-apis.html.

We will end up with the following code:

<!DOCTYPE html>
<html>
  <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
  <!-- we import arjs version without NFT but with marker + location based support -->
  <script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
  <script>
    window.onload = function () {
      document
        .querySelector(".say-hi-button")
        .addEventListener("click", function () {
          // here you can change also a-scene or a-entity properties, like
          // changing your 3D model source, size, position and so on
          // or you can just open links, trigger actions...
          alert("Hi there!");
        });
    };
  </script>
  <style>
    .buttons {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 5em;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 10;
    }

    .say-hi-button {
      padding: 0.25em;
      border-radius: 4px;
      border: none;
      background: white;
      color: black;
      width: 4em;
      height: 2em;
    }
  </style>
  <body style="margin : 0px; overflow: hidden;">
    <div class="buttons">
      <button class="say-hi-button">SAY HI!</button>
    </div>
    <a-scene embedded arjs>
      <a-marker preset="hiro">
        <a-entity
          position="0 0 0"
          scale="0.05 0.05 0.05"
          gltf-model="https://arjs-cors-proxy.herokuapp.com/https://raw.githack.com/AR-js-org/AR.js/master/aframe/examples/image-tracking/nft/trex/scene.gltf"
        ></a-entity>
      </a-marker>
      <a-entity camera></a-entity>
    </a-scene>
  </body>
</html>

Custom Events

AR.js dispatches several Custom Events.

Some of them are general, others are specific for AR Feature. Here's the full list.

Custom Event name Description Payload Source File Feature
arjs-video-loaded Fired when camera video stream has been appended to the DOM { detail: { component: <HTMLElement> }} threex-artoolkitsource.js all
camera-error Fired when camera video stream could not be retrieved { error: <Error> } threex-artoolkitsource.js all
camera-init Fired when camera video stream has been retrieved correctly { stream: <MediaStream> } threex-artoolkitsource.js all
markerFound Fired when a marker in Marker Based, or a picture in Image Tracking, has been found - component-anchor.js only Image Tracking and Marker Based
markerLost Fired when a marker in Marker Based, or a picture in Image Tracking, has been lost - component-anchor.js only Image Tracking and Marker Based
arjs-nft-loaded Fired when a nft marker is full loaded threex-armarkercontrols-nft-start.js only Image Tracking
gps-camera-update-positon Fired when gps-camera has updated its position { detail: { position: <GeolocationCoordinates>, origin: <GeolocationCoordinates> }} gps-camera.js only Location Based
gps-entity-place-update-positon Fired when gps-entity-place has updated its position { detail: { distance: <Number> }} gps-entity-place.js only classic and projected Location Based
gps-entity-place-added Fired when the gps-entity-place has been added { detail: { component: <HTMLElement> }} gps-entity-place.js only classic and projected Location Based
gps-camera-origin-coord-set Fired when the origin coordinates are set - gps-camera.js only classic and projected Location Based
gps-entity-place-loaded Fired when the gps-entity-place has been - see 'loaded' event of A-Frame entities { detail: { component: <HTMLElement> }} gps-entity-place.js only classic and projected Location Based

Internal Loading Events

⚡️ Both Image Tracking and Location Based automatically handle an internal event when

  • origin location has been set
  • Image Tracking (Image Descriptors) are fully loaded

And automatically remove from the DOM elements that match the .arjs-loader selector.

You can add any custom loader that will be remove in the above situations, just use the .arjs-loader class on it.

Trigger actions when image has been found

You can trigger any action you want when marker/image has been found. You can avoid linking a content to a marker/image and only trigger an action (like a redirect to an external website) when the anchor has been found by the camera.

<script src="https://cdn.jsdelivr.net/gh/aframevr/aframe@1c2407b26c61958baa93967b5412487cd94b290b/dist/aframe-master.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script>

<style>
  .arjs-loader {
    height: 100%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.8);
    z-index: 9999;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .arjs-loader div {
    text-align: center;
    font-size: 1.25em;
    color: white;
  }
</style>
<script>
  AFRAME.registerComponent('markerhandler', {
    init: function () {
      this.el.sceneEl.addEventListener('markerFound', () => {
        // redirect to custom URL
        window.location = 'https://github.com/AR-js-org/AR.js';
      });
  });
  },
</script>

<body style="margin : 0px; overflow: hidden;">
  <!-- minimal loader shown until image descriptors are loaded -->
  <div class="arjs-loader">
    <div>Loading, please wait...</div>
  </div>
  <a-scene
    vr-mode-ui="enabled: false;"
    renderer="logarithmicDepthBuffer: true;"
    embedded
    arjs="trackingMethod: best; sourceType: webcam;debugUIEnabled: false;"
  >
    <!-- we use cors proxy to avoid cross-origin problems -->
    <!-- we use the trex image shown on the homepage of the docs -->
    <a-nft
      markerhandler
      type="nft"
      url="https://arjs-cors-proxy.herokuapp.com/https://raw.githack.com/AR-js-org/AR.js/master/aframe/examples/image-tracking/nft/trex/trex-image/trex"
    >
    </a-nft>
    <a-entity camera></a-entity>
  </a-scene>
</body>

Trigger action when marker has been found

<script src="https://cdn.jsdelivr.net/gh/aframevr/aframe@1c2407b26c61958baa93967b5412487cd94b290b/dist/aframe-master.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script>

<script>
  AFRAME.registerComponent('markerhandler', {
    init: function () {
      this.el.sceneEl.addEventListener('markerFound', () => {
        // redirect to custom URL e.g. google.com
        window.location = 'https://www.google.com/';
      })
    }
  });
</script>

<body style="margin : 0px; overflow: hidden;">
  <a-scene
    vr-mode-ui="enabled: false;"
    renderer="logarithmicDepthBuffer: true;"
    embedded
    arjs="trackingMethod: best; sourceType: webcam; debugUIEnabled: false; detectionMode: mono_and_matrix; matrixCodeType: 3x3;">

    <a-marker markerhandler type='barcode' value='7'>
        <a-box position='0 0.5 0' color="yellow"></a-box>
    </a-marker>

    <a-entity camera></a-entity>
  </a-scene>
</body>

Get distance from marker

// import this on your HTML
window.addEventListener('load', () => {
    const camera = document.querySelector('[camera]');
    const marker = document.querySelector('a-marker');
    let check;

    marker.addEventListener('markerFound', () => {
        let cameraPosition = camera.object3D.position;
        let markerPosition = marker.object3D.position;
        let distance = cameraPosition.distanceTo(markerPosition)

        check = setInterval(() => {
            cameraPosition = camera.object3D.position;
            markerPosition = marker.object3D.position;
            distance = cameraPosition.distanceTo(markerPosition)

            // do what you want with the distance:
            console.log(distance);
        }, 100);
    });

    marker.addEventListener('markerLost', () => {
      clearInterval(check);
    })
})