You set in your code the z-index:-1;
of #background and display:absolute;
. That means: It is behind the body z-layer and mouse events will be targeted to the body or anything else in a layer above.
You could set pointer-events:none;
of body or even all elements, but that could be a bad thing with other codes included into the site later. Prefer positioning other covering elements absolute with a higher z-index, those are: #container and #container>canvas.
See how the path is, a DOM event propagates: http://www.w3.org/TR/DOM-Level-3-Events/
In capturing phase the event is first propagated to the view, then the, html and body and after that it will get to the target displayed under the mouse cursor. Than in the bubbeling phase propagation takes the reversed way back to the view.
The rendered scene objects are not in this DOM event path. You have already raytraced a hit in the document event listener. Do this before the propagation reaches the #background element and safe somewhere if the event is known to be handled. You have tried preventDefault()
. Unfortunately the mousemove event isn't cancelable, so this has no effect. Later called event listeners which check preventedDefault
will get false
. The meaning of preventDefault()
is to prevent a default operation that is performed after propagation by the user agent, e.g. mark the word under the mouse pointer after double click. If the default operation should be performed then you cannot use preventDefault()
to tell other listener that the event is already handled.
You could set a custom property of Event to true. It works at least in firefox, however, host objects are not strictly specified by W3C. This could lead to some error in other browsers or in the future. You could use stopPropagation()
if no other listener should be called. Under circumstances this can lead to undesirable behavior or bugs in conjunction with some JavaScript frameworks.
Another approach is to set a variable in a scope of accessible to all of your event listeners, e.g. the global scope or better an encapsulating anonymous function. This could hold the latest handled event and that will not change until the propagation has completed (next event will not be fired before).
You can also handle the events targeted to #background in the listener of document if the raytracer doesn't hit a scene object. Event.target
holds the topmost DOM element hit by a mouse event.
Your modified code with demonstration of the different approaches:
<!DOCTYPE html>
<html>
<head>
<title>events passthrough</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style type="text/css">
body {
font-family: Monospace;
margin: 0px;
overflow: hidden;
}
div#background {
position: absolute;
top: 40px;
left: 40px;
width: 100px;
height: 100px;
background-color: pink;
/*z-index: -1; /* this is behind the body */
}
#container>canvas
{ position: absolute;
z-index: 100;
}
/* Explicitly disable mouse events on covering element. */
/* If z-index of background object is below zero then also disable body */
/* body, */
#container, #container canvas
{ pointer-events:none;
}
/* but let enabled all other elements */
*
{ pointer-events:auto;
}
</style>
</head>
<body>
<script type="text/javascript" src="https://mrdoob.github.io/three.js/build/three.min.js"></script>
<script type="text/javascript" src="https://mrdoob.github.io/three.js/examples/js/libs/stats.min.js"></script>
<div id="background"></div>
<div id="container"></div>
<script type="text/javascript">
var lastHandledEvent;
var container, stats;
var camera, scene, projector, renderer;
var particleMaterial;
var objects = [];
// don't run DOM relevant scripts before construction of DOM is guaranteed
document.addEventListener("DOMContentLoaded", function()
{
init();
animate();
}, false);
function init() {
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 10, 300, 500 );
scene = new THREE.Scene();
var geometry = new THREE.BoxGeometry( 100, 100, 100 );
for ( var i = 0; i < 10; i ++ ) {
var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object.position.x = Math.random() * 800 - 400;
object.position.y = Math.random() * 800 - 400;
object.position.z = Math.random() * 800 - 400;
object.scale.x = Math.random() * 2 + 1;
object.scale.y = Math.random() * 2 + 1;
object.scale.z = Math.random() * 2 + 1;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
scene.add( object );
objects.push( object );
}
var PI2 = Math.PI * 2;
particleMaterial = new THREE.SpriteCanvasMaterial( {
color: 0x000000,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize( window.innerWidth, window.innerHeight );
// renderer.domElement.style.position = 'absolute'; // is done in CSS
// renderer.domElement.style.zIndex = 100; // is done in CSS
container.appendChild( renderer.domElement );
// register document mousemove in capturing phase
// if you want to use another handlers subsequently
document.addEventListener('mousemove', onDocumentMouseMove, true );
document.querySelector('#background').addEventListener('mousemove', onMouseMove, false);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var background = document.getElementById('background');
function onDocumentMouseMove( event )
{
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 )
{
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
//event.preventDefault(); // mousemove is not cancelable
// extending event objects works at least in Firefox, not sure if crossbrowser combatible
// always be careful when extending DOM
event.handled = true; // add custom property
lastHandledEvent = event; // another way: store in a variable accessible by all handlers
// to remember this one was the last handled event
console.log('event marked as handled in document listener');
//event.stopPropagation(); // or could stop propagation, however,
// often not a good praxis in conjuction with frameworks
}
else if(event.target === background)
console.log('background could be handled in document listener');
}
function onMouseMove( event )
{
// if(event.defaultPrevented // mousemove not cancable, always false
if(event.handled) // TODO: check crossbrowser compatibility
// of custom properties on event objects.
// In doubt use a var outside the functions.
console.log('other listener: event.handled is: '+event.handled);
if(lastHandledEvent === event) // the safer way: use variable accessible
{
console.log('NOT handling event in other listener');
return;
}
console.log(event.target, '...or handled in other listener');
event.target.style.backgroundColor = '#'+('00000' + (Math.random() * 0xffffff).toString(16)).slice(-6);
}
function animate() {
requestAnimationFrame( animate );
render();
}
var radius = 600;
var theta = 0;
function render() {
theta += 0.1;
camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
</html>
pointer-events
? – DendrochronologyEvent.target
holds the element the event was targeted to. – Faerie