Exploring the world of Three.js: showcasing various models and bringing them to life with animations out

I'm working on a threejs project where I aim to render a model and utilize mouse interaction to rotate around it.

For inspiration, I found a similar concept on this page:

While I have successfully created an example, the controls are currently limited to click and drag actions.

  • The main objectives are to render a model.
  • Implement a feature where clicking a button will rotate the camera and display a new model as the previous one fades away (currently using a dropdown to switch models).
  • Upon hovering over the model, trigger a call to action or a special mouse animation that can act as a link.
  • Enable the ability to pan around the model using mouse movement (without click-drag).

-- To start off, I'm experimenting with this demo:

I tried incorporating mouse interactions like this:

let mouseX = 0, mouseY = 0;

function onDocumentMouseMove( event ) {    
  mouseX = ( event.clientX - windowHalfX );
  mouseY = ( event.clientY - windowHalfY );    
}


function render() {    
  camera.position.x += ( mouseX - camera.position.x ) * 0.05;
  camera.position.y += ( - mouseY - camera.position.y ) * 0.05;
  camera.lookAt( scene.position );
  renderer.render( scene, camera );
}

However, this caused the model to spin rapidly and zoom in excessively.


Here's the most recent code snippet:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>three.js webgl - animation - keyframes</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <link type="text/css" rel="stylesheet" href="main.css">
        <style>
            body {
                background-color: #bfe3dd;
                color: #000;
            }

            a {
                color: #2983ff;
            }
        </style>
    </head>

    <body>

        <div id="container"></div>

    
        <div id="info">
    
            <!--
                <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - animation - keyframes<br/>
                Model: <a href="https://www.artstation.com/artwork/1AGwX" target="_blank" rel="noopener">Littlest Tokyo</a> by
                <a href="https://www.artstation.com/glenatron" target="_blank" rel="noopener">Glen Fox</a>, CC Attribution.
            --> 

            <select id="models">
              <option value="models/gltf/LittlestTokyo.glb">LittlestTokyo</option>
              <option value="models/gltf/Flamingo.glb" selected="selected">Flamingo</option>
            </select>
        </div>
    




        <script type="module">

            import * as THREE from '../build/three.module.js';

            //import Stats from './jsm/libs/stats.module.js';

            import { OrbitControls } from './jsm/controls/OrbitControls.js';
            import { RoomEnvironment } from './jsm/environments/RoomEnvironment.js';

            import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
            import { DRACOLoader } from './jsm/loaders/DRACOLoader.js';

            let mixer;

            const clock = new THREE.Clock();
            const container = document.getElementById( 'container' );

            //const stats = new Stats();
            //container.appendChild( stats.dom );

            const renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.outputEncoding = THREE.sRGBEncoding;
            container.appendChild( renderer.domElement );

            const pmremGenerator = new THREE.PMREMGenerator( renderer );

            const scene = new THREE.Scene();
            scene.background = new THREE.Color( 0xbfe3dd );
            scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;

            const camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
            camera.position.set( 5, 2, 8 );

            const controls = new OrbitControls( camera, renderer.domElement );
            controls.target.set( 0, 0.5, 0 );
            controls.update();
            controls.enablePan = false;
            controls.enableDamping = true;

            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath( 'js/libs/draco/gltf/' );

            const loader = new GLTFLoader();
            loader.setDRACOLoader( dracoLoader );
            

            loadModel('models/gltf/Flamingo.glb')


            function loadModel(path){

                loader.load(path, function ( gltf ) {
                     //remove model

                     // remove last model
                     // if(model) model.parent.remove(model)
                     scene.clear();

                    //add model
                    add(gltf)
                }, undefined, function ( e ) {
                    console.error(e);
                });

            }


            window.onresize = function () {

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

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

            };


            function add(gltf){
                let model = gltf.scene;
                model.position.set( 1, 1, 0 );
                model.scale.set( 0.01, 0.01, 0.01 );
                scene.add( model );

                mixer = new THREE.AnimationMixer( model );
                mixer.clipAction( gltf.animations[ 0 ] ).play();

                animate();
            }



            function load(path) {
               console.log("loading", path)

               loadModel(path)
            }


            document.getElementById('models').addEventListener('change', function() {
              console.log('You selected: ', this.value);

                console.log("model", window.model)

                load(this.value)
            });


            function animate() {

                requestAnimationFrame( animate );

                const delta = clock.getDelta();

                mixer.update( delta );

                controls.update();

                //stats.update();

                renderer.render( scene, camera );

            }


        </script>

    </body>

</html>

Answer №1

Using the x-position to set itself in the code

camera.position.x += mouseX - camera.position.x
can lead to unpredictable behavior due to self-referencing values. A simpler approach would be:

camera.position.x = mouseX * 0.05;

For a smoother animation, consider using linear interpolation between two values:

let mouseXTarget = 0, mouseX = 0;

function onDocumentMouseMove( event ) {    
  mouseXTarget = ( event.clientX - windowHalfX ) * 0.05;
}

function render() {
  // Smoothly transition mouseX to its target
  mouseX = THREE.MathUtils.lerp(mouseX, mouseXTarget, 0.1);
  camera.position.x += mouseX;
  camera.lookAt( scene.position );
}

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

Sending a JSON stringified JavaScript object to a server: A step-by-step guide

I am currently working with VB.Net and MVC 5. In my project, I have an object that I created using javaScript: var myEdits = { listOfIDs: [], listOfValues : [] }; My goal is to send this object to the controller an ...

Revamp the layout of the lower HTML video menu

Here is my code: <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> video { width: 100%; height: auto; } </style> </head> <body> <video width="400" controls> ...

The CSRF token in Laravel Blade seems to be unreachable by Vue

Having a blade with the following code snippet. <meta name="csrf-token" content="{{ csrf_token() }}"> <covid-form> </covid-form> Inside the covid-form component, there is a form structure like this: <form @submit.prevent="send"&g ...

determine the vertical dimension of the horizontal scrollbar

I ran into an issue where I needed to determine the height of a horizontal scrollbar. This question and answer recommended using the clientHeight property to calculate the difference. Unfortunately, this method no longer works as shown here: https://jsfid ...

Bringing in two JavaScript files with identical names

Recently, I encountered a challenging situation. I am working with material-ui and nextjs, both of which have a module called Link that is essential for my project. import { Link } from '@material-ui/core'; However, this setup is causing compil ...

"UIWebView experiences a crash when a long press gesture is performed on a textarea element

Looking to implement a custom keyboard for UIWebView that is entirely based on HTML/JS/CSS for compatibility across multiple devices. To achieve this, I included a notification as shown below: [[NSNotificationCenter defaultCenter] addObserver:self selecto ...

Using SWR, retrieve data in client components consistently for each request in a Next.js version 13.3 environment

I recently upgraded to Next.js 13.3 with the app dir set up. I have a simple component that displays the current date. However, it only updates the date once and then doesn't update again until I rebuild the app. In Next.js 12, the date would update w ...

Can a client component in NextJs retrieve data from a server component?

Apologies for the way the question is phrased. I have a server component named auth.ts that retrieves user details after they log in. This server side component is located at //auth.ts export const validateRequest = cache( async (): Promise< { use ...

Getting Started with ThreeJS code results in a console error message

Currently, I am diving into learning Three.js by following their comprehensive getting started guide, which can be found at . I have diligently copied the code provided on the site and ensured that I am using the most up-to-date library. However, when I ex ...

Navigating with firebase authentication and angular routing

Currently, I am in the process of developing an ionic app using version 4. For this project, I have decided to utilize firestore as my database and implement firebase-authentication for user login and registration functionalities. However, I have encounter ...

JavaScript is unable to modify the value of an input in HTML

function modifyComment(id) { var pageID = "<?= $ID ?>"; var commentID = "p" + id; console.log(id); console.log(commentID); console.log(pageID); var commentContent = document.getElementById(commentID).innerHTML; < ...

ReactJS does not display an image from the local file system

I'm currently facing an issue while trying to load an image in my next.js project. I am using the native tag from react, but the image is not appearing on the page. I have provided the links to my two JavaScript files (styles and component) below. An ...

Using async/await keywords in React Native while iterating through an array and calling an API does not result in successful resolution

When attempting to request data from my API in this manner: export default ({ navigation }) => { // Call getMovieImages with the id of the likedMovies from the state.likedMovies const { getMovieImages, state:{ likedMovies }} = useContext(MovieContext); ...

Guide to making an `Ajax Login` button

I am interested in creating a SIGN IN button using ajax. Specifically, I want it to display something (such as welcome) on the same page without refreshing it. This is the progress I have made so far: update2: <form id="myForm" onsubmit="return signi ...

The action is not being added to the HTML when the click event is triggered

I'm developing a GIF generator, with the aim of generating clickable buttons that will dynamically add 10 gifs based on the search term to the page. Although the click event is triggering the console log, it's not adding divs with gif images and ...

Is there a method to stop react-select (Select.Async) from erasing the search input value when it loses focus?

Situation: In my setup, I have a standard select element (categories), which dictates the options displayed in a Select.Async component from react-select. Problem: Consider this scenario: a user is searching for an option within Select.Async whil ...

What is the best way to customize a button component's className when importing it into another component?

Looking to customize a button based on the specific page it's imported on? Let's dive into my button component code: import React from "react"; import "./Button.css"; export interface Props { // List of props here } // Button component def ...

Having trouble with a jquery link not working even after removing the 'disabled' class

I am currently using a script that disables links with the class "disabled" //disable links $(document).ready(function() { $(".disabled a").click(function() { return false; }); }); In addition, I have another script that toggles the disabled s ...

Create a new division directly underneath the bootstrap column

Imagine having a bootstrap table like this: https://i.sstatic.net/kXHiP.jpg Now, if you want to click on a column and open a div with more details below it, is it achievable with CSS or JavaScript? https://i.sstatic.net/HIfkK.jpg I tried using the Metr ...

Webstorm showcases files with similar content in a distinct manner

In Webstorm, everything is color-coded based on whether it is a function, object, etc. I am working with JavaScript. I have noticed that in two different files, the same line of code looks differently: var Comment = React.createClass({ In file 1: "var" ...