Prevent scaling in CSS3DRenderer

For a detailed explanation of what I am attempting to achieve, please refer to my previous question here.

In summary: My goal is to have HTML elements rotate in sync with OrbitControls to give the illusion that these elements are attached to a globe and move along with it (similar to map markers on a 3D earth above specific countries).

I managed to accomplish this using the THREE.js CSS3DRenderer, successfully positioning the HTML elements on the 3D globe based on latitude and longitude calculations, making them rotate with the globe when using OrbitControls.

The issue

However, the problem I'm facing is that the HTML elements scale dynamically based on their proximity to the camera. While this may enhance the sense of distance, it complicates sizing consistency and causes blurriness/pixelation for text and SVGs inside the elements.

Desired solution

I am seeking a way to disable this scaling behavior so that the HTML elements retain their original size regardless of position within the 3D renderer created by CSS3DRenderer.

I anticipate having to modify the CSS3DRenderer.js code for this purpose, but I lack guidance on where to begin and haven't found relevant information elsewhere.

Thank you in advance.

Snippet of my code:

Initializing the CSS3DRenderer

//CSS3D Renderer
rendererHTML = new THREE.CSS3DRenderer();
rendererHTML.setSize(WIDTH, HEIGHT);
rendererHTML.domElement.classList.add('CSS3D-container');

containerHTML = document.querySelector('.globe__container');
containerHTML.appendChild(rendererHTML.domElement);

Resize function (triggered on window resize event)

HEIGHT = sizeControlElem.getBoundingClientRect().width;
WIDTH = sizeControlElem.getBoundingClientRect().width;

renderer.setSize(WIDTH, HEIGHT);
rendererHTML.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();

Creating CSS3DSprite objects from <li> elements in the HTML and assigning initial globe positions

for (let key in this.locationsObject) {

    _this.locationsObject[key].coordinates = calcPosFromLatLonRad(this.locationsObject[key].lat, this.locationsObject[key].long, 300);

    let CSS3D_Object = new THREE.CSS3DSprite(_this.locationsObject[key].element);
    CSS3D_Object.position.set(_this.locationsObject[key].coordinates[0], _this.locationsObject[key].coordinates[1], _this.locationsObject[key].coordinates[2]);
    CSS3D_Object.receiveShadow = false;
    CSS3D_Object.castShadow = false;
    sceneHTML.add(CSS3D_Object);

    _this.locationsObject[key].CSS_Object = CSS3D_Object;

    console.info(CSS3D_Object);
}

Additional code snippets can be viewed in the initial question here

Answer №1

To halt the scaling process, it is essential to convert 3D positions to 2D using the Vector3.project() method. Refer to the code snippet below for details where I have highlighted key aspects in the JavaScript code. Here's a brief explanation:

  1. Duplicate the 3D position of the hotspot into a new vector.
  2. Utilize vector.project(camera) to translate the 3D point to 2D coordinates.
  3. Adjust the range of 2D coords from [-1, 1] to [0, window.width]
  4. Assign these coordinates through CSS to your hotspot element.

Bonus Tip: You can leverage the .z attribute of the 2D vector to identify whether it lies within the camera's frustum or not.

var camera, controls, scene, renderer;

// Initializing arrays for 3D positions and hotspot DIVs
var posArray3D = [];
var divArray = [];

// Creating temporary vector for reuse during loops
var tempVec = new THREE.Vector3();

init();
animate();

function init() {

// Setting up the scene
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xcccccc );

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 3000 );
camera.position.set( 400, 200, 0 );

// Initializing controls
controls = new THREE.OrbitControls( camera, renderer.domElement );
// Additional control settings...

// Generating world elements
// Loop through random positioning...
 
window.addEventListener( 'resize', onWindowResize, false);

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

// Updating positions of all DIVs based on camera view
function updateDivs() {
var vectorScreen = new THREE.Vector3();

// Looping through all positions
for (var i = 0; i < posArray3D.length; i ++) {
vectorScreen.copy(worldToScreen(posArray3D[i], camera));

// Updating CSS attributes for each DIV
divArray[i].style.transform = "translate(" + vectorScreen.x + "px, " + vectorScreen.y + "px)";

// Checking depth, hiding if behind the camera
if(vectorScreen.z <= 1) {
divArray[i].style.display = "block";
} else {
divArray[i].style.display = "none";
}
}
}

// Projecting 3D coordinates to 2D space 
function worldToScreen(_position, _cam) {
tempVec.copy(_position);

tempVec.project(_cam);

// Converting range from [-1, 1] to [0, windowWidth]
tempVec.x = (   tempVec.x + 1 ) * window.innerWidth  / 2;
tempVec.y = ( - tempVec.y + 1 ) * window.innerHeight / 2;

return tempVec;
}

function animate() {

requestAnimationFrame( animate );

controls.update();
updateDivs();

render();

}

function render() {

renderer.render( scene, camera );

}
body {
color: #000;
font-family:Monospace;
font-size:13px;
text-align:center;
font-weight: bold;

background-color: #fff;
margin: 0px;
overflow: hidden;
}
/*hotspotBox holds all .hotspots It's placed on top of WebGL canvas*/
#hotspotBox{
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border: 1px dashed #f90;
pointer-events: none;
}
/*100 hotspots to be placed within #hotspotBox */
.hotspot {
background: #f90;
width: 10px;
height: 10px;
border-radius: 5px;
position: absolute;
cursor: pointer;
pointer-events: auto;
}
<div id="hotspotBox"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>

Answer №2

After exploring different approaches, I discovered that the most straightforward solution to my query is to utilize the CSS2DRenderer instead of the CSS3DRenderer.

The CSS2DRenderer essentially achieves the same result by only transforming the HTML element with translate, without the need for rotate or scale. This allows for easy modification of the size of HTML elements using CSS properties like width and size, which perfectly aligns with my requirements.

Implementing this change was seamless as it follows the exact same process, with the only adjustment being replacing CSS3DSprite with CSS2DObject in my code.

To delve deeper into the functionalities of CSS2DRenderer, check out the documentation here.

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

Incorporating sweetalert2 with the latest Bootstrap 4 framework

I am currently utilizing sweetAlert2 and attempting to integrate bootstrap 4 for button styling by implementing the following properties: buttonsStyling: false, confirmButtonClass: 'btn btn-primary btn-lg', cancelButtonClass: 'btn btn-lg&ap ...

What is the best way to include various audio tracks from my array list?

I am looking to implement multiple audio tracks for a single video file, similar to the example provided in this link https://codepen.io/eabangalore/pen/NZjrNd (they are using their own custom JavaScript with videojs) Here is the list of sound tracks that ...

Obtain directive content in AngularJS prior to compilation

Is there a way to extract the HTML content of a directive prior to compilation? For instance, consider the following: <my-directive> <ul> <li ng-repeat="item in items">{{item.label}}</li> </ul> </my-directive> ...

Website experiences technical difficulties but database remains up-to-date

I previously made a similar post, but I have not resolved the issue yet. Here is the output from my terminal: 23 Dec 22:31:23 - Serving request for url[GET] /team 23 Dec 22:31:23 - Successfully created team with Name : Japan 23 Dec 22:31:23 - Serving re ...

What is causing the data element to remain unchanged within a Vue.js function and what steps can be taken to ensure its value can be updated?

Using the axios API in the created() function, I am able to access webURLs. When a mouseover event triggers a v-for handler in the HTML file, the index is obtained and assigned to the selectedState data element. Although the value of selectedState changes ...

Unable to fetch permissions for user:email via GitHub API

Currently, I am utilizing node-fetch to fetch an OAuth2 token from an OAuth2 GitHub App. The obtained token allows me to successfully retrieve user information from "https://api.github.com/user". However, I also require the email address, which necessitate ...

Individual Ajax data

Starting out with javascript, I'm a bit unsure of how to tackle this task. Essentially, I am looking to implement a for loop within the ajax data call, rather than listing each item manually. jQuery(document).ready(function() { ...

Display the child elements in an HTML document that have the same class name as the parent element, while concealing all

<div class="element-2"> <div class="element-1"> <p>div 1</p> </div> <div class="element-2"> <p>div 2</p> </div> </div> The main objective is to display only child divs with sim ...

What are the best techniques for optimizing loops when inserting data into a database?

Hi there! Currently, I am facing a challenge in inserting data from an XML file into my MySql database using Sails.js and waterline for the queries. In my database schema, I have two tables named Users and Pets. A user can have multiple pets, and a pet can ...

Secure your Express.js session cookies for enhanced protection

Struggling to figure out how to set a secure cookie in the expressjs framework. Any suggestions on where I can find an option for this? ...

Retrieve the bounding rectangle of a div that has the CSS style `display: contents` using the getBoundingClientRect

My objective is to apply styling and obtain the bounding box of an entire "row" within a CSS grid, including features like highlighting when hovering over it. To achieve the styling aspect, I make use of the display: contents property, so that the styles ...

Switch the cursor to display the magnifying glass icon for zooming in and out

I am curious about how to modify the cursor shape to display a zoom in and zoom out symbol. Changing the cursor to indicate busy or wait status is something I am familiar with, document.manual_production.style.cursor='wait'; However, I am unsu ...

Exploring the world of unit testing in a store - any tips on getting it to work

I am currently working on a store.js file import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const mutations = { increment: state => state.count++, Changeloading(state, status) { state.loading = status }, Cha ...

Unable to locate the value of the query string

I need help finding the query string value for the URL www.example.com/product?id=23 This is the code I am using: let myApp = angular.module('myApp', []); myApp.controller('test', ['$scope', '$location', '$ ...

Node.JS displaying the asynchronous functionality with 'Promise' included in the returned data

Here is a node.js function call and function that I am having trouble understanding: var returned = checkCurrentProcesses() returned.then(() => { console.log(returned) }) function checkCurrentProcesses() { return new Promise(funct ...

Having issues with adding elements to an array object in JavaScript

I've got some HTML code that looks like this: HTML: <INPUT TYPE=CHECKBOX NAME="clcik" onClick="add('1234','blah')" /> <input type="hidden" id="project" value="" /> JS: function add(obj1 , obj2){ var jsonAr ...

What are the steps to access an Alexa skill through a web browser?

I recently developed an Alexa skill for recipe recommendations. I am wondering if it is possible to have the skill open a browser on my phone and display the recipe when I say, "Alexa, send me the recipe"? The skill is working perfectly in the Alexa devel ...

I am having trouble retrieving the properties of "2d" objects using tiles[i-1], unlike standard objects in JavaScript

I've been working on constructing a random map generator by utilizing a grid composed of tiles within a canvas. In my process, I'm investigating the properties of each tile tiles[i] before handling tiles[i-1]. While this procedure seems to functi ...

Organize information by time intervals using JavaScript

I am currently facing an issue where I need to dynamically sort data from the server based on different fields. While sorting is working flawlessly for all fields, I am encountering a problem with the time slot field. The challenge lies in sorting the data ...

Ways to dynamically update the value of an object property within reactJS state

In the scenario where a component holds state like so: this.state = { enabled: { one: false, two: false, three: false } } What is the proper way to utilize this.setState() in order to set the value of a dynamic property? An attempt such ...