使用可重用、自包含的组件以声明方式构建您的场景,这些组件对状态做出反应、易于交互并且可以利用 React 的生态系统。 没有任何限制,一切工作在three.js
这里将无一例外地工作。 生态完善。
npm install three @react-three/fiber
import { Canvas } from '@react-three/fiber'
export default function App() {
return (
<div id="canvas-container">
<Canvas />
</div>
)
}
Canvas 组件在幕后做了一些重要的设置工作:
请注意,Canvas 将调整大小以适合父 div,因此您可以通过更改 css 中 #canvas-container
的 width
和 height
来控制它的大小。
表示基于以三角形为polygon mesh(多边形网格)的物体的类。 同时也作为其他类的基类 ,一个Mesh
是three.js 中的一个基本对象,它用于保存在3D 空间中表示对象所需的多边形和材质。我们将使用BoxGeometry组件为几何体和MeshPhongMaterial组件为材料创建一个新网格。
为了将这些对象实际添加到我们的场景中,我们将它们安装在Canvas
组件中。
import { Canvas } from '@react-three/fiber'
export default function App() {
return (
<div id="canvas-container">
<Canvas>
<mesh>
<boxGeometry />
<meshPhongMaterial />
</mesh>
</Canvas>
</div>
)
}
上述代码会被编译为如下代码:
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
const mesh = new THREE.Mesh()
mesh.geometry = new THREE.BoxGeometry()
mesh.material = new THREE.MeshPhongMaterial()
scene.add(mesh)
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
当引入一个mesh
组件时,Fiber 会创建一个新THREE.Mesh
对象,几何和材料也是如此。然后,几何体和材料被附加到它们的父级。
集合体构造函数可以传递三个参数:宽度、长度和深度。
const geometry = new THREE.BoxGeometry(2, 2, 2)
在fiber中可以使用如下方式代替上述写法:
<boxGeometry args={[2, 2, 2]} />
**请注意,每次更改这些时,都必须重新构造对象! **
向场景中添加一些灯光
<Canvas>
...
<ambientLight intensity={0.1} /> // 环境光会均匀的照亮场景中的所有物体。
<directionalLight color="red" position={[0, 0, 5]} /> // 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。
Canvas>
参数传递和上述几何传递参数类似。
<directionalLight
// we are setting the position
position={[0, 0, 5]}
// we are setting the color
color="red"
/>
编译成
const light = new THREE.DirectionalLight()
light.position.set(0, 0, 5)
light.color.set('red')
console.log('click')}
onContextMenu={(e) => console.log('context menu')}
onDoubleClick={(e) => console.log('double click')}
onWheel={(e) => console.log('wheel spins')}
onPointerUp={(e) => console.log('up')}
onPointerDown={(e) => console.log('down')}
onPointerOver={(e) => console.log('over')}
onPointerOut={(e) => console.log('out')}
onPointerEnter={(e) => console.log('enter')}
onPointerLeave={(e) => console.log('leave')}
onPointerMove={(e) => console.log('move')}
onPointerMissed={() => console.log('missed')}
onUpdate={(self) => console.log('props have been updated')}
/>
事件名称 | 作用 |
---|---|
onClick | 点击网格做出响应 |
onContextMenu | 在纹理上右击鼠标事件 |
onDoubleClick | 在纹理上双击事件 |
onWheel | 在纹理上滚动鼠标滚轮触发的事件 |
onPointerUp | 鼠标抬起事件 |
onPointerDown | 鼠标按下事件 |
onPointerOver | 鼠标滑过 |
onPointerOut | 鼠标离开 |
onPointerEnter | 事件回调进入 |
onPointerLeave | 事件回调离开 |
onPointerMove | 鼠标移动 |
onPointerMissed | 事件消失回调 |
onUpdate | 视图更新回调 |
setActive(!active)} ref={myMesh}>
<boxGeometry />
<meshPhongMaterial color="royalblue" />
mesh>
注意: 引入资源必须放在public文件夹下,否则都会引入失败
import React, { useRef } from 'react'
import { useGLTF } from '@react-three/drei'
export default function Model(props) {
const group = useRef()
const gltf = useGLTF('/images/xin.gltf'); // public下的images
return (
<group ref={group} {...props} dispose={null}>
<primitive object={gltf.scene} />
</group>
)
}
useGLTF.preload('/images/xin.gltf'); // 预加载
import { Canvas } from "@react-three/fiber";
import { Suspense } from "react";
import { Environment } from "@react-three/drei";
import Model from "./Model";
import './App.css';
import Loader from './Loader';
export default function App() {
return (
<div className="App">
<Canvas>
<ambientLight intensity={1} />
<Suspense fallback={<Loader />}>
<Model position={[0, -4, -10]} />
<Environment preset="apartment" background /> // 背景图片
</Suspense>
</Canvas>
</div>
);
}
import { Html, useProgress } from '@react-three/drei'
function Loader() {
const { progress } = useProgress()
return <Html center>{progress} % loaded</Html>
}
useFrame
是一个 Fiber 挂钩,可让您在 Fiber 渲染循环的每一帧上执行代码。这可以有很多用途,但我们将专注于用它构建动画。
重要的是要记住Fiber 钩子只能在父级内部调用!
在每一帧直接改变我们的网格。首先,我们必须reference
通过useRef
React 钩子获取它:
import React, { useRef } from "react";
import { useGLTF } from "@react-three/drei";
import { useFrame } from '@react-three/fiber';
export default function Model(props) {
const group = useRef();
const gltf = useGLTF("/images/kunchong.glb");
useFrame(({clock}) => {
group.current.rotation.y = Math.sin(clock.getElapsedTime());
});
return (
<group ref={group} {...props} dispose={null}>
<primitive object={gltf.scene} />
</group>
);
}
useGLTF.preload("/images/kunchong.glb");
npm install three @react-spring/three
import { useSpring, animated } from '@react-spring/three'
useSpring
- 将值转换为动画值的钩子animated
-用于你的DOM代替或网状的部件,所以不是用mesh
你animated.mesh
,如果你希望它受以下因素影响react-spring
import React, { useRef, useState } from "react";
import { useGLTF } from "@react-three/drei";
import { useFrame } from '@react-three/fiber';
import { useSpring, animated, config } from '@react-spring/three';
export default function Model(props) {
const group = useRef();
const gltf = useGLTF("/images/kunchong.glb");
useFrame(({clock}) => {
group.current.rotation.y = Math.sin(clock.getElapsedTime());
});
const [active, setActive]=useState(false);
const { scale } = useSpring({ scale: active ? 1.5 : 1, config: config.wobbly });
return (
<animated.group ref={group} {...props} dispose={null} scale={scale} onClick={() => setActive(!active)}>
<primitive object={gltf.scene} />
</animated.group>
);
}
useGLTF.preload("/images/kunchong.glb");
您会看到它不仅从一个值跳到另一个值,而是在两个值之间平滑地进行动画处理。 我想做的最后一步是为动画添加一些摇摆不定的效果,为此我们可以config
从react-spring
以下位置导入对象:
import { useSpring, animated, config } from 'react-spring/three';
最后,当我们调用钩子时,我们可以为 config 传递一个值,然后让我们传递wobbly
配置:
const { scale } = useSpring({
scale: active ? 1.5 : 1,
config: config.wobbly,
});
import React, { useState, useEffect, useCallback } from "react";
import { useGLTF, useAnimations } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { useSpring, animated, config } from "@react-spring/three";
export default function Model(props) {
// const group = useRef();
const { scene, animations } = useGLTF("/images/kunchong.glb");
const { actions, names, ref } = useAnimations(animations);
useFrame(({ clock }) => {
ref.current.rotation.y = Math.sin(clock.getElapsedTime());
});
const [active, setActive] = useState(false);
const { scale } = useSpring({
scale: active ? 1.5 : 1,
config: config.wobbly,
});
const [activeIndex, setActiveIndex]=useState(0);
useEffect(() => {
console.log(actions, "actions", animations, names);
if (actions) {
console.log(actions, "actions", animations, names);
actions[names[activeIndex]].reset().fadeIn(0.5).play();
}
// In the clean-up phase, fade it out
return () => actions[names[activeIndex]].fadeOut(0.5);
}, [actions, names, activeIndex]);
const handleChangeAnmition=useCallback(()=>{
if(activeIndex<names.length-1){
setActiveIndex(activeIndex+1);
}else{
setActiveIndex(0);
}
}, [activeIndex]);
return (
<animated.group
ref={ref}
{...props}
dispose={null}
scale={scale}
onPointerOver={() => setActive(!active)}
onClick={handleChangeAnmition}
>
<primitive object={scene} />
</animated.group>
);
}
useGLTF.preload("/images/kunchong.glb");
Module.js
import React, { useState, useEffect, useCallback } from "react";
import { useGLTF, useAnimations } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { useSpring, animated, config } from "@react-spring/three";
export default function Model(props) {
// const group = useRef();
const { scene, animations } = useGLTF("/images/kunchong.glb");
const { actions, names, ref } = useAnimations(animations);
useFrame(({ clock }) => {
ref.current.rotation.y = Math.sin(clock.getElapsedTime());
});
const [active, setActive] = useState(false);
const { scale } = useSpring({
scale: active ? 0.6 : 1,
config: config.wobbly,
});
const [activeIndex, setActiveIndex]=useState(0);
useEffect(() => {
console.log(actions, "actions", animations, names);
if (actions) {
actions[names[activeIndex]].reset().fadeIn(0.5).play();
}
return () => actions[names[activeIndex]].fadeOut(0.5);
}, [actions, names, activeIndex]);
const handleChangeAnmition=useCallback(()=>{
if(activeIndex<names.length-1){
setActiveIndex(activeIndex+1);
}else{
setActiveIndex(0);
}
}, [activeIndex]);
return (
<animated.group
ref={ref}
{...props}
dispose={null}
scale={scale}
onPointerOver={() => setActive(!active)}
onClick={handleChangeAnmition}
>
<primitive object={scene} />
</animated.group>
);
}
useGLTF.preload("/images/kunchong.glb");
Loader.js
import { Html, useProgress } from "@react-three/drei";
export default function Loader() {
const { progress } = useProgress();
return (
<Html center style={{ color: "blue" }}>
{progress ? parseInt(progress) : 0} % loaded
</Html>
);
}
App.js
import { Canvas } from "@react-three/fiber";
import { Suspense } from "react";
import { Environment } from "@react-three/drei";
import Model from "./Model";
import './App.css';
import Loader from './Loader';
export default function App() {
return (
<div className="App">
<Canvas>
<ambientLight intensity={1} />
<Suspense fallback={<Loader />}>
<Model position={[0, -4, -12]} />
{/* */}
</Suspense>
</Canvas>
</div>
);
}