Looking for mouseover tooltips in three.js?

Currently, I am working on loading an object similar to this demo. My goal is to display a tooltip or infobox when hovering over a specific part of the object. For example, upon clicking the top button, I would like a tooltip to appear above the box. I have experimented with various examples, but none seem to work with my particular object.

References: Example 1, Example 2

Here's what I've attempted:

 cube.name = "top cube";
  cube.getObjectbyname = "top cube obj";
  scene.add(cube); // add cube to the scene

As a newcomer to both three.js and angular material, I'm unsure how to incorporate a tooltip. Can someone provide guidance on how to address this issue? Specifically, I'm looking for assistance in linking my button to the object's top, left, or right side.

Answer №1

Check out this example showcasing tooltips using the CSS2DRenderer module, where tooltips are continuously displayed and synchronized with objects' positions.

If you prefer them to only show up on hover, you'll need to selectively toggle them through a Raycaster (sample code available in the documentation).

Here's an illustration of both techniques combined (Codesandbox) with a single shared label that dynamically moves and updates based on hovered elements within your scene:

body {
  margin: 0;
}

canvas {
  cursor: grab;
  display: block;
}

canvas:active {
  cursor: grabbing;
}

.hovered {
  cursor: help;
}

.label {
  color: #fff;
  font-family: sans-serif;
  padding: 2px;
  background: rgba(0, 0, 0, 0.6);
}
<script type="module">
  import {
    WebGLRenderer,
    PerspectiveCamera,
    Scene,
    BoxGeometry,
    MeshNormalMaterial,
    Mesh,
    Vector3,
    Box3,
    Raycaster,
    Vector2,
  } from 'https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ccb8a4bea9a98cfce2fdfef9e2fe">[email protected]</a>/build/three.module.js';
  import { OrbitControls } from 'https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="196d716b7c7c592937282b2c">[email protected]</a>/examples/jsm/controls/OrbitControls.js';
  import {
    CSS2DRenderer,
    CSS2DObject,
  } from 'https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="acd8c4dec9c9ec9c829d9e99829e">[email protected]</a>/examples/jsm/renderers/CSS2DRenderer.js';

  // Boilerplate
  const { innerWidth, innerHeight } = window;

  const renderer = new WebGLRenderer({ alpha: true, antialias: true });
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(2);
  document.body.appendChild(renderer.domElement);

  const camera = new PerspectiveCamera(70, innerWidth / innerHeight, 0.1, 10);
  camera.position.z = 1;

  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;

  const scene = new Scene();

  const geometry = new BoxGeometry(0.2, 0.2, 0.2);
  const material = new MeshNormalMaterial();
  const mesh = new Mesh(geometry, material);
  mesh.name = 'mesh';
  scene.add(mesh);

  // Setting up labels
  const labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize(innerWidth, innerHeight);
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.body.appendChild(labelRenderer.domElement);

  const labelDiv = document.createElement('div');
  labelDiv.className = 'label';
  labelDiv.style.marginTop = '-1em';
  const label = new CSS2DObject(labelDiv);
  label.visible = false;
  scene.add(label);

  // Tracking mouse movement for object selection
  const raycaster = new Raycaster();
  const mouse = new Vector2();

  window.addEventListener('mousemove', ({ clientX, clientY }) => {
    const { innerWidth, innerHeight } = window;

    mouse.x = (clientX / innerWidth) * 2 - 1;
    mouse.y = -(clientY / innerHeight) * 2 + 1;
  });

  // Handling window resize
  window.addEventListener('resize', () => {
    const { innerWidth, innerHeight } = window;

    renderer.setSize(innerWidth, innerHeight);
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
  });

  renderer.setAnimationLoop(() => {
    controls.update();

    // Selecting objects with normalized mouse coordinates
    raycaster.setFromCamera(mouse, camera);

    const [hovered] = raycaster.intersectObjects(scene.children);

    if (hovered) {
      // Configuring label
      renderer.domElement.className = 'hovered';
      label.visible = true;
      labelDiv.textContent = hovered.object.name;

      // Determining offset from object dimensions
      const offset = new Vector3();
      new Box3().setFromObject(hovered.object).getSize(offset);

      // Moving label over hovered element
      label.position.set(
        hovered.object.position.x,
        offset.y / 2,
        hovered.object.position.z
      );
    } else {
      // Resetting label
      renderer.domElement.className = '';
      label.visible = false;
      labelDiv.textContent = '';
    }

    // Rendering scene
    renderer.render(scene, camera);

    // Rendering labels
    labelRenderer.render(scene, camera);
  });
</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Javascript addClass and removeClass functions are not functioning as expected

Can you help me figure out why the icon still goes into the "startSharing" section even when it is turned off? In my html file, I have the following code for the icon: <div class="startSharing"></div> And in my javascript file, I have the fo ...

Successful AJAX Post request functioning exclusively within the Web Console (Preview)

When a user clicks on an element on the website, I have a simple AJAX request to send the ID of that element. However, the script only works in the Web Console under the Network -> Preview section and this issue occurs across all browsers. Here is the ...

Troubleshooting: Height setting issue with Kendo UI Grid during editing using Jquery

Issue: My Kendo UI JQuery Grid is fully functional except for a bug that occurs when adding a new record. When I add and save a new record, the grid's footer "floats" halfway up the grid and the scrollbar disappears, making it look distorted. Furth ...

Issue with hidden.bs.modal event not triggering in Bootstrap

Even after using the hidden.bs.modal event, it still doesn't seem to work for me. I have attempted to implement the code below, but unfortunately, it's not functioning as expected. Any guidance on what I should try next would be greatly appreciat ...

Can someone break down the meaning of "returning $http.post" in Angular while developing a factory service?

I've been grappling with the concept of return $http.post('/some link') and can't seem to fully grasp it. Imagine I have a node/express backend and am utilizing Angular for my frontend. Within my api, there is a function like this: v ...

Adding color between lines in Three.js

I have two different sets of vertices. One set contains real vertices, and the other set contains the same vertices but with a y value of zero. I am trying to connect these vertices and fill them in but have not been successful so far. I have attempted to ...

Exploring the power of Vue.js by utilizing nested components within single file components

I have been attempting to implement nested single file components, but it's not working as expected. Below is a snippet of my main.js file : import Vue from 'vue' import BootstrapVue from "bootstrap-vue" import App from './App.vue&apos ...

Experiencing a lack of information when trying to retrieve data through an http request in an express/react

Issue: My problem lies in attempting to retrieve data from http://localhost:3000/auth/sendUserData using the http protocol. Unfortunately, I am not receiving any data or response, as indicated by the absence of console logs. Tools at my disposal: The te ...

Rearranging columns in Bootstrap 4 beta across multiple containers

Sorry for any language issues in my English. This shows my regular grid: ------------------------------- | A | B | C | ------------------------------- | D (horizontal menu) | ------------------------------- Is it possible to d ...

Understanding the performance of video in threejs when using getImageData

Edit; Check out the working codepen (you'll need to provide a video file to avoid cross-origin policy) https://codepen.io/bw1984/pen/pezOXm I'm currently trying to adapt the amazing rutt etra example from to utilize video (using threejs), but ...

Tips for executing an SQL query containing a period in its name using JavaScript and Node.JS for an Alexa application

Hello there, I've been attempting to make Alexa announce the outcomes of an SQOL query, but I'm encountering a persistent error whenever I try to incorporate owner.name in the output. this.t("CASEINFO",resp.records[0]._fields.casenumber, resp.r ...

Creating a uniform mobile version for all smartphones on a website

My website, www.sauleezy.altervista.org, is experiencing an issue that I have been unable to resolve. I have set up two different .css files for desktop (style.css) and mobile (handheld.css). In my index file, I included the following code: <meta name= ...

I encountered an SyntaxError while working with Ionic. The error message reads: "Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>)."

Here's the code that is causing me trouble: this.http.get('http://localhost/....) .map((res) => res.json()) .subscribe(( this.navCtrl.push(OtpPage,{mobileno:this.mobile}); }, (err) => { console.log(err ...

What is the method to have the text cursor within a text field start a few pixels in?

I need a text field with the cursor starting a few pixels (let's say 4) from the left-hand side. I am aware that this can be achieved by adjusting the size of the text field using padding, but I am curious if there is a way to resize the text box with ...

How would you go about creating a VueJS component that displays a table with a set number of columns and automatically distributes the cells within them?

Hey there! I'm currently working with a VueJS component that looks like this: <template> <div> <table> <tbody> <tr v-for="(item, index) in items" :key="index"> <t ...

Using Puppeteer-cluster along with cheerio in an express router API results in an unexpectedly blank response

I am currently working on developing an API using express, puppeteer-cluster, and cheerio to extract anchor elements containing specific words that can be used as query parameters. My aim is to utilize puppeteer to capture dynamically generated elements as ...

What is the best way to keep the calendar of a Datepicker always visible while still being able to select a date easily?

When I write my code, the calendar only appears when I press the TextBox. I attempted to place the datepicker in a <div>, but then I was unable to retrieve the selected date. @model Plotting.Models.CalendarModel @using (Html.BeginForm("Calendar", "H ...

Automatically synchronize dynatable with AJAX requests and JSON responses

I'm currently facing a bit of confusion as my code appears to be functioning properly, but the table is not updating as expected. xD I am fetching data from my database and loading it into a table, aiming for it to automatically update every three se ...

How can we eliminate every item of a specific type from an array?

On my Angular controller, I've set up a method linked to a click handler in the scope. This function checks if a checkbox is checked or unchecked and toggles the associated objects in an array accordingly. The code block where I push objects back int ...

The "add to cart" button is unresponsive

My issue is that the add to cart button on my website is not responding properly. I have implemented JavaScript on the add to cart button, where I have assigned an attribute called data-product with a value of {{product.id}}. var updateBtns = document.g ...