I'm attempting to use a design built with React-Three-Fiber as the background of my application.
The canvas appears to show and run the animation on chrome and firefox dev tools in the desktop browsers but is failing to show on mobile. I've tried firefox, chrome, and safari using my iPhone and a few other phones but still no prevail.
It is a fairly basic animation so it shouldn't be too much for the browser to handle.
I've included the proper meta tag in the index.html, found within the documentation.
The code below that is contained within its own component.
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
import * as THREE from "three";
import React, { Suspense, useMemo, useRef } from "react";
import styled from "styled-components";
import { Canvas, useFrame } from "#react-three/fiber";
import vertexShader from "./Shaders/VertexShader";
import fragmentShader from "./Shaders/FragmentShader";
export default function Animation() {
// universal points ref
const points = useRef();
const radius = 2;
const Planet = () => {
const distance = 2;
const count = 20000;
const particlesPosition = useMemo(() => {
// create the float32 array to store the point positions.
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = THREE.MathUtils.randFloatSpread(360);
const phi = THREE.MathUtils.randFloatSpread(360);
// Generate random values for x, y, z
let x = distance * Math.sin(theta) * Math.cos(phi);
let y = distance * Math.sin(theta) * Math.sin(phi);
let z = distance * Math.cos(theta);
// add each value to the array
positions.set([x, y, z], i * 3);
}
return positions;
}, [count]);
const uniforms = useMemo(
() => ({
uTime: {
value: 0.8,
},
uRadius: {
value: radius,
},
}),
[]
);
useFrame((state) => {
const { clock } = state;
// adding 100 starts the animation at 100s ahead of animation start time.
points.current.material.uniforms.uTime.value = clock.elapsedTime + 110;
});
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute // bufferAttribute will allow you to set the position attribute of the geometry.
attach="attributes-position" // this will allow the data being fed to bufferAttribute to be accessable under the position attribute.
count={particlesPosition.length / 3} //number of particles
array={particlesPosition}
itemSize={3} // number of values from the particlesPosition array associated with one item/vertex. It is set to 3 becuase position is based on X, Y, Z
/>
</bufferGeometry>
<shaderMaterial
depthWrite={false}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={uniforms}
/>
</points>
);
};
return (
//Canvas takes place of all the boilerplate to get three.js running with react.
<Wrapper>
<Canvas className="canvas">
<ambientLight intensity={0.5} />
<Suspense>
<Planet />
</Suspense>
</Canvas>
</Wrapper>
);
}
const Wrapper = styled.div`
position: relative;
background: inherit;
height: 100vh;
width: 100vw;
`;
Related
I have a 320x40 .png file that I am trying to play each animation from inside of my canvas, for a total of 10 frames. Each individual animation frame is a 32x40 section of the png file. I'll cut to the chase and show the png animation code for the character:
export default function SpriteAni(ctx, charAnim, currentFrame) {
const charSprite = new Image();
charSprite.src = `./images/char_${charAnim}.png`
const posx = 320;
const posy = 200;
const sheet_width = 32;
const sheet_height = 40;
const sprite_width = sheet_width * 1.5;
const sprite_height = sheet_height * 1.5;
ctx.drawImage(charSprite, currentFrame * sheet_width, 0, sheet_width, sheet_height, posx, posy, sprite_width * 1.5, sprite_height * 1.5);
}
Here's the relevant code for drawing this to the screen inside of my main Canvas.jsx file
import { useEffect, useRef, useState } from "react";
import SpriteAni from '../SpriteAni.jsx';
// Canvas and game logic for display
const Canvas = ({ width, height }) => {
const canvasRef = useRef(null);
const requestIdRef = useRef(null);
let frame = 0; // frames canvas is drawn to screen
const [currentFrame, setCurrentFrame] = useState(0); // used to determine current frame for current Wario animation
const [charAnim, setCharAnim] = useState('walk')
// Char animation frame counts
const walk_frames = 9;
const drawFrame = () => {
const ctx = canvasRef.current.getContext('2d');
/// clear screen each frame
ctx.clearRect(0,0,constants.CANVAS_WIDTH,constants.CANVAS_HEIGHT);
// draw graphics
handleBackground(ctx);
SpriteAni(ctx, charAnim, currentFrame);
if (currentFrame >= walk_frames) setCurrentFrame(0);
else setCurrentFrame(currentFrame + 1);
}
const tick = () => {
if (!canvasRef.current) return;
drawFrame();
frame++;
requestAnimationFrame(tick)
}
useEffect(() => {
requestIdRef.current = requestAnimationFrame(tick)
return () => {
cancelAnimationFrame(requestIdRef.current)
};
}, [])
return (
<canvas
width={width}
height={height}
style={canvasStyle}
ref={canvasRef}
/>
)
}
export default Canvas
const canvasStyle = {
border: "1px solid black",
position: "absolute",
}
What I'm especially confused about is I can delete all of the "if (currentFrame >= ${charAnim}_frames)..." code underneath SpriteAni in the drawFrame function and nothing changes. It seems to all be acting independently of any of that and just races through all 10 frames before vanishing off of the screen within one second. I don't even understand how that's possible since the only function inside of SpriteAni is a drawImage of which the only variable that should be updating each frame is currentFrame.
How is it animating anything independently like that? I'm just baffled what's going on here! Any help greatly appreciated!
I have an iphone model and i want to load it to my react project. I use primitive tag from react three fiber R3F which is not a real object that we will be able to see in the scene, but it’s a container supported by R3F. I want to display 2 iphone that'w why i use primitive tag twice for the same model but the first primitive tag doesn't load the model and only the second one. Is is because i only can use it once for the same model? Should i copy the model and
import { useGLTF, OrbitControls, useHelper, useScroll, Html } from '#react-three/drei'
import * as THREE from 'three'
import { useFrame } from '#react-three/fiber'
import { useRef, forwardRef } from 'react'
export default function App() {
const model = useGLTF('/iphone_14_pro_max.glb');
const model2 = useGLTF('/iphone_14_pro_max.glb');
const scroll = useScroll()
const iphone = useRef()
const first = useRef()
useFrame((state, delta) => {
//kapan pake range kapan pake visible?
const r1 = scroll.range(0 / 4, 1 / 4)
const r2 = scroll.range(1 / 4, 1 / 4)
const r3 = scroll.range(1 / 4, 1 / 4)
iphone.current.rotation.y = (Math.PI * 0.67) + r1 + r2 + r3 * 3
})
return(
<>
<directionalLight position={ [ 1, 2, 3 ] } intensity={ 1.5 } />
<ambientLight intensity={ 0.5 } />
<primitive ref={iphone} object={model.scene} scale={20} position={ [ 0, -1.5, 0] }>
<Tagline ref={first} left="PRO" right="BEYOND"></Tagline>
</primitive>
<primitive object={model2.scene} scale={15} position={ [ 1, 1, 1] } rotation-y={-45}/>
</>
)
}
const Tagline = forwardRef(({ left, right, ...props }, ref) => {
return (
<Html occlude ref={ref} className="homepage" center {...props}>
<h1>{left}</h1>
<h1>{right}</h1>
</Html>
)
})
I already have an answer that primitive tag can only load one model for each tag. If you want to load the same model then you need to copy it with different name and load it agaian.
I am trying to use three for the first time to level up my webpages a little so I'm following a guide on YouTube here so you can see what I'm trying to accomplish. https://www.youtube.com/watch?v=YK1Sw_hnm58 I'm at around 1:12:20. the only difference is I'm using react instead of plain JavaScript so I'm struggling to convert it since I've never used it before.
/* eslint-disable no-unused-vars */
import React, { useRef } from "react";
import { useFrame, useLoader } from "#react-three/fiber";
import { OrbitControls } from "#react-three/drei";
import * as THREE from "three";
import { useEffect } from "react";
import { useState } from "react";
const PlaneMesh = (params) => {
const paneRef = useRef();
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(false)
const { array } = paneRef.current.geometry.attributes.position
for (let i = 0; i < array.length; i += 3){
array[i + 2] = array[i + 2] + Math.random()
}
}, [loading])
const [MousePosition, setMousePosition] = useState({
left: 0,
top: 0
})
useFrame( state => {
let mouseX = state.mouse.x;
let mouseY = state.mouse.y;
console.log(mouseX)
});
return (
<>
{/* <ambientLight intensity={1} /> */}
<directionalLight color={0xFFFFFF} position={[0, 0, 1]} intensity={1} />
{/* <pointLight color="#FFFFFF" position={[0, 0, 2]} intensity={1} /> */}
<mesh ref={paneRef} position={[0,0, -5]}>
<planeGeometry args={[100, 100, 50, 50]} />
<meshPhongMaterial
color={0x000044}
side={THREE.DoubleSide}
flatShading={true}
/>
{/* <OrbitControls
enableZoom={true}
enablePan={true}
enableRotate={true}
zoomSpeed={0.6}
panSpeed={0.5}
rotateSpeed={0.4}
/> */}
</mesh>
</>
);
};
export default PlaneMesh;
This is my full file right now. I cant work out how to add the mouse move event listener and have it work as any method I have tried either only runs once or only runs when I click on the page rather than when I move the mouse. This is the closest I have been able to get it as it constantly prints the value but the mouse coordinates don't update unless I click. Any advice is greatly appreciated.
I ended up fixing it by adding a window event listener and subtracting the bounding box to get the mouse position over the element.
const section = document.getElementById("planeSection");
section.addEventListener("mousemove", (event) => {
var rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / window.innerHeight) * 2 + 1;
});
I am trying learn how to use three js with react.For this purpose I am using #react-three/fiber and #react-three/drei.
This project by chriscourses was what I used to learn three js and I hope to use it to learn using threejs with react.
The below code should show boxes sprouting out of the cordinates given however currently they are all skewed towards one direction. Using lookat should solve the problem, however I do not know how to use lookat in #react-three/fiber / #react-three/drei.
import React, { FC, useRef } from 'react';
import { Box as NativeBox } from '#react-three/drei'
import { Vector3 } from 'three';
import { useFrame } from '#react-three/fiber';
type Props = {
country: any,
}
const lookAtCubePosition = new Vector3(0, 0, 0)
const Box: FC<Props> = (props) => {
const mesh = useRef()
const { country } = props;
const scale = country.population / 1000000000
const lat = country.latlng[0]
const lng = country.latlng[1]
const zScale = 0.8 * scale
const latitude = (lat / 180) * Math.PI
const longitude = (lng / 180) * Math.PI
const radius = 5
const x = radius * Math.cos(latitude) * Math.sin(longitude)
const y = radius * Math.sin(latitude)
const z = radius * Math.cos(latitude) * Math.cos(longitude)
console.log('box');
const lookAtFunc = (lookAt: Vector3) => {
lookAt.x = 0;
lookAt.y = 0;
lookAt.z = 0;
}
useFrame((state) => {
state.camera.lookAt(lookAtCubePosition)
})
return <NativeBox
args={[
Math.max(0.1, 0.2 * scale),
Math.max(0.1, 0.2 * scale),
Math.max(zScale, 0.4 * Math.random())
]}
position={[
x, y, z
]}
lookAt={lookAtFunc}
ref={mesh}
>
<meshBasicMaterial
attach="material"
color="#3BF7FF"
opacity={0.6}
transparent={true}
/>
</NativeBox>;
};
export default Box;
If you are using OrbitControls, you must set the 'target' for OrbitControls rather than lookAt of the Camera since the lookAt of the camera is being overridden by OrbitControls
I think (the documentation is not too specific on that) that we are not meant to set lookAt as a prop but instead use it as a function. We can gain access to the active camera using the useThree hook.
In a component which is mounted as a child of Canvas do:
import { useThree } from "#react-three/fiber";
// Takes a lookAt position and adjusts the camera accordingly
const SetupComponent = (
{ lookAtPosition }: { lookAtPosition: { x: number, y: number, z: number } }
) => {
// "Hook into" camera and set the lookAt position
const { camera } = useThree();
camera.lookAt(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
// Return an empty fragment
return <></>;
}
I'm currently working on a pet web project with react and I'm using a container that's 16:9 and scaling with the current viewport. The idea is based on a pen, I've created with vanilla JS (https://codepen.io/AllBitsEqual/pen/PgMrgm) and that's already working like a charm.
function adjustScreen() {...}
adjustScreen()
[...]
const resizeHandler = function () { adjustScreen() }
window.addEventListener('resize', helpers.debounce(resizeHandler, 250, this))
I've now written a script that's sitting in my App.jsx, (un)binds itself via useEffect and calculates the current size of the viewport to adjust the container, whenever the viewport changes (throttled for performance). I'm also using media queries to adjust the size and font size of elements in the container, which is working ok but isn't much fun to work with.
I want to expand on this idea and change the font size of the HTML Element in the same function that calculated the current container size so that I can use REM to scale font-size and other elements based on my calculated root font size.
Is there a valid and future-proofed way of changing the font-size style of my Tag via ReactJS either via the style tag or style attribute?
For now I've resorted to using "document.documentElement" and "style.fontSize" to achieve what I wanted but I'm not 100% sure this is the best solution. I'll see if I can find or get a better solution before I accept my own answer as the best...
I'm using useState for the game dimensions and within the useEffect, I'm attaching the listener to resize events, which I throttle a bit for performance reasons.
const App = () => {
const game_outerDOMNode = useRef(null)
const rootElement = document.documentElement
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window
return {
width,
height,
}
}
const [gameDimensions, setGameDimensions] = useState({ width: 0, height: 0})
useEffect(() => {
function adjustScreen() {
const game_outer = game_outerDOMNode.current
const ratioHeight = 1 / 1.78
const ratioWidth = 1.78
const { width: vw, height: vh } = getWindowDimensions()
const width = (vw > vh)
? (vh * ratioWidth <= vw)
? (vh * ratioWidth)
: (vw)
: (vh * ratioHeight <= vw)
? (vh * ratioHeight)
: (vw)
const height = (vw > vh)
? (vw * ratioHeight <= vh)
? (vw * ratioHeight)
: (vh)
: (vw * ratioWidth <= vh)
? (vw * ratioWidth)
: (vh)
const longestSide = height > width ? height : width
const fontSize = longestSide/37.5 // my calculated global base size
setGameDimensions({ width, height })
rootElement.style.fontSize = `${fontSize}px`
}
const debouncedResizeHandler = debounce(200, () => {
adjustScreen()
})
adjustScreen()
window.addEventListener('resize', debouncedResizeHandler)
return () => window.removeEventListener('resize', debouncedResizeHandler)
}, [rootElement.style.fontSize])
const { width: gameWidth, height: gameHeight } = gameDimensions
return (
<div
className="game__outer"
ref={game_outerDOMNode}
style={{ width: gameWidth, height: gameHeight }}
>
<div className="game__inner">
{my actual game code}
</div>
</div>
)
}