How can I use plain javascript in React JS

Hello guys, I have a plain javascript code, but I can not use this code in my react page. I want to integrate this three js to my react functional component and use it whenever I want. Can you help me about that ?

console.clear();

// =====================================
class App {
    constructor(opts) {
        this.opts = Object.assign({}, App.defaultOpts, opts);
        this.world = new World();
        this.init();
    }

    init() {
        this.threeEnvironment();
        window.requestAnimationFrame(this.animate.bind(this));
    }

    threeEnvironment() {
        const light = new Light();
        this.world.sceneAdd(light.ambient);
        this.world.sceneAdd(light.sun);
        const lights = lightBalls(this.world, light.lights);
        
        const composition = new Composition({
            sideLength: 10,
            amount: 15, 
            radius: 6, 
            thickness: 2,
            offset: 0.3,
        });
        this.world.sceneAdd(composition.tubes);
    }

    animate() {
        this.world.renderer.render(this.world.scene, this.world.camera);
        window.requestAnimationFrame(this.animate.bind(this));
    }
}

App.defaultOpts = {
    debug: false,
};

function lightBalls(world, meshes) {
    const radius = 12.4;
    const mainTl = new TimelineMax();
    
    meshes.forEach(function (group) {
        world.sceneAdd(group);
        createAnimation(group);
    });
    
    function createAnimation(group) {
        const tl = new TimelineMax({
            yoyo: true,
        });
        
        tl.set(group.position, {
            x: THREE.Math.randInt(-2, 2) * radius + (radius * 0.5),
            z: THREE.Math.randInt(-2, 2) * radius + (radius * 0.5),
        })
        .to(group.position, 2, {
            y: 18,
            ease: Linear.easeNone,
        })
        .to(group.children[0], 1.2, {
            intensity: 4.0,
            distance: 18,
            ease: Linear.easeNone,
        }, '-=1.2');
        
        tl.paused(true);
        
        mainTl.to(tl, 1.2, {
            progress: 1,
            ease: SlowMo.ease.config(0.0, 0.1, true),
            onComplete: createAnimation,
            onCompleteParams: [group],
            delay: THREE.Math.randFloat(0, 0.8),
        }, mainTl.time());
    }
}


// =====================================
class Light {
    
    constructor() {
        this.lights = [];
        this.ambient = null;
        this.sun = null;
        this.createLights();
        this.createAmbient();
        this.createSun();
    }
    
    createLights() {
        for(let i = 0; i < 3; i++) {
            const group = new THREE.Group();

            const light = new THREE.PointLight(0xff0000);
            light.intensity = 4.0; 
            light.distance = 6;
            light.decay = 1.0;
            group.add(light);

            const geometry = new THREE.SphereBufferGeometry(2, 16, 16);
            const material = new THREE.MeshBasicMaterial({
                color: 0xff0000,
            })
            const mesh = new THREE.Mesh(geometry, material);
            group.add(mesh);
            group.position.set(0, -5, 0);

            this.lights.push(group);
        }    
    }
    
    createAmbient() {
        this.ambient = new THREE.AmbientLight(0xffffff, 0.03);
    }
    
    createSun() {
        this.sun = new THREE.SpotLight(0xffffff); // 0.1
        this.sun.intensity = 0.4; 
        this.sun.distance = 100;
        this.sun.angle = Math.PI;
        this.sun.penumbra = 2.0;
        this.sun.decay = 1.0;
        this.sun.position.set(0, 50, 0);
    }
}



// =====================================

class World {
    constructor(opts) {
        this.opts = Object.assign({}, World.defaultOpts, opts);
        this.init();
    }

    init() {
        this.initScene();
        this.initCamera();
        this.initRenderer();
        this.addRenderer();
        window.addEventListener('resize', this.resizeHandler.bind(this));
    }

    initScene() {
        this.scene = new THREE.Scene();
    }

    initCamera() {
        this.camera = new THREE.PerspectiveCamera(
            this.opts.camFov,
            window.innerWidth / window.innerHeight,
            this.opts.camNear,
            this.opts.camFar
        );
        this.camera.position.set(
            this.opts.camPosition.x,
            this.opts.camPosition.y,
            this.opts.camPosition.z
        );
        this.camera.lookAt(this.scene.position);
        this.scene.add(this.camera);
    }

    initRenderer() {
        this.renderer = new THREE.WebGLRenderer({
            alpha: true,
            antialias: true,
            logarithmicDepthBuffer: true,
        });
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    }

    addRenderer() {
        this.opts.container.appendChild(this.renderer.domElement);
    }

    resizeHandler() {
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
    }

    sceneAdd(obj) {
        this.scene.add(obj);
    }
}

World.defaultOpts = {
    container: document.body,
    camPosition: new THREE.Vector3(150, 200, 400),
    camFov: 6,
    camNear: 0.1,
    camFar: 800,
};


class Composition {
    constructor(opts) {
        this.opts = Object.assign({}, Composition.defaultOpts, opts);
        this.tube = Tube({ 
            amount: this.opts.amount, 
            radius: this.opts.radius, 
            thickness: this.opts.thickness 
        });
        this.tubes = this.createTubes();
    }
    
    createRow() {
        const radius = this.opts.radius + this.opts.offset;
        const geometry = new THREE.Geometry();
        
        for(let i = 0; i < this.opts.sideLength; i++) {
            const t = this.tube.clone();
            t.translate(i*radius*2, 0, 0);
            geometry.merge(t);
        }
        
        return geometry;
    }
    
    createTubes() {
        const row = this.createRow();
        const radius = this.opts.radius + this.opts.offset;
        const geometry = new THREE.Geometry();
        
        for(let i = 0; i < this.opts.sideLength; i++) {
            const r = row.clone();
            r.translate(0, 0, i*radius*2);
            geometry.merge(r);
        }
        
        geometry.center();

        const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
        
        const materials = [
        // front
            new THREE.MeshStandardMaterial({ 
                color: 0x333333,
                roughness: 1.0,
                metalness: 0.0,
                emissive: 0x000000,
                flatShading: true, // true
                side: THREE.DoubleSide,
            }),
            // side
            new THREE.MeshStandardMaterial({
                color: 0x333333,
                roughness: 0.6,
                metalness: 0.0,
                emissive: 0x000000,
                flatShading: true, // false
                side: THREE.DoubleSide,
            }) 
        ];

        const mesh = new THREE.Mesh(bufferGeometry, materials)
        // mesh.rotation.set(0, -Math.PI*0.15, 0);
        
        return mesh;
    }
}

Composition.defaultOpts = {
    sideLength: 10,
    amount: 15, 
    radius: 6, 
    thickness: 2,
    offset: 0.3,
};

// ===========================
function createShape({ innerRadius = 4, outerRadius = 6, fineness = 30 }) {
    
    const outer = getPath(outerRadius, fineness, false);
    const baseShape = new THREE.Shape(outer);
    
    const inner = getPath(innerRadius, fineness, true);
    const baseHole = new THREE.Path(inner);
    
    baseShape.holes.push(baseHole);

    return baseShape;
};

const getPath = (radius, fineness, reverse) => {
    const c = radius * 0.55191502449;
    const path = new THREE.CurvePath();

    path.curves = [
        new THREE.CubicBezierCurve(
            new THREE.Vector2(0, radius),
            new THREE.Vector2(c, radius),
            new THREE.Vector2(radius, c),
            new THREE.Vector2(radius, 0)
        ),
        new THREE.CubicBezierCurve(
            new THREE.Vector2(radius, 0),
            new THREE.Vector2(radius, -c),
            new THREE.Vector2(c, -radius),
            new THREE.Vector2(0, -radius)
        ),
        new THREE.CubicBezierCurve(
            new THREE.Vector2(0, -radius),
            new THREE.Vector2(-c, -radius),
            new THREE.Vector2(-radius, -c),
            new THREE.Vector2(-radius, 0)
        ),
        new THREE.CubicBezierCurve(
            new THREE.Vector2(-radius, 0),
            new THREE.Vector2(-radius, c),
            new THREE.Vector2(-c, radius),
            new THREE.Vector2(0, radius)
        ),
    ];

    const points = path.getPoints(fineness);
    if(reverse) points.reverse();
    return points;
};

function Tube({ amount = 4, radius = 6, thickness = 2 }) {
    
    const shape = createShape({ innerRadius: radius - thickness, outerRadius: radius, fineness: 14 });
    const props = {
        amount: amount,
        bevelEnabled: true,
        bevelThickness: 0.3,
        bevelSize: 0.2,
        bevelSegments: 1
    }

    const geometry = new THREE.ExtrudeGeometry( 
        shape,
        props
    );

    geometry.center();
    geometry.computeVertexNormals();

    for ( var i = 0; i < geometry.faces.length; i ++ ) {
        var face = geometry.faces[ i ];
        if (face.materialIndex == 1 ) {
            for ( var j = 0; j < face.vertexNormals.length; j ++ ) {
                face.vertexNormals[ j ].z = 0;
                face.vertexNormals[ j ].normalize();
            }
        }
    }
    geometry.rotateX(Math.PI * 0.5);
    geometry.rotateZ(Math.PI);

    return geometry;
};


// ===========================
const app = new App();

That’s plain javascript code

Technically speaking, all you need is to pass react node ref to your new World constructor - and in theory it should have worked just fine. But taking into consideration how the code is structured - I’d suggest you don’t do that and just rewrite the code to fit React ecosystem:

  • Right now you can’t create new App instance on component mount / update, since you’ll spawn endless amounts of requestAnimationFrame loops.
  • Right now, you can’t also reuse a single App or World instance easily, since they are super coupled and, for example, App modifies World scene, which it shouldn’t, World creates renderer and window listeners, which App should be doing, etc etc etc. These responsibilities seem to be all over the place atm, if you try to create one App for your entire application, and then just assign react node refs to World.renderer - in the best scenario you’ll get the same, unmodifiable scene in each React component, but more likely you’ll only get rendering in last component you apply the App to - and the rest will just show nothing.

It’s not only not portable, but also just poorly structured right now - consider just rewriting and simplifying the entire thing (maybe also try to avoid OOP at all, unless you really know you need it.)

Thank you so much. That’s very clear. But how can I create this type of models with using three.js, this library is so detailed that I can not find any clear article or code components about it.

Dear @Mahammad
I think in your case, it is better to find the method for integrating this javascript code to react app as you mentioned in your post. :slight_smile: :slight_smile: :slight_smile:
But it is not impossible, along the logic, you have to do work using react props and hooks.
Probably this will be easier than other one and more correct in performance. wish your work will be going well.