React-three-fiber快速入门
越来越多的站点使用交互式 3d 动画。 这些站点的交互体验让您感觉自己正在与真实世界的对象进行交互。 这种趋势只会增长,因为公司承诺进行巨额投资,以便他们能够在 Metaverse 世界中站稳脚跟。
以下是两个业界公认的交互式 3d 动画使用示例:
Bruno Simons 获奖网站 - 这个网站的设计就像一个 3D 游戏,你可以通过汽车导航来浏览网站。
Github 主页 - Github 整合了这个交互式 3d 地球仪,你可以与之交互以查看实时 Github 提交。
这两个站点都是使用 three.js 构建的。 three.js 的问题在于它的学习曲线非常陡峭。 你必须写很多代码来做简单的事情。 幸运的是,在 react 中,我们有一个 react-three-fiber 形式的解决方案。 React-three-fiber 通过利用简化了 api 的基于组件的模式,简化了很多复杂的编码。 在底层仍然是 three.js,没有任何妥协。
如果你打算在未来的 React.js 项目中使用交互式 3D 动画,并且想了解更多关于 react-three-fiber 的信息,那么这篇文章将是开始这一旅程的完美选择。
这篇文章将涵盖的内容包括:
- three.js、React-three-fiber 和交互性的快速介绍
- 3D 坐标系等 3D 概念的基本介绍
- 关于 3d 项目如何在 three.js 中构建的一些基本概念
- 如何使用 react-three-fiber 和 react-spring 构建第一个 3d 交互式应用程序
1、React-three-fiber快速介绍
Three.js 是事实上的 3d 动画库,已在 Javascript 开发人员中流行起来。 React-three-fiber 是 React.js 的 three.js 渲染器。 你可以用 three.js 做的一切都可以用 react-three-fiber 来完成。
此外,3d 对象也可以相对容易地进行交互。 例如,你可以附加事件处理程序来处理悬停和单击事件。 在 React.js 中,可以通过状态管理来管理 3d 对象的状态,进而更改其属性。 在我们要构建的演示中,我们将分别在悬停和单击时更改 3d 立方体的颜色和大小。
React-three-fiber 也有一个很好的生态系统。 它甚至包括流行的动画库 react-spring 的集成。 在本文中,您将学习如何将 react-spring 与 react-three-fiber 集成。 您可以在此处查看更多适用于 react-three-fiber 的库,可以看到它有一个也有详细记录的辅助功能模块。
2、关于 3d 坐标系
当使用 CSS 和 Html 时,我们使用相对或绝对位置定位事物,可以将 div 的左、右、上和下属性的值设置为该 div 的绝对位置。 当使用 3D 时,则需要了解如何在 3d 空间中定位事物,这是一个需要掌握的全新概念。
想象你在一个房间里,有一把椅子漂浮在地板和天花板之间的稀薄空气中,在 3d 中你可以使用三个值来定位它的位置,这是 x、y 和 Z 值。 这些值将相对于某个原点,假设原点将是房间的一个选定角落。 让我们看另一个位于房间角落的透明立方体示例。
立方体的高度、宽度和深度均为 2 个单位。
在图中,我用圆圈和相关坐标标记了立方体的 8 个角,房间的角是原点 (0,0,0)。 我还用红色箭头标记了 X、Y 和 Z 轴。 我们习惯于处理 X 和 Y 轴,这被视为 2D。 但是这里我们有一个额外的 Z 轴,可以看作是深度。
正如你在示例中看到的那样。 如果查看值为 (2,2,2) 的立方体角,你会发现这是立方体中唯一不接触左墙、右墙或地板但从地面升高的点。 这也是唯一没有零值的点。 所以这里重要的是前两个数字是 2D 中的 x、y 轴位置,第三个数字处理深度。
这是一个基本的解释,但请注意所有的轴也可以是负数,这意味着这些点将落在我们可见的空间之外。 你可以将其视为绝对定位的 div,可以在其中赋予负左值或负顶值以将其移出其父 div。
作为练习,你可以去 three.js 官方游乐场 进行一些尝试。
在上面的屏幕截图中,我所做的只是添加一个立方盒子,拖动手柄并观察位置值。 你可以拖动所有手柄并对此进行试验。
3、基本的 3D 项目结构
在我们深入研究 React-three-fiber 之前,需要对项目在 three.js 中的结构有一个基本的了解。 下面是概述这一点的图表。
上图中显示的内容列举如下:
- 场景 - 一个场景将包含所有 3D 对象。 每个对象也称为网格
- 网格 - 这是一个基本的场景对象,它用于保存在 3D 空间中表示形状所需的几何体和材质。
- 几何 - 几何定义形状,你可以将其视为没有图形细节的骨架结构
- 材质 - 定义形状表面的外观,这将是图形细节。
- Camera - 这将捕获场景中的所有内容,它还有一个位置值。 光 - 你需要一个光源才能看到物体。 如果没有光源,那么将看不到现实生活中的颜色和阴影。
渲染没有光或颜色的立方体的基本代码如下所示:
//The renderer will have access to the Canvas DOM element to
//update the display to show our 3d cube
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)
// Create Scene and camera
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
//Mesh - Holds the 3d object that will be added to scene
const mesh = new THREE.Mesh()
//Geometry is a property of Mesh
// Our mesh will have a geometry for a box or cube
mesh.geometry = new THREE.BoxGeometry()
//Material is also a property of Mesh
mesh.material = new THREE.MeshStandardMaterial()
//Add our Mesh to the scene
scene.add(mesh)
// After you have composed your scene with your mesh
// and added the camera, you can render the whole thing
// with animation loop
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
如果你看了代码中的注释,你就会对结构有一个大概的了解。 我不会深入探讨它,因为它已经很安静了。 这就是 React 和 React-three-fiber 派上用场的地方。 React-three-fiber 抽象出上述复杂性,允许我们以声明方式创建 3D 动画。 在这篇文章的其余部分,我将向你展示如何使用 react-three-fiber 和 spring 动画构建具有流畅动画的交互式立方体。
4、React-three-fiber项目开发
在本节中,我们将构建一个交互式立方体,它会旋转,当你将鼠标悬停在它上面时会改变颜色,当你单击鼠标时它会变大。 我们也将使用 react-spring 来实现流畅的动画。
4.1项目设置
假设你已经使用 create-react-app 设置了一个基本的 React 应用程序。 然后你可以运行:
npm install three @react-three/fiber
这将安装 three.js 和 react-three-fiber。 然后安装 Drei 依赖项:
npm install @react-three/drei
.请注意 drei 组件为 react-three.fiber 提供了一些强大的功能,但我们只会将其用于光源。
4.2 添加一些基本样式
然后在你的 app.css 中复制以下基本样式:
//app.css
html,
body,
#root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
4.3 基本 app.js 结构
这是我们 app.js 的骨架结构。 随着我们的进行,我们将填补空白。
import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import "./app.css"
function Cube(props) {
// Code for our 3d cube goes here. In other words Our mesh
}
function App() {
return (
<Canvas>
<ambientLight />
<Cube />
</Canvas>
);
}
export default App;
在顶部,我们有依赖项。 我们将使用 useState 和 useRef。
import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import "./app.css"
我们将让 react-three-fiber 处理渲染,这就是我们使用 useRef 的原因,这允许我们直接访问 DOM。
我们从 react-three-fiber 导入 Canvas,这允许我们创建一个 WebGl 容器来渲染我们的 3D。 useFrame 是 react-three-fiber 的标准动画钩子。
我们还附加了我们在最后一步中编写的 app.css。
让我们看看我们的 App 函数:
function App() {
return (
<Canvas>
<ambientLight />
<Cube />
</Canvas>
);
}
在 App 函数的 JSX 部分,我们使用 react-three-fiber Canvas 组件作为包装器。 这有两个子元素,一个是光源。 第二个元素是 ,该组件将渲染定义 3d 立方体的网格。 正如你之前在我们的框架代码中看到的那样,我们尚未为此编写代码。
请注意,通常还会添加一个摄像头,但对于我们的示例,我们可以保留它,因为 React-three-fiber 会自动添加一个具有默认位置的摄像头。 所以我们将使用默认值。
4.4 定义3d 立方体
我们的 cube 函数将如下所示:
function Cube(props) {
// Use useRef hook to access the mesh element
const mesh=useRef()
// Jsx to rnder our cube
return (
<mesh ref={mesh}>
<boxGeometry args={[2,2,2]}/>
<meshStandardMaterial />
</mesh>
)
}
我们在这里所做的就是创建一个具有 ref={mesh} 属性的 元素,我们正在使用它以便 react-three-fiber 可以直接访问 mesh 元素。 因此,我们有一行 const mesh=useRef() 来声明这个 ref 值。 元素有一个子元素和 。 请记住,在 three.js 中,网格元素具有几何形状和材质,这就是这些元素的用途。
元素中的参数用于维度。 我们使用具有三个值的 na 数组来创建一个高度、宽度和深度都等于单位的立方体。
我们的 app.js 代码现在看起来像这样:
import { useState, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import "./app.css";
function Cube(props) {
// Use useRef hook to access the mesh element
const mesh=useRef()
// Jsx to render our 3d cube. Our cube will have height
// width and depth equal 2 units.
// You also need a material so that you can add color
// and show shadows. We are using the standard
// material <<meshStandardMaterial />
return (
<mesh ref={mesh}>
<boxGeometry args={[2,2,2]}/>
<meshStandardMaterial />
</mesh>
)
}
// Basic app structure to render a 3d cube
//<ambientLight /> is the standard light to use, otherwise
// everything comes out as black
function App() {
return (
<Canvas>
<ambientLight />
<Cube />
</Canvas>
);
}
export default App;
在浏览器中,你会看到一个灰色框,如下所示。 但它实际上是一个立方体。 目前我们只看到正面。 在下一节中,我们将添加一些颜色和旋转。
4.5 添加灯光、颜色和动画
为了给我们的立方体真实的阴影,我们需要添加一个带有位置的特定点光源元素,看起来像 pointLight position={[10,10,10]} />
这样 。 这是要在 <ambientLight/>
之后添加我们的 App 功能。
我们的 App 功能现在看起来像这样:
function App() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10,10,10]} />
<Cube />
</Canvas>
);
}
现在让我们将注意力转移到我们的 Cube 函数上来添加颜色和基本动画。 为了给我们的立方体添加颜色,我们向 meshStandardMaterial 元素添加了一个属性,所以它变成了 <meshStandardMaterial color={"orange"}/> 。 这里我们将颜色设置为橙色。
要添加一个基本的旋转动画,我们使用动画帧钩子,它看起来像这样 useFrame ( ()=> (mesh.current.rotation.x += 0.01)) 。 在这里,我们正在访问 mesh.current.rotation.x 值以将其增加 0.01 个单位。 它基本上在 x 轴上旋转。
我们的代码如下所示:
import { useState, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import "./app.css";
function Cube(props) {
// Use useRef hook to access the mesh element
const mesh = useRef();
//Basic animation to rotate our cube using animation frame
useFrame ( ()=> (mesh.current.rotation.x += 0.01))
// Jsx to render our 3d cube. Our cube will have height
// width and depth equal 2 units.
// You also need a material so that you can add color
// and show shadows. We are using the standard
// material <<meshStandardMaterial />
return (
<mesh ref={mesh}>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial />
<meshStandardMaterial color={"orange"}/>
</mesh>
);
}
// Basic app structure to render a 3d cube
//<ambientLight /> is the standard light to use, otherwise
// everything comes out as black
function App() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10,10,10]} />
<Cube />
</Canvas>
);
}
export default App;
很好,我们的 3D 立方体有了色彩和阴影,它在 3D 空间中移动。
4.6 动态改变立方体的颜色
我们的目标是在将鼠标悬停在立方体上时让立方体改变颜色。 如你所知,如果要更改显示中的某些属性,那么需要使用状态变量和事件处理程序。
在 Cube 函数中,让我们引入一个状态变量来存储悬停状态:
const [hovered,setHover] = useState(false)
现在我们所要做的就是将事件处理程序绑定到 元素。 幸运的是网格组件有它的悬停事件处理程序,它们是:onPointerOver 和 onPointerOut。 这允许我们切换悬停进出的值。 所以我们的网格元素开始标签看起来像这样:
<mesh ref={mesh}
onPointerOver={ (event)=> setHover(true)}
onPointerOut={(event)=> setHover(false)} >
最后一部分是在状态变为悬停时将颜色更改为亮粉色。 这可以通过 meshStandardMaterial 元素的颜色属性的三元表达式来完成。 这样就变成了:
<meshStandardMaterial color={hovered ? "hotpink" : "orange"}/>
我们的立方体函数现在看起来像这样:
function Cube(props) {
// Use useRef hook to access the mesh element
const mesh = useRef();
// State values for hover
const [hovered, setHover] = useState(false);
//Basic animation to rotate our cube using animation frame
useFrame(() => (mesh.current.rotation.x += 0.01));
// Jsx to render our 3d cube. Our cube will have height
// width and depth equal 2 units.
// You also need a material so that you can add color
// and show shadows. We are using the standard
// material <<meshStandardMaterial />
return (
<mesh
ref={mesh}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
);
}
这就是它在悬停时改变颜色的全部。
4.7 添加事件
Three.js 有自己的动画钩子,但是three.js 做不到的我们可以用react-spring 动画来实现。 为了顺利调整立方体的大小,这次我们可以使用 react-spring 钩子。
提醒一下,要安装 react-spring 以便你可以将它与 react-three-fiber 一起使用,你需要运行以下命令:npm install three @react-spring/three。 然后你需要将它导入到 app.js 中,比如:
import { useSpring, animated } from '@react-spring/three'
app.js 顶部的导入部分现在看起来像这样:
import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import { useSpring, animated } from "@react-spring/three";
回到手头的任务,我们需要在点击事件中调整立方体的大小。 再次像之前改变颜色的任务一样,我们需要一个状态变量来存储单击时网格的活动状态。 因此,我们将以下行添加到我们的立方体函数中:
const [active,setActive] = useState(false)
现在我们向网格添加一个点击处理程序,就像之前我们有一个箭头函数来改变点击状态:onClick = {(event)=> setActive(!active)},所以我们的网格元素开始标签看起来像:
<mesh ref={mesh}
onPointerOver={ (event)=> setHover(true)}
onPointerOut={(event)=> setHover(false)}
onClick = {(event)=> setActive(!active)}
>
接下来,如果活动状态为真,我们需要将立方体的比例增加 1.5。 现在棘手的部分是 react-spring 将处理尺寸变化。
我们将把它应用到网格元素,所以我们需要做的是首先将网格元素重命名为 animated.mesh。 所以 .... 将变成 <animated.mesh>....</animated.mesh>。 我们还将设置一个比例属性,这个比例属性将由 react-spring 钩子处理,所以我们只需说 <animated.mesh scale={scale}>....</animated.mesh> 这样我们的 网格打开和关闭标签现在看起来像这样:
<animated.mesh ref={mesh}
onPointerOver={ (event)=> setHover(true)}
onPointerOut={(event)=> setHover(false)}
onClick = {(event)=> setActive(!active)}
scale = { scale}
> .......
....
</animated.mesh>
现在我们简单地使用 react-spring 钩子来设置大小和处理动画。 以下代码行可以解决问题:
const { scale } = useSpring({ scale: active ? 1.5 : 1 })
这里发生的是,我们正在传递一个三元表达式来检查活动状态是真还是假。 react-spring 将处理动画。
大功告成! app.js 的最终代码如下所示:
import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import { useSpring, animated } from "@react-spring/three"
import "./app.css"
function Cube(props) {
// Use useRef hook to access the mesh element
const mesh = useRef();
// State values for hover and active state
const [hovered, setHover] = useState(false);
const [active, setActive] = useState(false);
//Basic animation to rotate our cube using animation frame
useFrame(() => (mesh.current.rotation.x += 0.01));
//Spring animation hook that scales size based on active state
const { scale } = useSpring({ scale: active ? 1.5 : 1 });
// Jsx to render our 3d cube. Our cube will have height
// width and depth equal 2 units.
// You also need a material so that you can add color
// and show shadows. We are using the standard
// material <<meshStandardMaterial />
return (
<animated.mesh
ref={mesh}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
onClick={(event) => setActive(!active)}
scale={scale}
>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</animated.mesh>
);
}
function App() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Cube />
</Canvas>
);
}
export default App;
原文链接:Crash course in interactive 3d animation with React-three-fiber and React-spring
BimAnt翻译整理,转载请标明出处