Touch/Click Function Not Working on Mobile Devices with 8th Wall

Hello 8th Wall community,

I’m encountering an issue with touch/click functionality in my 8th Wall project when deployed on mobile devices. I’m hoping someone can provide some insight or assistance.

The Issue:

  • Touch functions are not working on mobile devices for custom click-to-open interactions.
  • This is happening specifically for elements that should trigger a URL open or visibility change of other elements.

What’s Working:

  • The project runs fine in the simulator, and all click functions work as expected.
  • XRExtras touch gestures (like two-finger rotate and pinch-to-zoom) are functioning correctly on mobile devices.

What’s Not Working:

  • Simple click-to-open or click-to-change-visibility functions are not triggering on mobile devices.
  • This affects elements like:
    • Clickable 3D models that should open URLs
    • Pins that should reveal 360-degree spheres

What I’ve Tried:

  • Added both ‘click’ and ‘touchend’ event listeners
  • Used e.preventDefault() and e.stopPropagation() in event handlers
  • Disabled default touch behavior on the entire scene
  • Verified that 3D assets are loading correctly on mobile

Environment:

  • 8th Wall Web (latest version)
  • Tested on multiple iOS and Android devices
  • Using A-Frame for the 3D scene

Questions:

  1. Is there a known issue with touch events on 3D elements in 8th Wall projects?
  2. Are there any specific considerations for handling touch events in 8th Wall that differ from standard web development?
  3. Could there be a conflict between XRExtras touch handlers and custom touch events?
  4. Are there any recommended best practices for implementing clickable 3D elements in 8th Wall projects?

Any help or direction would be greatly appreciated. Thank you in advance for your time and expertise!

Hi!

I really appreciate the incredibly well-thought-out post!

I would recommend using a THREE raycast, similar to how we implement it in the World Effects sample project. We’re also working on simplifying and streamlining this functionality in the future.

1 Like

What does your camera look like?

Sometimes I forget to add:

 raycaster="objects: .cantap"
    cursor="
      fuse: false;
      rayOrigin: mouse;">

Onto the camera as well as:

class="cantap"

Onto an entity I want to be able to tap. So the setup would look something like this

 <a-camera
    id="camera"
    position="0 8 8"
    raycaster="objects: .cantap"
    cursor="
      fuse: false;
      rayOrigin: mouse;"></a-camera>

<a-plane
    id="ground" 
    rotation="-90 0 0"
    position="0 0 0"
    width="250" 
    height="250" 
    shadow="receive: true; cast: false"
    class="cantap"
    xrextras-hider-material></a-plane>

the javascript would look something like this:

document.getElementById('ground').addEventListener('click', () = > {
 window.open('http://www.google.com')
})
2 Likes

Thank you for your response. I appreciate your help, but I’m still encountering issues. Let me provide some more details:

  1. My app.js does look similar to what you wrote, but I’m using querySelector instead of getElementById. I don’t think this should make a difference, but please let me know if it could be causing issues.

  2. The functionality I’m aiming for is quite complex:

    • There’s an Earth model which, when clicked, should fade out.
    • Then, a country model should fade in.
    • The country model has multiple interactive elements:
      • Hotel 3D models that, when clicked, should redirect to specific URLs.
      • Pins that, when clicked, should display photospheres.
  3. I’m also using XRExtras functions for two-finger rotation and pinch-to-zoom, but not hold-and-drag. These gesture functions seem to be working correctly.

  4. My interactive elements have both “cantap” and “clickable” classes. I’ve tried using each class individually, but the result remains the same.

  5. Testing results:

    • It works fine in the simulator.
    • On my iPhone 13 Pro, the Earth model appears, but nothing happens when I try to interact with it.
    • On a friend’s iPhone 15, it worked twice, but after refreshing, it stopped responding.
    • It’s not working at all on Android devices we’ve tested.
  6. Specific behavior:

    • On the iPhone 13 Pro, the scene loads, and I can see the Earth model, but clicking doesn’t trigger any actions.
    • On the iPhone 15, we initially got two successful interactions before it stopped working.

I’m puzzled because it works in the simulator but fails on actual devices. Do you have any ideas about what could be causing this discrepancy between the simulator and real devices? Could it be related to how touch events are handled on mobile browsers versus the simulator?

Also, is there a way to debug or log events on mobile devices to see if the touch/click events are being registered at all? I’m particularly interested in understanding why the XRExtras gesture functions work, but my custom click/tap functions don’t.

Thank you again for your help. I’m really hoping to get this working consistently across devices.

I’ve been working on a project where I’m using THREE Raycast, but I haven’t been able to get the desired results on my device. It works perfectly in the simulator, but once I test it on a physical device, it doesn’t behave as expected.

I’ve also tried cloning the world effect project and made some modifications, but the issue persists. Despite my efforts, I’m not able to solve it. Below is the code I’ve been working with. Would it be possible for you to recreate this and test it on your device? It would be a huge help if you could let me know if it works for you or if you have any suggestions to fix the issue.

Thanks so much in advance for your assistance!

camera setup

a-camera position=“0 8 8”
raycaster=“objects: .clickable, .cantap;
interval: 100; far: 20”
cursor=“fuse: false;
rayOrigin: mouse”>

entity code

    <a-entity id="earth" 
    gltf-model="#earthModel" 
    scale="2 2 2" 
    position="0 0 0" 
    class="cantap" 
    visible="true" 
    shadow="receive: true; cast: false"
    xrextras-two-finger-rotate 
    xrextras-pinch-scale></a-entity>

    <!-- Country Group (Initially Hidden) -->
    <a-entity id="country" 
    scale="0 0 0" 
    position="0 0 0" 
    visible="false" 
    xrextras-two-finger-rotate 
    xrextras-pinch-scale>
        <!-- Country Base -->
        <a-entity gltf-model="#countryModel"></a-entity>
        <!-- Hotels -->
        <a-entity id="hotel1" 
        gltf-model="#hotelModel" 
        position="-1 0 0" 
        class="cantap hotel"></a-entity>

        <a-entity id="hotel2" 
        gltf-model="#hotelModel" 
        position="1 0 0" 
        class="cantap hotel"></a-entity>

        <a-entity id="hotel3" 
        gltf-model="#hotelModel" 
        position="2 0 0" 
        class="cantap hotel"></a-entity>

        <a-entity id="hotel4" 
        gltf-model="#hotelModel" 
        position="3 0 0" 
        class="cantap hotel"></a-entity>

        <!-- Pins -->
        <a-entity id="pin1" 
        gltf-model="#pinModel" 
        position="-1 1 0" 
        class="cantap pin"></a-entity>

        <a-entity id="pin2" 
        gltf-model="#pinModel" 
        position="1 1 0" 
        class="cantap pin"></a-entity>

        <a-entity id="pin3" 
        gltf-model="#pinModel" 
        position="2 1 0" 
        class="cantap pin"></a-entity>

        <a-entity id="pin4" 
        gltf-model="#pinModel" 
        position="3 1 0" 
        class="cantap pin"></a-entity>

    </a-entity>

    <!-- 360 Photosphere (Initially Hidden) -->
    <a-sphere id="sphere" 
    radius="200" 
    position="0 0 0" 
    visible="false" 
    shader="flat"
    side="double"></a-sphere>

and this is my whole js setup

window.onload = function handleWindowLoad() {
let currentState = ‘earth’

const earth = document.querySelector(‘#earth’)
const country = document.querySelector(‘#country’)
const sphere = document.querySelector(‘#sphere’)
const backButton = document.querySelector(‘#backButton’)

const hotelURLs = [
https://rb.gy/iq93nb’,
https://rb.gy/6qx7ta’,
https://rb.gy/x5x736’,
https://rb.gy/ion32w’,
]

const pinTextures = [
#360image1’,
#360image2’,
#360image3’,
]

// Fade out Earth and show Country
const handleEarthClick = () => {
if (currentState === ‘earth’) {
earth.setAttribute(‘visible’, ‘false’)
country.setAttribute(‘visible’, ‘true’)
country.setAttribute(‘scale’, ‘2 2 2’)
currentState = ‘country’
}
}

earth.addEventListener(‘click’, handleEarthClick)
earth.addEventListener(‘touchstart’, handleEarthClick) // Handle touch

// Click on Hotels (Redirect to URLs)
document.querySelectorAll(‘.hotel’).forEach((hotel, i) => {
const handleHotelClick = () => {
window.location.href = hotelURLs[i]
}
hotel.addEventListener(‘click’, handleHotelClick)
hotel.addEventListener(‘touchstart’, handleHotelClick) // Handle touch
})

// Click on Pins (Show 360 Sphere)
document.querySelectorAll(‘.pin’).forEach((pin, i) => {
const handlePinClick = () => {
country.setAttribute(‘visible’, ‘false’)
sphere.setAttribute(‘visible’, ‘true’)
sphere.setAttribute(‘src’, pinTextures[i])
currentState = ‘sphere’
}
pin.addEventListener(‘click’, handlePinClick)
pin.addEventListener(‘touchstart’, handlePinClick) // Handle touch
})

// Back Button Logic
const handleBackButton = () => {
if (currentState === ‘sphere’) {
sphere.setAttribute(‘visible’, ‘false’)
country.setAttribute(‘visible’, ‘true’)
currentState = ‘country’
} else if (currentState === ‘country’) {
country.setAttribute(‘visible’, ‘false’)
earth.setAttribute(‘visible’, ‘true’)
currentState = ‘earth’
}
}

backButton.addEventListener(‘click’, handleBackButton)
backButton.addEventListener(‘touchstart’, handleBackButton) // Handle touch
}