How to position items relative to image target?

I have elements that need to be precisely positioned relative to an image target.
Is there any way to do this via some kind of GUI?
Currently it’s a lot of trial and error tweaking position values ad nauseum.

Hi @Ze_ev_Gilad at this time the best options is to either use the jellyfish photos for reference and or bring your image target into a 3D modeling software such as Blender and position your objects according to the image target. I’ve attached a photo of how I set up my image targe in blender to better align objects. Then second photo showing some animations.

Whenever the events xrimagefound or xrimageupdated are fired, the detail will contain scaledWidth and scaledHeight, which are scaling information so you know if it’s a landscape or a portrait image. The bigger of these two will always be 1 (meter).

However, the smaller dimension tells you how many meters it takes to fill up the fill width/height of the image target. So if you got a scaledHeight of 0.74 (meters), that tells you that if you set the height to 0.74, your video or image element will take up the height of the entire image target.

But the problem is that 8th Wall always crops the image targets while scaling down the larger dimension (width, in the example) to 1, which will scale down the entire visible element.

So the solution is to calculate the ratio of your image or video element after the metadata has loaded, multiply that with the minimum dimension (e.g. scaledHeight 0.74), and use the minimum dimension and the modified ratio to set the scale on the child element of your image target element.

NOTE: It’s really important that the image target is the exact same size as your element and that you don’t adjust the cropping at all. Simply upload your image and follow all the required steps for adding a new image target.

Example code

I’m writing this blindly so … take it for what it is.

<!-- HTML -->
 <a-entity example-named-image-target="name: video-target">
      example-play-video="video: #jelly-video; thumb: #jelly-thumb;"
      geometry="primitive: plane"

Original source: Image Targets Flyer example (copied and modified)

/** Javascript: registering components in index.js **/

import { exampleNamedImageTarget } from './example-named-image-target.js';
import { examplePlayVideo } from './example-play-video.js';

AFRAME.registerComponent('example-named-image-target', exampleNamedImageTarget);
AFRAME.registerComponent('example-play-video', examplePlayVideo);
/** Javascript component: example-named-image-target.js **/

const exampleNamedImageTarget = {
  schema: {
    name: {type: 'string'},
  init() {
    const {object3D} = this.el
    const {name} =

    const setupScalingFactor = ({detail}) => {
      if (name === {
        this.el.firstElementChild.components.minDimension = Math.min(detail.scaledHeight, detail.scaledWidth)

    // Basically the same as the original code
    const updateImage = ({detail}) => {
      if (name === {
        object3D.scale.set(detail.scale, detail.scale, detail.scale)
        object3D.visible = true

    const hideImage = ({detail}) => {
      if (name === {
        object3D.visible = false

    this.el.sceneEl.addEventListener('xrimagefound', setupScalingFactor)
    this.el.sceneEl.addEventListener('xrimageupdated', updateImage)
    this.el.sceneEl.addEventListener('xrimagelost', hideImage)

export { exampleNamedImageTarget }

Original source: xrextras-named-image-target (copied, simplified, and modified)

/** Javascript component: example-play-video.js **/

const examplePlayVideo = {
  schema: {
    video: {type: 'string'},
    thumb: {type: 'string'},
  init() {
    const v = document.querySelector(
    const p = && document.querySelector(
    const {el} = this

    el.setAttribute('material', 'src', p || v)
    el.setAttribute('class', 'cantap')

    * 'scaledWidth' and 'scaledHeight' are in meters
    const setScale(scaledWidth, scaledHeight) {
        this.el.components.minDimension * scaledWidth,
        this.el.components.minDimension * scaledHeight

    // Parameters are either 1 or bigger than 1.
      (v.videoWidth > v.videoHeight) ? v.videoWidth / v.videoHeight : 1,
      (v.videoHeight > v.videoWidth) ? v.videoHeight / v.videoWidth : 1

    el.addEventListener('click', () => {
      if (!playing) {
        el.setAttribute('material', 'src', v)
      } else {
        el.setAttribute('material', 'src', p || v)

export { examplePlayVideo }

Original source: xrextras-play-video (copied, modified, and extended)

Note that if the thumbnail doesn’t have the same dimensions as the element itself, then you need to take that into consideration by setting the scale based on the image dimensions and then setting the scale again based on the video dimensions when the video is about to be played.


Thank you! Very useful info.


Discovered the other week that the event xrimagescanning is sent out whenever image targets are set, and the detail will contain the following properties for all image targets:


So, it’s not necessary to wait for the metadata from the image or video to set the scale, unless you create example-name-image-targets dynamically (like I do). You can instead calculate the scale directly in the example-named-image-target component, using originalHeight and originalWidth together with Math.min(geometry.scaledWidth, geometry.scaledHeight).

Hi Rickard ,

Did you find a way to disable the " xr image scanning " function …
I am seeing a lot of positional jitter , else floating point errors when the marker position updates. Then we have a tall 3d object so its made worse at the extents. Ideally once the " xr image scanning " is done , we should be able to turn it off …

Thanks !