home

Blog

Augmented Reality: create an interactive business card using Mind AR and A-Frame JS.

Blog, Pitang.

Pitang Labs

Augmented Reality: create an interactive business card using Mind AR and A-Frame JS.

Development

The Technical Break on Augmented Reality is now live! 

Below is the tutorial for creating the Business Card in text format:

What are Mind-AR and A-Frame?


A-Frame is a framework that allows the creation of virtual scenes using HTML, enabling the creation of augmented reality (AR) and virtual reality (VR) experiences, providing full access to the three.js API. 


MindAR is an Open Source JavaScript augmented reality (AR) library for the web that supports image tracking and face tracking, utilizing the GPU (via WebGL) and web workers to boost performance.

Initial Setup


For this project, we will need an index.html that will contain the application's main code for building and displaying the virtual scene. In addition, some graphic assets will be required, such as the target and the virtual elements that will be part of the AR experience. Thus, we structure the directory as follows:

Creating the Target


We will create the business card that will serve as the trigger for displaying our application's interactive content. When creating yours, it is important to take into account aspects that ensure as many feature points as possible, which allow greater accuracy during real-time target detection and tracking. MindAR provides some tips so you can design or choose images for this purpose. We provide the following canvas template that we will use: 

                                               

We will use the Image Targets Compiler to turn the Business Card into a target. This compilation will return a targets.mind file that stores the feature point data in a compact format. Save your business card image in PNG format and the compiled file in the targets folder.

                             

In index.html, we will need to import three libraries that will enable the application implementation using MindAR together with A-Frame. To import via CDN, add to the head of your index.html: mindar-image.prod.js, mindar-image-aframe.prod.js, and aframe.min.js. So the initial code might look like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="<https://cdn.jsdelivr.net/gh/hiukim/mind-ar-js@1.1.4/dist/mindar-image.prod.js>"></script>
    <script src="<https://aframe.io/releases/1.2.0/aframe.min.js>"></script>
    <script src="<https://cdn.jsdelivr.net/gh/hiukim/mind-ar-js@1.1.4/dist/mindar-image-aframe.prod.js>"></script>

    <title>My Business Card - Jane Doe</title>
  </head>

  <body>
  </body>
</html>

Creating the Scene


We will begin by creating the scene, the a-scene, which is the global object of our application. In it, we will specify some properties according to the MindAR documentation and also add all the elements that will compose the scenario.

<a-scene
  mindar-image="imageTargetSrc: ./assets/target/target.mind;"
  color-space="sRGB"
  renderer="colorManagement: true, physicallyCorrectLights"
  vr-mode-ui="enabled: false"
  device-orientation-permission-ui="enabled: false">
</a-scene>     

 

a-scene properties:

  • mindar-image: to specify the path to the compiled target. 

  • color-space and renderer: these are two optional properties that ensure higher quality when rendering graphic elements.

  • vr-mode-ui: will be included to disable the button that allows access to the virtual reality experience.

  • device-orientation-permission-ui: will be disabled to avoid displaying dialogs requesting access to certain device orientation events.

Adding Elements to the Scene


Inside a-scene, we will import our assets using the a-assets tag. In it, we can place images, 3D objects, audio, and videos. For each imported element, we must specify a unique ID, which is how this element will be referenced later, and the src attribute that will contain the path to the item, as shown in the examples below.  

<a-assets>
  <img id="imagem" src="./imagem.png"/>
  <a-asset-item id="object3D" src="./objeto3D.glft"></a-asset-item>
  <audio id="audio" src="./audio.mp3"></audio>
  <video id="video" src="./video.mp4"></video>
</a-assets>  
    

Since we will be inserting images into our application, we will need the img tag. Let's add our first content, which is the image of the business card you created.  

<a-assets>
  <img id="businessCard" src="./assets/target/businessCard.png"/>
</a-assets>  
    

 

Now, let's insert the camera using the a-camera tag. In it, we will add the following initial parameters: 

  • position: to set the positioning of this entity considering the x, y, and z coordinates, respectively.  

  • look-controls: to disable the option of rotating entities using the mouse and touch. 

 

<a-camera
  position="0 0 0"
  look-controls="enabled: false"
>

</a-camera>  

 

In order to display our first content, we need the a-entity tag, which will enable the insertion of components that provide appearance, behavior, and functionalities.


As a tag attribute, we need to specify the path to the target that needs to be detected and tracked in real time. This is done through the mindar-image-target="targetIndex: 0" property. The targetIndex: 0 indicates the index of the compiled image. 

Note: It is possible to compile more than one image together. In this case, the targets will be referenced considering the order in which the images were placed in the image targets compiler.

<a-entity
  mindar-image-target="targetIndex: 0"
>

</a-entity>

 

Inside a-entity, we place the content we want to display. Since it is just an image, using the a-image tag, we specify the following attributes:

  • id: unique element identification

  • position: considering the x, y, and z coordinates, we can position the image at the center with the values 0, 0, 0.

  • height: height of the image

  • width: width of the image. Whenever we set 1, we are indicating that it should match the target's width in the physical environment.

  • src: identifier of the item that was loaded in the a-assets tag.

 

<a-entity mindar-image-target="targetIndex: 0">
  <a-image
id="card"
      position="0 0 0"
      height="0.552"
      width="1"
      src="#businessCard"
  ></a-image>
</a-entity>

 

After all these procedures, your index.html might look like the code below. 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Libraries that need to be imported to use MindAR with A-Frame -->
    <script src="https://cdn.jsdelivr.net/gh/hiukim/mind-ar-js@1.1.4/dist/mindar-image.prod.js"></script>
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/hiukim/mind-ar-js@1.1.4/dist/mindar-image-aframe.prod.js"></script>

    <title>My Business Card - Jane Doe</title>
  </head>

  <body>

    <a-scene
      mindar-image="imageTargetSrc: ./assets/target/target.mind;"
      color-space="sRGB"
      renderer="colorManagement: true, physicallyCorrectLights"
      vr-mode-ui="enabled: false"
      device-orientation-permission-ui="enabled: false"
    >
      <a-assets>
        <img id="businessCard" src="./assets/target/businessCard.png" />
      </a-assets>
     
      <a-camera position="0 0 0" look-controls="enabled: false" ></a-camera>

      <a-entity mindar-image-target="targetIndex: 0">
           
<a-image id="card" position="0 0 0" height="0.552" width="1" src="#businessCard"></a-image>
      </a-entity>
    </a-scene>
  </body>
</html>

 

Let's run it using Live Server in your VS Code.  After installing the extension, right-click on the index.html file, and select the option Open with Live Server, and the web page will be launched.             

Adding Interactions


We can start placing the rest of the items in a-assets. To do this, download these icons and put them in the icons folder, and insert a photo of yourself, preferably with dimensions 980 x 980, in the profile folder. You can use our canvas template to place the image according to the design proposed for this application.

<a-assets>

   <img id="perfil" src="./assets/images/profile/perfil.png" />
  <img id="businessCard" src="./assets/target/businessCard.png" />

  <img id="icon_github" src="./assets/images/icons/icon_github.png" />
  <img id="icon_linkedin" src="./assets/images/icons/icon_linkedin.png" />
  <img id="icon_twitter" src="./assets/images/icons/icon_twitter.png" />
  <img id="icon_web" src="./assets/images/icons/icon_web.png" />
  <img id="icon_email" src="./assets/images/icons/icon_email.png" />
</a-assets>

 

After importing the elements into a-assets, we will organize our code better by grouping the business card image and the photo inside an a-entity. In the code, we adjust the positioning, height, width, and scale of the images to match the created design.

<a-entity mindar-image-target="targetIndex: 0">
<a-entity>
        <a-image
            id="card"
            position="-0.0 0.00427 -0.07243"
            height="0.552"
            width="1"
            src="#businessCard"></a-image>
        <a-image
            id="foto-perfil"
            position="0.850 0.0 -0.04426"
            height="0.15"
            width="0.15"
            src="#perfil" 

            scale="4.2 4.2 4.2"
            ></a-image>
        </a-entity>
</a-entity>

 

Tip: If you want to visually adjust some parameters, you can use the A-Frame inspector tool using the shortcut <ctrl> + <alt> + i.

                               


Next, let's insert an a-entity containing five a-image tags referring to the buttons for websites and social networks. Experimentally, we adjusted the positions of all the buttons and of the entity that groups them together, as shown in the code below. 

<a-entity position="0.065 -0.002 -0.004">
          <a-image
            id="github-button"
            position="-0.52361 -0.4575 -0.04426"
            height="0.15"
            width="0.15"
            src="#icon_github"
            animation="property: scale; to: 1.1 1.1 1.1; easing: easeInOutQuad; loop: true; dir: alternate"
          >
          </a-image>

          <a-image
            id="linkedin-button"
            position="-0.30772 -0.4575 -0.04426"
            height="0.15"
            width="0.15"
            src="#icon_linkedin"
            animation="property: scale; to: 1.1 1.1 1.1; easing: easeInOutQuad; loop: true; dir: alternate"
          >
          </a-image>
          <a-image
            id="twitter-button"
            position="-0.07819 -0.4575 -0.04426"
            height="0.15"
            width="0.15"
            src="#icon_twitter"
            animation="property: scale; to: 1.1 1.1 1.1; easing: easeInOutQuad; loop: true; dir: alternate"
          >
          </a-image>
          <a-image
            id="email-button"
            position="0.16583 -0.4575 -0.04426"
            height="0.15"
            width="0.15"
            src="#icon_email"
            animation="property: scale; to: 1.1 1.1 1.1; easing: easeInOutQuad; loop: true; dir: alternate"
          >
          </a-image>
          <a-image
            id="web-button"
            position="0.40564 -0.4575 -0.04426"
            height="0.15"
            width="0.15"
            src="#icon_web"
            animation="property: scale; to: 1.1 1.1 1.1; easing: easeInOutQuad; loop: true; dir: alternate"
          >
          </a-image>
        </a-entity>

 

Optionally, we added an animation via the animation property. The animation occurs by scaling up the size of the icons in an alternating direction. For more information and customizations, refer to the official documentation of A-Frame. 

The scale increases and decreases slightly, but to clearly illustrate how the animation occurs, we adjusted one of the icons to a larger scale as shown in the GIF below.

                                        Figure 5: Animation on the buttons


 

After adding the buttons to the scene, we need to allow interactions through clicking. To do this, we must insert the cursor and raycaster properties into the a-camera tag. 

<a-camera
  position="0 0 0"
  look-controls="enabled: false"
  cursor="fuse: false; rayOrigin: mouse;"
  raycaster=" far: 10000; objects: .clickable;">

</a-camera>  

 

In raycaster, we specify that all objects with the .clickable class will be clickable. Therefore, for all icons, we must add the attribute class=”clickable”.


To enable interactivity, the raycaster casts a line from the location where the user clicks into the scene. Thus, if any interactive object—that is, one with the .clickable class—lies in the path of the line, the click event will be triggered. 


Now we need to create the script that adds the behaviors resulting from clicking the buttons. To do this, let's import the index.js file into the head of index.html as follows:

<script defer src="./assets/scripts/index.js"></script>

 

The defer attribute will prevent the script from executing until the HTML page is ready.


In the JavaScript file, we need to "listen" for the click events for each button. To select the elements, we can use the document.querySelector() method, specifying the id corresponding to each object, according to the code below. 

const btnGithub = document.querySelector("#github-button");
const btnLinkedin = document.querySelector("#linkedin-button");
const btnTwitter = document.querySelector("#twitter-button");
const btnEmail = document.querySelector("#email-button");
const btnWebsite = document.querySelector("#web-button");

 

After selecting the elements, we can attach the click events using the addEventListener() method. In the method, we need to implement the function that will execute when this event is fired. In our application, each button will open a specific web page in a new window or tab. Set the URL for your accounts and web pages.

btnGithub.addEventListener('click', () => {
    window.open('https://github.com/pitanglabs', '_blank');
});

btnLinkedin.addEventListener('click', () => {
    window.open('https://linkedin.com/in/', '_blank');
});

btnTwitter.addEventListener('click', () => {
    window.open('https://twitter.com/', '_blank');
});


btnEmail.addEventListener('click', () => {
    window.open('mailto:labs-l@pitang.com');
});

btnWebsite.addEventListener('click', () => {
    window.open('https://www.pitang.com/', '_blank');
});

Custom Scanner


To customize our application, we can optionally insert a custom scanner following the example provided by the MindAR library.

                              

 

To do so, place the code below in the body of your index.html and, in the img tag, specify the path to the business card image.

<div id="example-scanning-overlay" class="hidden">
    <div class="inner">
        <img src="./assets/target/businessCard.png" />
        <div class="scanline"></div>
    </div>
</div>

 

Next, import the index.css file into the head as follows:

<link rel="stylesheet" href="assets/styles/index.css" />

 

Now we need to override the default MindAR scanner using the uiScanning parameter in the mindar-image property of a-scene, as follows: 

<a-scene
  mindar-image="imageTargetSrc: ./assets/target/target.mind; uiScanning: #example-scanning-overlay"
  color-space="sRGB"
  renderer="colorManagement: true, physicallyCorrectLights"
  vr-mode-ui="enabled: false"
  device-orientation-permission-ui="enabled: false">


...

 

 In your index.css, add the code below, in accordance with this demo.

body {
    margin: 0;
}

.container {
    /* fill the entire screen */
    overflow: hidden;
    position: absolute;
    width: 100%;
    height: 100%;
}

/* MindAR scanning start */
#example-scanning-overlay {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: transparent;
    z-index: 2;
}

@media (min-aspect-ratio: 1/1) {
    #example-scanning-overlay .inner {
        width: 50vh;
        height: 50vh;
    }
}

@media (max-aspect-ratio: 1/1) {
    #example-scanning-overlay .inner {
        width: 80vw;
        height: 80vw;
    }
}

#example-scanning-overlay .inner {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;

    background: linear-gradient(to right, white 10px, transparent 10px) 0 0,
        linear-gradient(to right, white 10px, transparent 10px) 0 100%,
        linear-gradient(to left, white 10px, transparent 10px) 100% 0,
        linear-gradient(to left, white 10px, transparent 10px) 100% 100%,
        linear-gradient(to bottom, white 10px, transparent 10px) 0 0,
        linear-gradient(to bottom, white 10px, transparent 10px) 100% 0,
        linear-gradient(to top, white 10px, transparent 10px) 0 100%,
        linear-gradient(to top, white 10px, transparent 10px) 100% 100%;
    background-repeat: no-repeat;
    background-size: 40px 40px;
}

#example-scanning-overlay.hidden {
    display: none;
}

#example-scanning-overlay img {
    opacity: 0.6;
    width: 90%;
    align-self: center;
}

#example-scanning-overlay .inner .scanline {
    position: absolute;
    width: 100%;
    height: 10px;
    background: white;
    animation: move 2s linear infinite;
}

@keyframes move {

    0%,
    100% {
        top: 0%;
    }

    50% {
        top: calc(100% - 10px);
    }
}

/* MindAR scanning end */

Tracking Configuration


If we run the application, we can observe element jitter at times. This is considered a common issue in AR applications, due to factors such as noise coming from the camera. 

 

To solve this problem, we can adjust the filterMinCF and filterBeta parameters. Technical information can be found in the official documentation of MindAR. But, generally speaking, by decreasing the filterMinCF value we reduce jitter, and by increasing the filterBeta value, we reduce latency/delay. 


The default values for filterMinCF and filterBeta are 0.001 and 1000. Experimentally, we modified these values in the mindar-image attribute to reduce jitter without significantly increasing delay.

<a-scene
      mindar-image="imageTargetSrc: ./assets/target/target.mind; filterMinCF:0.001; filterBeta: 0.09; uiScanning: #example-scanning-overlay"
      color-space="sRGB"
      renderer="colorManagement: true, physicallyCorrectLights"
      vr-mode-ui="enabled: false"
      device-orientation-permission-ui="enabled: false">

...

 

We can observe through the following GIF that jitter was substantially reduced, ensuring a better user experience.

                     


 

In the end, your complete project might look similar to the code found in this repository. 

Deployment


To complete our tutorial, we will deploy to the Vercel platform using the following steps:

  1. Create a repository on your GitHub to place your code;

  2. Sign up on Vercel, preferably using your GitHub account;

  3. Create a new project on Vercel;  

4. Import your GitHub account 

5. You can grant access to all repositories or only to the one you just created;  

6. After installing and selecting the repository, click the Import button;

7. If you want, change the project name, then select the deploy option and wait.

8. A new page will open, indicating that the deployment was successful.

9. Click on the blank window to open the site or press the Go to Dashboard button to view status, domains, etc.

10. On your business card, change jane.com/card to the URL generated by Vercel. Then, repeat the target compilation procedure with the new image and replace it in your project.

11. Finally, push to GitHub and wait, as Vercel will update the page with the new changes.


 

Recife

Cais do Apolo Street, 222 - 13th floor

ZIP: 50030-220 - Old Recife Recife/PE

Tel: +55(81) 3134 5200

Fax: +55(81) 3134 5374

São Paulo

Joaquim Floriano Street, 466 - Suite 1001 - 0

Brascan Century Building

Corporate, Itaim Bibi

São Paulo/SP

Recife

Cais do Apolo Street, 222 - 13th floor

ZIP: 50030-220 - Old Recife Recife/PE

Tel: +55(81) 3134 5200

Fax: +55(81) 3134 5374

São Paulo

Joaquim Floriano Street, 466 - Suite 1001 - 0

Brascan Century Building

Corporate, Itaim Bibi

São Paulo/SP

Recife

Cais do Apolo Street, 222 - 13th floor

ZIP: 50030-220 - Old Recife Recife/PE

Tel: +55(81) 3134 5200

Fax: +55(81) 3134 5374

São Paulo

Joaquim Floriano Street, 466 - Suite 1001 - 0

Brascan Century Building

Corporate, Itaim Bibi

São Paulo/SP