0

i am working on my Web Developer Portfolio in next.js, TypeScript and react-three-fiber. In one page, I want to do a nice background who is inspired by this: https://codepen.io/algoregalore/pen/KJdBYP.

I want to make multiple models (in a .glb format) appearing with a fade in animation in random positions in the canvas, wait for severals seconds then fade out slowly in order to reappear in another position and vice versa. The main thing is that each model will play its animation independently of the others, out of sync.

Here is the main code in TypeScript:

import {Suspense, useRef, useEffect, RefObject} from 'react';
import {Canvas, useFrame} from '@react-three/fiber';
import {Preload} from '@react-three/drei';
import {Group, Material, Mesh} from 'three';
import {SmartphoneModel, LaptopModel, CookieModel, PawnModel } from './models';
import {getNewPositionsArray, getRandomIntInclusive} from "@/utils/function";
import { ModelProps } from '@/constants/interface';

const componentModels = {
  smartphone: SmartphoneModel,
  laptop: LaptopModel,
  cookie: CookieModel,
  pawn: PawnModel
};

// this component is a wrapper to add new prop and control the animation of the model passed as prop
const ModelFadeWrapper = ({model, position, props} : {model : string, position? : [number, number, number], props : ModelProps}) => {
  const ref = useRef<Group>(null); // we create a ref to handle the model rotation
  const animationStarted = useRef<boolean>(false); // we create a ref that represent if the animation start
  const delayAnimationStart = getRandomIntInclusive(0,4) * 1000; // a random before the start of the animation
  console.log(delayAnimationStart);
  // we create a new var as a component model in order to pass new prop to it like position, ref and the other props.
  const SpecificModel = componentModels[model as keyof typeof componentModels];

  useEffect(()=>{
    if (ref.current?.children != null) {
      (ref.current.children as Mesh[]).forEach((mesh)=>{
        const material = mesh.material as Material;
        material.opacity = 0;
      });
      ref.current.visible = false;
      setTimeout(()=>{
        if (ref.current != null) {
          ref.current.visible = true;
          animationStarted.current = true;
          console.log(model);
        }
      },delayAnimationStart);
    }
  },[]);

  useFrame((state, delta) => {
    if (ref.current?.children != null && animationStarted) {
      ref.current.rotation.y += 0.01; 
    }
  });

  return (
    <SpecificModel modelRef={ref} position={position} {...props}/>
  );
};


// this component represent the background canvas of the about page
const AboutBGCanvas = () => {
  let modelList:{name:string,key:string,props:ModelProps}[] = [
    {name:"pawn", key:"Pawn1",props:{scale:0.1}},
    {name:"pawn", key:"Pawn2", props:{scale:0.1}},
    {name:"laptop", key:"Laptop1",props:{ rotation:[0.8,0,0], scale:0.8}},
    {name:"laptop", key:"Laptop2",props:{ rotation:[0.8,0,0], scale:0.8}},
    {name:"smartphone", key:"Smartphone1",props:{ scale:0.005}},
    {name:"smartphone", key:"Smartphone2",props:{ scale:0.005}},
    {name:"cookie", key:"Cookie1",props:{ scale:1}}
  ]; // this array contain model component
  let positionModelList:[number,number,number][] = getNewPositionsArray(7,{x:[-3,3],y:[-3,3]},1.6); // this array represent all the positions of the respective models in the canvas
  modelList.forEach((modelObj,index)=>{ // for each model object of modelList we add the position value to the props object
    modelObj.props.position = positionModelList[index];
  });

  return (
    <div className='w-full h-auto absolute inset-0 z-[-1]'>
      <Canvas>
        <directionalLight position={[10, 10, 5]} intensity={2} />
        <directionalLight position={[-10, -10, -5]} intensity={1} />
        <Suspense fallback={null}>
          {
            modelList.map((model, index)=>
              <ModelFadeWrapper key={model.key} model={model.name} position={positionModelList[index]} props={model.props}/>
            )
          }
        </Suspense>
        <Preload all/>
      </Canvas>
    </div>
  );
};

export default AboutBGCanvas;

I manage to display the .gbl models in the canvas and to access the rotation and material property of each mesh of the model. (so I can use the opacity). I create a list of object in modelList that I will use later to create the models and I generate a list of spreaded vector positions (x,y,z) to set the positions of all models in the canvas. Then inside the ModelFadeWrapper, I pass the ref and the props object (position, rotation) in the model and I modify the opacity and the rotation of the model by using the ref. For the moment, I test by only changing the y rotation of all the models inside the useFrame().

useFrame((state, delta) => {
    if (ref.current?.children != null && animationStarted) {
       ref.current.rotation.y += 0.01; 
    }
});

But all the models play theirs animations at the same time even though I set a setTimeout to desynchronise the start of the all model's animation. The rotation is in sync with all the models. What can I do to set the rotation for each model not play at the same time? Thanks in advance for your response.

1 Answer 1

0

You are trying to achieve "frame-by-frame" animation, IMO for your use case it is rather complex to implement and maintain.

You could switch for "imperative animations" with a "Tween" engine (for example gsap). With such it is convenient to set individual tweens that you can organise into timeline.

Example here.

Sign up to request clarification or add additional context in comments.

2 Comments

I have check the documentation and sadly, I don't believe that it is possible for my use case. while gsap is a very powerful tool to handle the position, rotation of dom element and even 3D object, it can't change the opacity of a 3DObject. Since I have a Group of mesh for a model I need to set the material's opacity of each mesh children of the 3DObject to make it fade away. And the timeline even with a refObject to the three group can't access to all of the opacity property of all the materials... Maybe I need to see with the Three.clock to create the desync
At the end, it turn out it work pretty well with an hack, I just used a fog behind the models and played with their z axis with gsap to make them fade in/out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.