Simple animation making certain components disappear and re-appear

Hello, I am trying to animate a little “online” artifact like a wifi symbol, with the emanations of the signal growing outwards and disappearing. Basically I just want the bands to appear until all three are there, then disappear, and loop the animation.

image

Here is my code. I figured when I have my business logic, I could be setting state for each ring that mutates a visibility attribute, but I am not sure the best way to do it

//@ts-nocheck
import {useEffect, useRef} from "react";
import {useFrame} from "@react-three/fiber";
 
export default function OnlineSymbol(props) {
 
  const group = useRef();
  const txt = useRef();
 
  useEffect(() => {

   }, [])
   
   useFrame(({clock}) => {
    console.log(clock.getElapsedTime());
    //loop making ringbuffer geometry appear and disappeaer
     })
 
return (
  <group  ref={group}  rotation={[0, 0, 0]}>
  
    <mesh position={[0, 0, 0]}>
        <circleGeometry args={[0.25, 200, 20]}/>
        <meshBasicMaterial attach="material" color="#008000" />
    </mesh>
    <mesh>
    <ringBufferGeometry args={[0.6, 0.8, 128, 1, 1.3, 1.9]} />
    <meshBasicMaterial attach="material" color="#008000" />
    </mesh>
    <mesh>
    <ringBufferGeometry args={[1, 1.2, 128, 1, 1.3, 1.9]} />
    <meshBasicMaterial attach="material" color="#008000" />
    </mesh>
    <mesh>
    <ringBufferGeometry args={[1.4, 1.6, 128, 1, 1.3, 1.9]} />
    <meshBasicMaterial attach="material" color="#008000" />
    </mesh>
   </group>
)

}

Yeahhh so I tried this and it seems to work, but my intuition is that i’m doing something much too complicated and there’s a much simpler solution so I’d be interested in hearing that lol.

Basically I am driving state changes defined by the field stateArray using a sin wave, and when it changes from negative to positive I advance the state

import {useRef, useState} from "react";
import {useFrame} from "@react-three/fiber";

 
export default function OnlineSymbol(props) {
 
  const positive = useRef(true);
  const stateArray = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]];
  const [stateIndex, setStateIndex] = useState(0);

   useFrame(({clock}) => {

let speed = 8;
let val = Math.sin(clock.getElapsedTime() * speed);
   
 if(val > 0 && !positive.current) {
 //Flip to positive
 positive.current = true;
 stateIndex == stateArray.length - 1 ? setStateIndex(0) : setStateIndex((index) => {
  return index + 1;
})
console.log("positive");
 }

 if(val < 0 && positive.current) {
//Flip to negative
positive.current = false;
stateIndex == stateArray.length - 1 ? setStateIndex(0) : setStateIndex((index) => {
  return index + 1;
})
console.log("Negative");
 }

});

 
return (
  <group  ref={group}  rotation={[0, 0, 0]}>
  
    <mesh position={[0, 0, 0]}>
        <circleGeometry args={[0.25, 200, 20]}/>
        <meshBasicMaterial attach="material" color="#008000" />
    </mesh>
    <mesh>
    <ringBufferGeometry args={[0.6, 0.8, 128, 1, 1.3, 1.9]} />
    <meshBasicMaterial attach="material" color="#008000" visible={stateArray[stateIndex][0] == 1}/>
    </mesh>
    <mesh>
    <ringBufferGeometry args={[1, 1.2, 128, 1, 1.3, 1.9]}/>
    <meshBasicMaterial attach="material" color="#008000"  visible={stateArray[stateIndex][1] == 1} />
    </mesh>
    <mesh>
    <ringBufferGeometry args={[1.4, 1.6, 128, 1, 1.3, 1.9]} />
    <meshBasicMaterial attach="material" color="#008000" visible={stateArray[stateIndex][2] == 1}/>
    </mesh>
   </group>
)

}



it seems very complex, i can’t even read it. something so simple shouldn’t cost so much code.

if animating the whole thing is ok, share the material

function OnlineSymbol({ speed = 1, ...props }) {
  const [material] = useState(() => new THREE.MeshBasicMaterial({ transparent: true, color: "#008000" }))
  useFrame((state) => {
    material.opacity = (1 + Math.sin(state.clock.elapsedTime * speed)) / 2
  })
  return (
    <group>
      <mesh material={material}>
        <circleGeometry args={[0.25, 200, 20]}/>
     </mesh>
      <mesh material={material}>
        ...
      <mesh material={material}>
        ...

if not, just change the delay for each segment. you don’t need any of the code you have up there. also the materials, just <meshBasicMaterial transparent color="#008000" /> will do.

useFrame((state) => {
  const t = state.clock.elapsedTime
  group.children.forEach((child, index) => {
    const speed = 0.5 + index * 0.1
    child.material.opacity = (1 + Math.sin(state.clock.elapsedTime * speed)) / 2
  })
})

you should never call a “setState” inside useFrame btw. this is a loop, don’t involve react.

1 Like

Your approach is fine, when there are not so many such things in the scene.
I would go with instanced mesh and modified material, drawing those signals in shaders:

1 Like

Thank you this is very helpful, that makes a lot of sense compared to what I was doing before

Interesting. I will read into your suggestion! I haven’t used shaders yet

btw, if you want to animate like in @prisoner849’s example, just slap a Math.round in front of the formula that calculates opacity and you have it flip between visible/invisible instead of smoothly animating it.

2 Likes