Animating bufferAttribute with react-three-fiber and react-spring - reactjs

I have simple Points mesh with custom shader and buffer geometry.
The geometry has position, size and color attributes.
On pointer hover, the hovered vertex turns into red color.
So far so good.
Now I would like to animate the change of color of the hovered vertex.
Here is the code snippet for the Points mesh:
const Points = (
props = { hoveredIndex: null, initialCameraZ: 0, array: [], sizes: [] }
) => {
const [_, setHover] = useState(false);
const vertices = new Float32Array(props.array);
const sizes = new Float32Array(props.sizes);
const _colors = genColors(props.sizes.length); // returns [] of numbers.
const uniforms = useMemo(() => {
return {
time: { type: "f", value: 0.0 },
cameraZ: { type: "f", value: props.initialCameraZ }
};
}, [props.initialCameraZ]);
// trying to use react-spring here
const [animProps, setAnimProps] = useSpring(() => ({
colors: _colors
}));
const geometry = useUpdate(
(geo) => {
if (props.hoveredIndex !== null) {
const i = props.hoveredIndex * 3;
const cols = [..._colors];
cols[i] = 1.0;
cols[i + 1] = 0.0;
cols[i + 2] = 0.0;
geo.setAttribute(
"color",
new THREE.BufferAttribute(new Float32Array(cols), 3)
);
setAnimProps({ colors: cols });
} else {
geo.setAttribute(
"color",
new THREE.BufferAttribute(new Float32Array(_colors), 3)
);
setAnimProps({ colors: _colors });
}
},
[props.hoveredIndex]
);
return (
<a.points
onPointerOver={(e) => setHover(true)}
onPointerOut={(e) => setHover(false)}
>
<a.bufferGeometry attach="geometry" ref={geometry}>
<bufferAttribute
attachObject={["attributes", "position"]}
count={vertices.length / 3}
array={vertices}
itemSize={3}
/>
<bufferAttribute
attachObject={["attributes", "size"]}
count={sizes.length}
array={sizes}
itemSize={1}
/>
<a.bufferAttribute
attachObject={["attributes", "color"]}
count={_colors.length}
array={new Float32Array(_colors)}
// array={animProps.colors} // this does not work
itemSize={3}
/>
</a.bufferGeometry>
<shaderMaterial
attach="material"
uniforms={uniforms}
vertexShader={PointsShader.vertexShader}
fragmentShader={PointsShader.fragmentShader}
vertexColors={true}
/>
</a.points>
);
};
Full code and example is available on codesandbox
When I try to use animProps.colors for color in bufferAttribute it fails to change the color.
What am i doing wrong? How to make it right?
I know I could create start and target color attributes, pass them to the shader and interpolate there but that would beat the purpose of using react-three-fiber.
Is there a way animating buffer attributes in react-three-fiber?

This likely isn't working as expected because changes made to buffer attributes need to be explicitly flagged for sending to the GPU. I suppose that react-spring does not do this out of the box.
Here's a vanilla example, note the usage of needsUpdate:
import { useFrame } from '#react-three/fiber'
import React, { useRef } from 'react'
import { BufferAttribute, DoubleSide } from 'three'
const positions = new Float32Array([
1, 0, 0,
0, 1, 0,
-1, 0, 0,
0, -1, 0
])
const indices = new Uint16Array([
0, 1, 3,
2, 3, 1,
])
const Comp = () => {
const positionsRef = useRef<BufferAttribute>(null)
useFrame(() => {
const x = 1 + Math.sin(performance.now() * 0.01) * 0.5
positionsRef.current.array[0] = x
positionsRef.current.needsUpdate = true
})
return <mesh>
<bufferGeometry>
<bufferAttribute
ref={positionsRef}
attach='attributes-position'
array={positions}
count={positions.length / 3}
itemSize={3}
/>
<bufferAttribute
attach="index"
array={indices}
count={indices.length}
itemSize={1}
/>
</bufferGeometry>
<meshBasicMaterial
color={[0, 1, 1]}
side={DoubleSide}
/>
</mesh>
}
For further reading check out this tutorial.

Related

What makes the component re-render?

I have trouble finding the reason for making the component re-render when scrolling down.
I've tried to build scenes with particles using shader and FBO techniques.
I used useRef and useMemo to prevent re-rendering instead of useState. I tried to load 3d models in this component using useGLTF, but not worked.
Re-render happens when scrolling down.
What seems to be the problem?
Here is my code.
let opacity;
const size = 80;
const FboParticles = ({ models }) => {
const { viewport } = useThree();
const scroll = useScroll();
/**
* Particles options
*/
// This reference gives us direct access to our points
const points = useRef();
const simulationMaterialRef = useRef();
// Create a camera and a scene for our FBO
// Create a simple square geometry with custom uv and positions attributes
const [scene, camera, positions, uvs] = useMemo(() => {
return [
new Scene(),
new OrthographicCamera(-1, 1, 1, -1, 1 / Math.pow(2, 53), 1),
new Float32Array([
-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0,
]),
new Float32Array([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0]),
];
}, []);
// Create our FBO render target
const renderTarget = useFBO(size, size, {
minFilter: NearestFilter,
magFilter: NearestFilter,
format: RGBAFormat,
stencilBuffer: false,
type: FloatType,
});
// Generate a "buffer" of vertex of size "size" with normalized coordinates
const particlesPosition = useMemo(() => {
const length = size * size;
const particles = new Float32Array(length * 3);
for (let i = 0; i < length; i++) {
let i3 = i * 3;
particles[i3 + 0] = (i % size) / size;
particles[i3 + 1] = i / size / size;
}
return particles;
}, []);
const uniforms = useMemo(
() => ({
uPositions: {
value: null,
},
uMobile: {
value: 1,
},
uPixelRatio: {
value: Math.min(window.devicePixelRatio, 2),
},
vColor: {
value: 0,
},
defaultTime: {
value: 0,
},
uOpacity: {
value: 0,
},
uColorTrigger: {
value: 0,
},
}),
[]
);
useLayoutEffect(() => {
if (viewport.width < 4.8) {
points.current.material.uniforms.uMobile.value = 0.1;
}
}, []);
// FBO useFrame
useFrame(({ gl, clock }) => {
gl.setRenderTarget(renderTarget);
gl.clear();
gl.render(scene, camera);
gl.setRenderTarget(null);
points.current.material.uniforms.uPositions.value = renderTarget.texture;
points.current.material.uniforms.defaultTime.value = clock.elapsedTime;
simulationMaterialRef.current.uniforms.uTime.value = clock.elapsedTime;
});
const [scales, colors] = useMemo(() => {
const length = size * size * 3;
const color = new Color();
const sca = [];
const cols = [];
let q = ["white", "white", 0x2675ad, 0x0b5394, 0x0b9490];
const range = viewport.width < 4.8 ? 20 : 40;
for (let i = 0; i < length; i++) {
const i3 = i * 3;
// color
color.set(q[randInt(0, 4)]);
cols[i3 + 0] = color.r;
cols[i3 + 1] = color.g;
cols[i3 + 2] = color.b;
// particles scale
sca[i] = Math.random() * range;
}
return [new Float32Array(sca), new Float32Array(cols)];
}, []);
/**
* mouse event
*/
const hoveredRef = useRef();
const planeGeo = useMemo(() => {
return new PlaneGeometry(viewport.width, viewport.height, 1, 1);
}, []);
/**
* scroll
*/
// scroll animation
useFrame(({ mouse }) => {
const x = (mouse.x * viewport.width) / 2;
const y = (mouse.y * viewport.height) / 2;
if (viewport.width > 6.7) {
if (hoveredRef.current) {
simulationMaterialRef.current.uniforms.uMouse.value = new Vector2(x, y);
simulationMaterialRef.current.uniforms.uMouseTrigger.value = 1;
} else simulationMaterialRef.current.uniforms.uMouseTrigger.value = 0;
}
const aRange = scroll.range(0.0, 1 / 12);
const bRange = scroll.range(0.7 / 12, 1 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerA.value = aRange;
const cRange = scroll.range(1.7 / 12, 1 / 12);
const dRange = scroll.range(3.6 / 12, 1 / 12);
const c = scroll.visible(1.7 / 12, 1 / 12);
const d = scroll.visible(2.7 / 12, 1.9 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerB.value = cRange;
const e = scroll.visible(4.8 / 12, 2 / 12);
const eRange = scroll.range(4.8 / 12, 1 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerC.value = eRange;
const f = scroll.visible(6.8 / 12, 1 / 12);
const g = scroll.visible(7.6 / 12, 1 / 12);
const fRange = scroll.range(6.8 / 12, 1 / 12);
const gRange = scroll.range(7.6 / 12, 1 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerD.value = fRange;
simulationMaterialRef.current.uniforms.scrollTriggerE.value = gRange;
const h = scroll.visible(9.6 / 12, 2.4 / 12);
const hRange = scroll.range(9.6 / 12, 1 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerF.value = hRange;
points.current.material.uniforms.uColorTrigger.value = hRange;
const iRange = scroll.range(10.6 / 12, 1.4 / 12);
simulationMaterialRef.current.uniforms.scrollTriggerG.value = iRange;
// opacity
opacity = 1 - bRange;
c && (opacity = cRange);
d && (opacity = 1 - dRange);
e && (opacity = eRange);
f && (opacity = 1 - fRange);
g && (opacity = fRange);
h && (opacity = hRange);
points.current.material.uniforms.uOpacity.value = opacity;
});
return (
<>
<mesh
position={[0, 0, 0]}
onPointerOver={(e) => (
e.stopPropagation(), (hoveredRef.current = true)
)}
onPointerOut={(e) => (hoveredRef.current = false)}
visible={false}
geometry={planeGeo}
/>
{/* Render off-screen our simulation material and square geometry */}
{createPortal(
<mesh>
<simulationMaterial
ref={simulationMaterialRef}
args={[size, viewport, models]}
/>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-uv"
count={uvs.length / 2}
array={uvs}
itemSize={2}
/>
</bufferGeometry>
</mesh>,
scene
)}
<points ref={points}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={particlesPosition.length / 3}
array={particlesPosition}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={colors.length / 3}
array={colors}
itemSize={3}
/>
<bufferAttribute
attach="attributes-aScale"
count={scales.length}
array={scales}
itemSize={1}
/>
</bufferGeometry>
<shaderMaterial
blending={AdditiveBlending}
depthWrite={false}
// transparent={true}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={uniforms}
/>
</points>
</>
);
};
export default FboParticles;
You just need use states, if you want re-render your component use states like this:
const App = () => {
const [,forceUpdate] = useState({update: true});
return (
<button onClick{() => forceUpdate({update:true})}>re render this component</button>
)
}
use this forceUpdate anywhere, but focus it should take object if you want to write int on that you should set different value so yes use random int on that
forceUpdate(Math.random())

Differences between vanilla three js vs react-three-fiber code, not able to figure out what is different

Following code works just fine and renders the geometry with all the attributes set i.e. color, uvs, normal and vertices.
import * as THREE from "three";
const { useEffect } = require("react");
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 150;
const aspect = 2;
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 5;
const scene = new THREE.Scene();
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
const positions = [...somepoints];
const normals = [...somepoints];
const uvs = [...somepoints];
const colours = [...somepoints];
const geometry = new THREE.BufferGeometry();
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
const colorComp = 4;
geometry.setAttribute(
'position',
new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
geometry.setAttribute(
'normal',
new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
geometry.setAttribute(
'uv',
new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
geometry.setAttribute(
'color',
new THREE.BufferAttribute(new Float32Array(colours), colorComp));
const material = new THREE.MeshPhongMaterial({color: 0x88FF88});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
function render(time) {
renderer.render(scene, camera);
}
requestAnimationFrame(render);
}
const App = () => {
useEffect(() => {
main();
});
return (
<canvas id="c"></canvas>
)
}
export default App;
However if I try to run it using react components it doesn't show anything and I get the following error 235 [.WebGL-0x38089eaa00] GL_INVALID_OPERATION: Vertex buffer is not big enough for the draw call:
I think that the problem might be with requestAnimationFrame but in above code it doesn't even perform any computation on the scene's object. It just calls renderer.render which react three fiber takes care off, right?
import { Canvas } from '#react-three/fiber'
import * as THREE from "three";
const App = () => {
const positions = [...somepoints];
const normals = [...somepoints];
const uvs = [...somepoints];
const colours = [...somepoints];
return (
<Canvas>
<perspectiveCamera
fov={150}
aspect={2}
position={[0,0,5]}
near={0.1}
far={100}
/>
<directionalLight
color={0xFFFFFF}
intensity={1.5}
position={[-1, 2, 4]}
/>
<mesh>
<bufferGeometry attach="geometry">
<bufferAttribute
attachObject={["attributes", "position"]}
count={positions.length / 3}
itemSize={3}
array={positions}
normalized={true}
/>
<bufferAttribute
attachObject={["attributes", "normal"]}
count={normals.length / 3}
itemSize={3}
array={normals}
normalized={true}
/>
<bufferAttribute
attachObject={["attributes", "color"]}
count={colours.length / 4}
itemSize={4}
array={colours}
normalized={true}
/>
<bufferAttribute
attachObject={["attributes", "uv"]}
count={uvs.length / 2}
itemSize={2}
array={uvs}
normalized={true}
/>
</bufferGeometry>
<meshPhongMaterial attach="material" color={0x88FF88} />
</mesh>
</Canvas>
)
};
export default App;
Any Help will be greatly appreciated! Thank you!
EDIT:
The issue here was with the react code. As buffer attributes requires all the arrays to be new Float32Array([...somepoints]). After changing positions, uvs, normal and colours to float32 array type everything worked fine.

meshBasicObject color change on condition react-fiber-three

** Looking for matched item to different color**
I'm currently trying to get color changes of mesh if condition match else keep it default color. but somehow it keeps same color for all mesh object.
I did try to add simple logic with Boolean to toggle for color but that also doesn't work. any advice will be accepted.
function generateMatrix(
mesh: MutableRefObject<InstancedMesh>,
getItemForGenerate: GetItemForGenerateData,
userDefineditems: string[],
xRotationValue: number,
yRotationValue: number
) {
if (getItemForGenerate?.items) {
const tempObject = new THREE.Object3D();
getItemForGenerate.items.forEach((items) => {
items.entities.forEach((item, index) => {
tempObject.position.set(
item.xMapPosition
item.yMapPosition
item.zMapPosition
);
//change color of the object if userdefined item matches the any of the items provided
if (userDefineditems?.includes(item.itemName)) {
tempObject.scale.set(item.length, item.depth , item.height );
// how to change color here if user defined lcoations matches any item on the Matrix
}
tempObject.scale.set(item.length , item.depth , item.height );
tempObject.updateMatrix();
mesh.current.setMatrixAt(index, tempObject.matrix);
});
});
mesh.current.rotation.x = xRotationValue;
mesh.current.rotation.y = yRotationValue;
mesh.current.instanceMatrix.needsUpdate = true;
return mesh;
}
}
const itemMatrix = (items: itemMatrixProps) => {
let mesh = useRef();
const { xRotationValue, yRotationValue, getItemForGenerate, userDefineditems } = items;
const [positionDetailsData, setPositionDetailsData] = useState<PositionDetails>(null);
useEffect(() => {
generateMatrix(mesh, getItemForGenerate, userDefineditems, xRotationValue, yRotationValue);
}, [getItemForGenerate, userDefineditems, xRotationValue, yRotationValue]);
useEffect(() => {
setPositionDetailsData(getPostion());
}, [getItemForGenerate]);
const getPostion = () => {
let xDivisor = 1;
let yDivisor = 1;
let zDivisor = 1;
const positionDetails: PositionDetails = {
xPosition: 0,
yPosition: 0,
zPosition: 0
};
let meshCount = getItemForGenerate?.count;
({ xDivisor, yDivisor, zDivisor } = generateMaxDivisors(getItemForGenerate, xDivisor, yDivisor, zDivisor));
const = xDivisor > yDivisor ? xDivisor : yDivisor;
const yPosition = yDivisor / (yDivisor + yDivisor * 0.3);
const xPosition = / (xDivisor + yDivisor);
positionDetails.xPosition = xPosition;
positionDetails.yPosition = yPosition;
positionDetails.zPosition = 0;
return positionDetails;
};
return (
<instancedMesh
ref={mesh}
args={[null, null, getItemForGenerate?.count]}
position={[-positionDetailsData?.xPosition, -positionDetailsData?.yPosition, positionDetailsData?.zPosition]}
>
<boxGeometry attach="geometry" args={[0.9, 0.9, 0.9]} />
<meshNormalMaterial />
<meshBasicMaterial color={'#047ABC'} />
</instancedMesh>
);
}
I try with THREE.Color but didn't work.

setState is delayed by one state

Don't know what I am doing wrong here, maybe it is a rookie mistake.
I am trying to recreate chess.
My state is delayed by one.. i.e. when I click on square to show possible moves of piece, the possible moves will appear but only after my second click, they will show on the right spot. And for the 3rd click the state from 2nd click will appear on the right spot and so on.
What am I doing wrong here?
I am sending few code examples from my next.js app.
This is my Square component
export default function Square({
x,
y,
color,
content,
contentClr,
handleClick,
}) {
const col = color == 'light' ? '#F3D9DC' : '#744253';
return (
<div
className='square'
x={x}
y={y}
style={{ backgroundColor: col }}
onClick={() => handleClick(x, y)}
>
{content != '' && (
<h1 style={{ color: contentClr == 'light' ? 'white' : 'black' }}>
{content}
</h1>
)}
</div>
);
}
This is my Board component
import { useState, useEffect } from 'react';
import Square from './square';
import styles from '../styles/Board.module.css';
import rookMoves from './possibleMoves/rookMoves';
export default function Board({ initialBoard, lightTurn, toggleTurn }) {
let size = 8;
const [board, setBoard] = useState(initialBoard);
const [possibleMoves, setPossibleMoves] = useState([]);
//possibleMoves .. each possible move is represented as x, y coordinates.
useEffect(() => {
console.log('board state changed');
}, [board]);
useEffect(() => {
console.log('possible moves state changed');
console.log(possibleMoves);
renderPossibleMoves();
}, [possibleMoves]);
const playerClickOnPiece = (x, y) => {
let pos = getPossibleMoves(x, y, rookMoves, [left, right, up, down]);
setPossibleMoves(pos);
};
//where to is object that has functions for directions in it
const getPossibleMoves = (x, y, movePiece, whereTo) => {
let posMoves = [];
for (let i = 0; i < whereTo.length; i++) {
posMoves = posMoves.concat(movePiece(board, x, y, whereTo[i]));
}
console.log(posMoves);
return posMoves;
};
const renderPossibleMoves = () => {
console.log('board from renderPossibleMoves ', board);
let newBoard = board;
for (let i = 0; i < possibleMoves.length; i++) {
newBoard[possibleMoves[i].y][possibleMoves[i].x] = 10;
}
setBoard(newBoard);
};
const squareColor = (x, y) => {
return (x % 2 == 0 && y % 2 == 0) || (x % 2 == 1 && y % 2 == 1)
? 'light'
: 'dark';
};
const handleClick = (x, y) => {
if (lightTurn && board[y][x] > 0) {
console.log(
`show me possible moves of light ${board[y][x]} on ${x} ${y}`
);
playerClickOnPiece(x, y);
} else if (!lightTurn && board[y][x] < 0) {
console.log(`show me possible moves of dark ${board[y][x]} on ${x} ${y}`);
playerClickOnPiece(x, y);
} else if (board[y][x] == 'Pm') {
playerMove();
}
};
return (
<>
<div className='board'>
{board.map((row, y) => {
return (
<div className='row' key={y}>
{row.map((square, x) => {
return (
<Square
x={x}
y={y}
key={x}
color={squareColor(x, y)}
content={square}
contentClr={square > 0 ? 'light' : 'dark'}
handleClick={handleClick}
playerMove={toggleTurn}
/>
);
})}
</div>
);
})}
</div>
</>
);
}
import { useState } from 'react';
import Board from './board';
let initialBoard = [
[-4, -3, -2, -5, -6, -2, -3, -4],
[-1, -1, -1, -1, -1, -1, -1, -1],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1],
[4, 3, 2, 5, 6, 2, 3, 4],
];
export default function Game() {
const [lightTurn, setLightTurn] = useState(true);
const toggleTurn = () => {
setLightTurn(!lightTurn);
};
return (
<Board
initialBoard={initialBoard}
lightTurn={lightTurn}
toggleTurn={toggleTurn}
/>
);
}
I am sending an example that shows only possible moves with rook but if u click on any light piece.
#juliomalves's comment helped!
I was mutating state directly in renderPossibleMoves. Spread operator solved the issue.
from
let newBoard = board;
to
let newBoard = [...board];

Can i call a specific function inside `useEffect` in React?

I have a canvas component in react. I am using useEffect to get the canvas element. So i have defined all needed functions in useEffect, as you can see below
import { useEffect, useRef } from "react"
import * as blobs2Animate from "blobs/v2/animate"
export const CornerExpand = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const ctx = canvas.getContext("2d")
const animation = blobs2Animate.canvasPath()
const width = canvas.clientWidth * window.devicePixelRatio
const height = canvas.clientHeight * window.devicePixelRatio
canvas.width = width
canvas.height = height
const renderAnimation = () => {
if (!ctx) return
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "#E0F2FE"
ctx.fill(animation.renderFrame())
requestAnimationFrame(renderAnimation)
}
requestAnimationFrame(renderAnimation)
const size = Math.min(width, height) * 1
const defaultOptions = () => ({
blobOptions: {
seed: Math.random(),
extraPoints: 36,
randomness: 0.7,
size,
},
canvasOptions: {
offsetX: -size / 2.2,
offsetY: -size / 2.2,
},
})
const loopAnimation = () => {
animation.transition({
duration: 4000,
timingFunction: "ease",
callback: loopAnimation,
...defaultOptions(),
})
}
animation.transition({
duration: 0,
callback: loopAnimation,
...defaultOptions(),
})
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
}, [canvasRef])
return (
<div>
<canvas className="absolute top-0 left-0 h-full w-full" ref={canvasRef} />
</div>
)
}
export default CornerExpand
Everything works as well, but now I have a problem. I want to execute the fullscreen() function when a button is clicked in the parent component. Since I have defined the function in useEffect, I can't call it directly, isn't it? What can I do to solve this?
You can do it like this.
export const CornerExpand = React.forwardRef((props, ref) => {
//....
{
//...
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
ref.current = fullscreen;
}, [canvasRef]);
You can wrap this comp with React.forwardRef. And call it from parent.
<CornerExpand ref={fullscreenCallbackRef} />
And then call like this
fullscreenCallbackRef.current()

Resources