multi canvas issue
the ball.jsx file renders 13 canvas which bad for android users how about this code for ball.jsx import React, { Suspense } from "react"; import { Canvas } from "@react-three/fiber"; import { Decal, Float, OrbitControls, Preload, useTexture, } from "@react-three/drei";
import CanvasLoader from "../Loader";
const Ball = ({ imgUrl, position, ballScale = 2.75 }) => { const [decal] = useTexture([imgUrl]);
return ( <directionalLight position={[0, 0, 0.05]} /> <icosahedronGeometry args={[1, 1]} /> <Decal position={[0, 0, 1]} rotation={[2 * Math.PI, 0, 6.25]} scale={1} map={decal} flatShading /> ); };
const BallsCanvas = ({ technologies }) => { const [isMobile, setIsMobile] = React.useState(false);
React.useEffect(() => { const mediaQuery = window.matchMedia("(max-width: 500px)"); setIsMobile(mediaQuery.matches); const handleMediaQueryChange = (event) => setIsMobile(event.matches); mediaQuery.addEventListener("change", handleMediaQueryChange); return () => mediaQuery.removeEventListener("change", handleMediaQueryChange); }, []);
// Arrange balls in a grid layout const cols = isMobile ? 2 : 5; // balls per row // Slightly tighter spacing on mobile to match the smaller ball scale (1.8 vs 2.75) const spacingX = isMobile ? 5 : 7; const spacingY = isMobile ? 5 : 7;
const totalRows = Math.ceil(technologies.length / cols); const offsetY = ((totalRows - 1) * spacingY) / 2;
const positions = technologies.map((_, index) => { const row = Math.floor(index / cols); const col = index % cols; const itemsInRow = Math.min(cols, technologies.length - row * cols); const rowOffsetX = ((itemsInRow - 1) * spacingX) / 2; const x = col * spacingX - rowOffsetX; const y = -(row * spacingY) + offsetY; return [x, y, 0]; });
// Calculate camera distance based on grid size const fov = 25; const gridWidth = (cols - 1) * spacingX; const gridHeight = (totalRows - 1) * spacingY; // On mobile: derive cameraZ from FOV math so the FULL grid height fits in view. // Formula: cameraZ = (gridHeight/2) / tan(fov/2) with a small margin buffer. const cameraZ = isMobile ? ((gridHeight / 2) / Math.tan((fov / 2) * (Math.PI / 180))) * 1.15 : Math.max(gridWidth, gridHeight) * 1.1 + 10;
return (
<Canvas
frameloop='demand'
dpr={[1, 2]}
gl={{ preserveDrawingBuffer: true }}
camera={{ position: [0, 0, cameraZ], fov: fov }}
style={{ width: '100%', height: ${totalRows * (isMobile ? 140 : 150)}px }}
>
<Suspense fallback={}>
<directionalLight position={[0, 0, 5]} />
{technologies.map((tech, index) => (
<Ball
key={tech.name}
imgUrl={tech.icon}
position={positions[index]}
ballScale={isMobile ? 1.7 : 2.75}
/>
))}
<Preload all />
</Canvas>
); };
// Keep backward-compatible single ball export const BallCanvas = ({ icon }) => { return ( <Canvas frameloop='demand' dpr={[1, 2]} gl={{ preserveDrawingBuffer: true }} > <Suspense fallback={}> <Ball imgUrl={icon} position={[0, 0, 0]} />
<Preload all />
</Canvas>
); };
export default BallCanvas; export { BallsCanvas }; and after this you just need to update the tech.jsx accordingly
Source: adrianhajdin/project_3D_developer_portfolio