Well working tooltip is not only correctly placed text label. It has some of show and hide intelligence.
It should:
- not appear immediately, but once mouse becomes stationary over some object,
- not disappear immediately, rather it should fade away after mouse left the object area,
- it should neither follow the mouse, nor stay where it was, if user moves the mouse around the object area not leaving it.
All of that is included in my solution: http://jsfiddle.net/mmalex/ycnh0wze/
<div id="tooltip"></div>
must be initially added to HTML. The recommended CSS you find below. Crucial to have it position: fixed;
, anything else is a matter of taste.
#tooltip {
position: fixed;
left: 0;
top: 0;
min-width: 100px;
text-align: center;
padding: 5px 12px;
font-family: monospace;
background: #a0c020;
display: none;
opacity: 0;
border: 1px solid black;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
transition: opacity 0.25s linear;
border-radius: 3px;
}
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
//create some camera
camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 5;
camera.position.y = 5;
camera.position.z = 5;
camera.lookAt(0, 0, 0);
var controls = new THREE.OrbitControls(camera);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x595959));
document.body.appendChild(renderer.domElement);
// white spotlight shining from the side, casting a shadow
var spotLight = new THREE.SpotLight(0xffffff, 2.5, 25, Math.PI / 6);
spotLight.position.set(4, 10, 7);
scene.add(spotLight);
// collect objects for raycasting,
// for better performance don't raytrace all scene
var tooltipEnabledObjects = [];
var colors = new RayysWebColors();
for (let k = 0; k < 12; k++) {
var size = 0.5;
var geometry = new THREE.BoxGeometry(size, 0.2, size);
var randomColor = colors.pickRandom();
var material = new THREE.MeshPhongMaterial({
color: randomColor.hex,
transparent: true,
opacity: 0.75
});
var cube = new THREE.Mesh(geometry, material);
cube.userData.tooltipText = randomColor.name;
cube.applyMatrix(new THREE.Matrix4().makeTranslation(-3 + 6 * Math.random(), 0, -3 + 0.5 * k));
scene.add(cube);
tooltipEnabledObjects.push(cube);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.
var mouse = new THREE.Vector2();
var latestMouseProjection; // this is the latest projection of the mouse on object (i.e. intersection with ray)
var hoveredObj; // this objects is hovered at the moment
// tooltip will not appear immediately. If object was hovered shortly,
// - the timer will be canceled and tooltip will not appear at all.
var tooltipDisplayTimeout;
// This will move tooltip to the current mouse position and show it by timer.
function showTooltip() {
var divElement = $("#tooltip");
if (divElement && latestMouseProjection) {
divElement.css({
display: "block",
opacity: 0.0
});
var canvasHalfWidth = renderer.domElement.offsetWidth / 2;
var canvasHalfHeight = renderer.domElement.offsetHeight / 2;
var tooltipPosition = latestMouseProjection.clone().project(camera);
tooltipPosition.x = (tooltipPosition.x * canvasHalfWidth) + canvasHalfWidth + renderer.domElement.offsetLeft;
tooltipPosition.y = -(tooltipPosition.y * canvasHalfHeight) + canvasHalfHeight + renderer.domElement.offsetTop;
var tootipWidth = divElement[0].offsetWidth;
var tootipHeight = divElement[0].offsetHeight;
divElement.css({
left: `${tooltipPosition.x - tootipWidth/2}px`,
top: `${tooltipPosition.y - tootipHeight - 5}px`
});
// var position = new THREE.Vector3();
// var quaternion = new THREE.Quaternion();
// var scale = new THREE.Vector3();
// hoveredObj.matrix.decompose(position, quaternion, scale);
divElement.text(hoveredObj.userData.tooltipText);
setTimeout(function() {
divElement.css({
opacity: 1.0
});
}, 25);
}
}
// This will immediately hide tooltip.
function hideTooltip() {
var divElement = $("#tooltip");
if (divElement) {
divElement.css({
display: "none"
});
}
}
// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
function handleManipulationUpdate() {
raycaster.setFromCamera(mouse, camera); {
var intersects = raycaster.intersectObjects(tooltipEnabledObjects);
if (intersects.length > 0) {
latestMouseProjection = intersects[0].point;
hoveredObj = intersects[0].object;
}
}
if (tooltipDisplayTimeout || !latestMouseProjection) {
clearTimeout(tooltipDisplayTimeout);
tooltipDisplayTimeout = undefined;
hideTooltip();
}
if (!tooltipDisplayTimeout && latestMouseProjection) {
tooltipDisplayTimeout = setTimeout(function() {
tooltipDisplayTimeout = undefined;
showTooltip();
}, 330);
}
}
function onMouseMove(event) {
updateMouseCoords(event, mouse);
latestMouseProjection = undefined;
hoveredObj = undefined;
handleManipulationUpdate();
}
window.addEventListener('mousemove', onMouseMove, false);
animate();