Changing camera view in Three.js upon clicking an HTML button

My goal is to create a straightforward animation using three.js, HTML, and CSS. The concept involves generating multiple BoxGeometries within a for loop and adjusting the rotation of each box incrementally with each iteration. I have successfully achieved this technique.

Below is my implementation in the main.js file:

import './style.css'
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';


//Initialize Camera and Set Position
const camera = new THREE.PerspectiveCamera( 100, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.setZ(0);
camera.position.setX(32);
camera.position.setY(0);

//Create Scene
const scene = new THREE.Scene();


//Initialize Renderer
const renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animation );
document.body.appendChild( renderer.domElement );


//Initialize OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);


//Initialize Light
const pointLight = new THREE.PointLight(0xfffff, 1);
pointLight.position.set( 1000, 1000, 1000);
scene.add(pointLight);

//Intro cube init
//This loop generates multiple rotating Box meshes 
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00, wireframe: false} );
for(var i = 0; i < 16; i++){
  const cube = new THREE.Mesh( geometry, material );
  cube.position.x = i * 2
  cube.rotation.x = i * .05
  cube.scale.x = .1
  scene.add( cube );
}

//HTML button event listener
var start_button = document.getElementById("begin-animation");
start_button.addEventListener("click", startButton);
function startButton(){
  camera.position.x -= 5;
}


// animation
function animation() {

  controls.update()
  renderer.render( scene, camera );

}

This is how my index.html file looks:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Site</title>
  </head>
  <body>
    <canvas id="bg"></canvas>

    <div class="container">
      <button id="begin-animation" class="begin-animation" value="False">Begin</button>
    </div>

    <script type="module" src="/main.js"></script>
  </body>
</html>

And here is my style.css file:

...

The challenge I face is ensuring that the camera.position.x only triggers when the "begin-animation" button is clicked once to start the animation. Moving this function to the main animation() results in an immediate action before the button is pressed. How can I achieve the same effect but tied to the button click?

I have attached an image illustrating the desired outcome (Note: the star marks the initial camera position facing the first mesh while showing the full view of every subsequent mesh): https://i.sstatic.net/NKWz6.jpg

Answer №1

UPDATED BY @PhilN, REFER BELOW

You are encountering a very simple problem that can be easily solved. To make your event listener run only once, there are various methods in JavaScript that you can utilize:

  1. Using the 'once' option: The addEventListener() method has a third argument which can be an option object (with multiple properties) or can use capture. By using an option object with a property named 'once', we can ensure that the event listener runs only once. Here's an example:
let btn = document.getElementById('btn');
btn.addEventListener("click", function() {

    // onClick code

}, {once : true});
  1. Removing the event listener after it is triggered: You can remove the event listener immediately after its first execution. For instance:
let btn = document.getElementById('btn');
function onClick(event){
   btn.removeEventListener('click', onClick);
   console.log("Event fired once, no more click will be handled");
}
btn.addEventListener('click', onClick);

*** UPDATE BY @PhilN ***

The advice from @CyrusKabir regarding throttling the click event is accurate. However, what is missing is a state check to determine whether the camera should start or continue animating. We need a state variable to indicate if the button has ever been clicked.

In a shared outer scope of the handler and animate(), let's create a boolean state variable.

let animatingCamera = false;

Initially, animatingCamera is set to false, indicating that the button has not been clicked. Inside the click handler, we will change this state to true and then remove the handler.

btn.addEventListener("click", function() {

    animatingCamera = true;

}, {once : true});

Within the animate() function, we will encapsulate the assignment to camera.position.x within a conditional statement that checks animatingCamera. If animatingCamera is false, indicating that the button hasn't been clicked yet, the block will exit. If it's true, then the animation is either starting or ongoing, allowing animate() to update camera.position.x.

function animate() {
  ...
  if(animatingCamera){
    camera.position.x = ...
  }
  ...
}

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

Automated form with built-in calculations

Whenever a product is selected from the dropdown menu, the price value should be automatically filled in the input field with ID #price. Then, the user can enter the quantity in the input field with ID #quantity, and the total sum of price multiplied by qu ...

Is it possible to use file upload for sending via Ajax's POST method?

Let's talk about the scenario at hand Here's what happens in a single form: 1) The user clicks on the 'browse' button, which opens a dialog to select an image file for uploading. Example: input id='img_upload' name="ufile" ...

Unexpected errors occur when adding scripts via innerHTML

Here's a function I'm using to add scripts for TeX: function adjustScript() { var format = document.getElementById("format"); var original_text = document.getElementById("question"); var s = document.createElement('s ...

Can we leverage map/filter/reduce functions within a promise by encapsulating the result with Promise.resolve()?

Currently, my approach to doing loops inside a promise looks like this: asyncFunc() .then(() => { return new Promise((resolve) => { for (let i = 0; i < length; i++) { // do something if (j == length - 1) { ...

CSS declarations that have not been acknowledged or acknowledged

While working on a client's stylesheet today, I came across the following code: p { -webkit-hyphens: auto; -webkit-hyphenate-character: "\2010"; -webkit-hyphenate-limit-after: 1; -webkit-hyphenate-limit-before: 3; -moz-hyphens: manual; orphans: ...

Seeing the Bootstrap css load immediately upon the page loading and switching between pages

Upon loading my webpage or navigating between subpages, I've noticed that the Bootstrap.css styles are interfering with my CSS transitions. For some reason, my regular links appear orange initially, but they turn blue during loading or when transition ...

The dropdown menu is extending beyond the edge of the screen

I'm having trouble with my navbar dropdown menu extending off the page to the right. Take a look at the code below: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-c ...

What could be causing my div element to not inherit its parent div's width?

My query revolves around a header div that I'm struggling to align with the same width as its parent div, while keeping the content centered. Currently, the div is only as wide as its content, causing it to be off-center. https://i.stack.imgur.com/E1 ...

What is the best way to continuously monitor MongoDB for updates and sync them with my user interface?

Looking to continuously monitor a user's notifications in MongoDB, updating them on specific actions and displaying updates on my React frontend without reloading every time the user logs in. I am currently utilizing Node and Mongoose models for datab ...

Splitting HTML code into separate sections based on tables is the main feature of Simple HTML DOM

My task involves scraping data from a webpage, but the challenge lies in the fact that the content is not enclosed within divs or any specific tags. The only distinguishing feature I have found is a particular table that separates the chunks of data I requ ...

In my experience, I have encountered issues with certain routes not functioning properly within Express

I am currently working on developing a tic-tac-toe game and looking to store user data in a database. However, I am facing an issue with the router I intended to use for this purpose as it is returning an 'Internal server error message (500)'. B ...

Unusual HTML Structure (content misplaced or out of order?)

Recently, I started learning CSS/HTML in school and we just delved into Javascript. Currently, we are working on a website project. However, while trying to integrate the content with the navbar, I encountered a strange issue. When resizing to 620px or le ...

To initiate animation upon a click event, follow these steps

Every time I try to access this webpage, it immediately sends me to a different page. This particular code is set up to redirect to specified links on its own. Is there a way for me to modify this code so that it only redirects when clicked? ...

Tips on effectively centering a wide div

After much experimentation, this is what I came up with: (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/en ...

Sending input values from textboxes to the Controller

I currently have the following code snippets: Home Controller: public IActionResult Index() { return View(); } public ActionResult Transfer() { string path = @Url.Content(webRootPath + "\\SampleData\\TruckDtrSource.json&q ...

Adjust the size and positioning of SVG elements within a flexbox layout

I am attempting to create a flexbox with an SVG element as a child. The goal is for the SVG element to be as large as possible in terms of both width and height. .grid { display: grid; grid-template-columns: 2fr 2fr 2fr; gap: 1em; } .parent { asp ...

What is the best way to erase information displayed when hovering over an element using mouseout?

Whenever the user hovers over an image, an information box appears regarding that specific image. The information inside the box changes as I move over another image, but when not hovering over any images, the information box remains visible. Unfortunately ...

Utilize the identical button and modify the text displayed on it for implementing my jQuery function that toggles visibility

Is there a way to modify my current jQuery function for displaying and hiding a contact form using the same button? Currently, I have two buttons that toggle the visibility of the form: "Write me" shows the form on click and changes to "I don't want ...

Reduce the length of selection choices in the dropdown menu of WebForms

In my WebForms project, I have created a drop-down list by binding a list of data with 'Title' and 'Id' to it. Ddltitlelist.DataSource = submissionTitleList; Ddltitlelist.DataTextField = "Submission_Title"; Ddltitlelist.DataValueField ...

The Bootstrap menu is having trouble collapsing properly

We're currently in the process of constructing a website using bootstrap, but we've encountered an issue with the menu bar. Upon visiting , you'll notice that the top menu functions properly and collapses perfectly when viewed on mobile dev ...