[SOLVED] Beginner - Raycaster - DOM

raycaster

#1

Hi there,

This thread is a part of the same project as the previous question I’ve asked here : Beginner - PerspectiveCamera

Note that you don’t need to read the old post to understand that one.

So I am currently creating boxes at some locations. Thoses boxes can be clicked out using Raycaster. It pop out a window (a div, DOM) that take all the screen. But, if I click on the div, it also include the Raycaster values, causing more boxes to be clicked through the div.

To get ride of this, I’ve included the div (Yes, I actually do) in the clickable objects. As a result, when I click wherever on the div, boxes aren’t clicked, as long as the div is now included in intersects and as long as I am only acting on the first item clicked. I actually made what I want and it works, but I get a JavaScript error, so I think this isn’t the way I should do this.

Error :

Uncaught TypeError: a.raycast is not a function (three.min.js:295)
at xe (three.min.js:295)
at uf.intersectObjects (three.min.js:863)
at HTMLDocument.onDocumentMouseDown (scene.js:334)

Line 334 :

var intersects = raycaster.intersectObjects(objects);

if(intersects.length > 0) {
    if(intersects[0].c_type == "div") {
        [...]

Any idea how I could get ride off that error ?

EDIT : As you should have understand, I define any clickable element by a custom attribute named c_type. It means that clicking on a c_type = “box” will not make the same action as a c_type = “div”. Example :

$('div#boxDetail').c_type = "div";

EDIT 2 : Forget what I’ve said. Raycaster doesn’t work anymore when the div is added to objects. But I have an inda, I will try, and tell you the result.

EDIT 3 : My idea doesn’t work. :frowning: It was to make an object containing the div. Why not, after all ?


#2

Raycaster Docs

raycaster.intersectObjects();

returns a three.js custom array. To access to objects you should access this array elements’ “object” property:

from this

if(intersects[0].c_type == "div") {

to this

if(intersects[0].object.c_type == "div") {

#3

Right,

To be honest it was already the case, I just posted a “fake” example of the code. Here is the real code. The problem doesn’t came from this.

var intersects = raycaster.intersectObjects(objects);

if(intersects.length > 0) {
    if(intersects[0].object.c_type == "box") {
        if(oldBox == "") {
            oldBox = intersects[0].object;
        } else {
            oldBox.material.color = oldBoxColor;
            oldBox = intersects[0].object;
        }
        
        intersects[0].object.material.color = targetColor;
        $('div#boxDetail').remove();
        showBoxDetail(intersects[0].object);
    } else if(intersects[0].object.c_type == "alveole") {
        console.log(intersects[0].object.c_name);
    } else {
        console.log("Item clicked without any action : "+intersects[0]);
    }
}

(In this example I should get the console.log() last alert)
But the error came from this line :

 var intersects = raycaster.intersectObjects(objects);

And I think it’s basically because a div cannot be a target for Raycaster.

The solution should be to ignore Raycaster if the div is clicked, or prevent action. But I don’t really know how to make this possible.


#4

I agree. Well, not a good practice, but you can set a global variable that will be set to (e.g.) 0 if you click the div and check the varibale if you can work on raycaster or not.


#5

I see. Using a flag may work.

I just tried, but get some difficulties. Using the code bellow, you will understand what I am searching to do, but it didn’t work.

var divClick = false;

[...]

if(event.target == "div") {
    console.log("div clicked");
    divClick = true;
} else {
    console.log(event);
    divClick = false;
}

if(!divClick) {
    var intersects = raycaster.intersectObjects(objects);

    if(intersects.length > 0) {
        if(intersects[0].object.c_type == "box") {
            if(oldBox == "") {
                oldBox = intersects[0].object;
            } else {
                oldBox.material.color = oldBoxColor;
                oldBox = intersects[0].object;
            }
            
            intersects[0].object.material.color = targetColor;
            $('div#boxDetail').remove();
            showBoxDetail(intersects[0].object);
        } else if(intersects[0].object.c_type == "alveole") {
            console.log(intersects[0].object.c_name);
        } else {
            console.log("Item clicked without any action : "+intersects[0]);
        }
    }
}

Actually, I am trying to use event attributes to determine if it’s a div… but using attribute “path[0]” & “target” doesn’t work. :frowning:

Thoses attributes contains the full DOM element (see below example). I don’t have any idea how I could recognize it as a DIV, or HTMLElement. Any idea ?

<div id="xxxx" style="yyyy">[...]</div>

Here is the full list of the attributes (event) :

MouseEvent {isTrusted: true, screenX: 325, screenY: 544, clientX: 325, clientY: 443, …}
altKey: false
bubbles: true
button: 0
buttons: 1
cancelBubble: false
cancelable: true
clientX: 325
clientY: 443
composed: true
ctrlKey: false
currentTarget: null
defaultPrevented: true
detail: 1
eventPhase: 0
fromElement: null
isTrusted: true
layerX: 304
layerY: 329
metaKey: false
movementX: 0
movementY: 0
offsetX: 303
offsetY: 328
pageX: 325
pageY: 443
path: (6) [div, div#boxDetail.boxDetail, body, html, document, Window]
relatedTarget: null
returnValue: false
screenX: 325
screenY: 544
shiftKey: false
sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
srcElement: div
target: div
timeStamp: 5804.200000013225
toElement: div
type: "mousedown"
view: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
which: 1
x: 325
y: 443
__proto__: MouseEvent

#6

you use jquery. Looking at your code I assume this ( let me know if I’m wrong ):

//you have the div saved in a var with jQuery
var div = $('#yourdiv');

//you have a mouse click handler like this
$(window).on('click', function(event) {
    
    if(event.target == "div") {
        console.log("div clicked");
        divClick = true;
    } else {
        console.log(event);
        divClick = false;
    }

if you saved the reference of “div” with jquery, you should check it’s first element since jquery saves DOM elements as objects with properties. So:

if( event.target == div[0] )

and I notice that you checked for “div” that’s a string. So it’s wrong anyway. If you saved div reference with pure js, like:

var div = document.getElementById('div');

you can check this way:

if( event.target == div )

EDIT Can’t post another reply.

Ok, try to handle the click directly on the div:

$('div').on('click', function() {

    divClick = ( divClick ) ? false : true;

}

It doesn’t matter how many elements are inside it.


#7

Your idea is awesome but it didn’t not work.

My div contains many HTMLElements, including other div. event.target is based on the upper element, not the container. Maybe by checking the event.path elements in a loop ?

I will try tomorrow and EDIT this post, or answer to expose the results.


#8

I am answering instead of editing my post to expose the solution… and it works !

@ThoughtsRiff I saw your edit too late, sorry. So, I tried the solution on my previous post.

var div = "", divClick = false, divLoop = true;

[...]

function onDocumentMouseDown(event) {
    event.preventDefault();

    mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
    mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);

    if(div != "") {
        for(var i in event.path) {
            if(divLoop) {
                if(event.path[i] == div[0]) {
                    divClick = true;
                    divLoop = false;
                    break;
                }
            }
        }
    }

    if(!divClick) {
        var intersects = raycaster.intersectObjects(objects);

        if(intersects.length > 0) {
            if(intersects[0].object.c_type == "box") {
                if(oldBox == "") {
                    oldBox = intersects[0].object;
                } else {
                    oldBox.material.color = oldBoxColor;
                    oldBox = intersects[0].object;
                }
                
                intersects[0].object.material.color = targetColor;
                $('div#boxDetail').remove();
                showBoxDetail(intersects[0].object);
            } else if(intersects[0].object.c_type == "alveole") {
                console.log(intersects[0].object.c_name);
            } else {
                console.log("Item clicked without any action : "+intersects[0]);
            }
        }
    }
    
    divClick = false;
    divLoop = true;
}

My DIV is created after a click on an element, so it isn’t natively on the DOM. Using a flag divLoop & another flag divClick I get this working. All the Raycaster part is ignored if divClick is true.

This solution is based on your post.

Problem solved !

Thanx.

EDIT : Code updated regarding the last comment of @ThoughtsRiff. Thanx you again !


#9

I’m glad you solved it. You can even “break” the for loop when you find path[I] = div[0] to avoid useless check :slight_smile: