使用 React Three Fiber 和 GSAP 实现滚动动画
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
让我们为 3D 模型和用户界面添加动画效果,使其能够跟随页面滚动:
- 维特
- React
- 顺风
- Three.js
- React 三纤维
- GSAP
🔥 本教程是打造精美作品集的良好起点。
此外,还有视频版本可供观看最终渲染效果:
项目设置
让我们先用 Vite 创建一个 React 应用。
yarn create vite
选择 React/Javascript 模板
现在添加 React Three Fiber 的依赖项。
yarn add three @react-three/drei @react-three/fiber
yarn dev
请前往index.css并删除其中的所有内容(保留该文件,我们稍后会用到)。
用App.css
#root {
width: 100vw;
height: 100vh;
background-color: #d9afd9;
background-image: linear-gradient(0deg, #d9afd9 0%, #97d9e1 100%);
}
现在创建一个名为“.”的文件夹components,并在其中创建一个Experience.jsx文件。我们将在这里构建我们的3D体验。
让我们在里面创建一个立方体,并添加OrbitControlsReact Three Drei:
import { OrbitControls } from "@react-three/drei";
export const Experience = () => {
return (
<>
<OrbitControls />
<mesh>
<boxBufferGeometry />
<meshNormalMaterial />
</mesh>
</>
);
};
现在让我们打开App.jsx并替换内容,Canvas使其包含我们的 Three.js 组件和Experience我们刚刚构建的组件。
import { Canvas } from "@react-three/fiber";
import "./App.css";
import { Experience } from "./components/Experience";
function App() {
return (
<Canvas>
<Experience />
</Canvas>
);
}
export default App;
保存并运行项目
yarn dev
你应该能看到一个立方体,并且可以用鼠标绕着它旋转(感谢…… OrbitControls)。
加载3D模型
你可以从这里获取模型。
别忘了感谢Thaís为我们制作了这个漂亮的模型🙏
现在在你的终端中运行
npx gltfjsx publics/models/WawaOffice.glb
gtlfjsx是一个客户端,可以根据你的 3D 模型自动创建 React 组件。它甚至还支持 TypeScript。
你应该已经生成了 WawaOffice.js 文件。
复制所有内容,Office.jsx在文件夹中创建components并粘贴组件。
将组件名称从Model“to”更改为“to Òffice”,并将路径修复为“to”。./models/WawaOffice.glb
现在你的办公室应该像这样了。
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/
import React, { useRef } from 'react'
import { useGLTF } from '@react-three/drei'
export function Office(props) {
const { nodes, materials } = useGLTF('./models/WawaOffice.glb')
return (
<group {...props} dispose={null}>
<mesh geometry={nodes['01_office'].geometry} material={materials['01']} />
<mesh geometry={nodes['02_library'].geometry} material={materials['02']} position={[0, 2.11, -2.23]} />
<mesh geometry={nodes['03_attic'].geometry} material={materials['03']} position={[-1.97, 4.23, -2.2]} />
</group>
)
}
useGLTF.preload('./models/WawaOffice.glb')
现在用组件Experience.jsx替换,并添加一个,以避免看到黑色的模型。mesh<Office /><ambientLight intensity={1}/>
顺便一提,这个模型包含了烘焙纹理(所以文件比较大)。也就是说,所有的光照和阴影都是在 Blender 中制作的,然后通过光线追踪烘焙到纹理文件中,才有了这么好的效果。
在滚动时为模型添加动画效果
让我们把Office组件包装到ScrollControlsReact Three Drei 中
<ScrollControls pages={3} damping={0.25}>
<Office />
</ScrollControls>
pages是您想要的页面数量。假设一页的高度等于视口的高度。damping是平滑因子。我使用 取得了不错的效果。0.25
更多信息请参阅文档。
你应该会看到一个滚动条出现,但你无法滚动,因为它们OrbitControls捕获了滚动事件。
只需按如下方式禁用即可
<OrbitControls enableZoom={false} />
为了控制我们的办公室动画,我们需要安装gsap库。
yarn add gsap
前往Office.jsx并存储ref到主组。
const ref = useRef();
return (
<group
{...props}
dispose={null}
ref={ref}
让我们gsap在 a 中创建 ou 时间线useLayoutEffect,并将组的 y 位置从其当前位置更新到 ,-FLOOR_HEIGHT * (NB_FLOORS - 1)持续时间为 2 秒。
export const FLOOR_HEIGHT = 2.3;
export const NB_FLOORS = 3;
export function Office(props) {
...
useLayoutEffect(() => {
tl.current = gsap.timeline();
// VERTICAL ANIMATION
tl.current.to(
ref.current.position,
{
duration: 2,
y: -FLOOR_HEIGHT * (NB_FLOORS - 1),
},
0
);
我们使用 2 秒的持续时间,因为我们有 3 个页面:
- 第一页和初始位置为 0 秒
- 第二个是1秒
- 第三页是动画的结尾(2秒)。
我们沿 Y 轴反向滚动办公室,因为我们滚动的是办公室本身,而不是摄像头。从下往上滚动时,我们需要降低办公室的垂直高度。
现在让我们播放动画。我们可以通过钩子访问滚动条useScroll,它包含一个偏移属性,其值介于 0 和 1 之间0,1用于表示当前的滚动百分比。
const scroll = useScroll();
useFrame(() => {
tl.current.seek(scroll.offset * tl.current.duration());
});
现在,我们的Office滚动条会随着页面滚动而垂直滚动。
让我们运用同样的原理来制作地板位置和旋转的动画。
这是我最终的版本,但您可以根据自己的喜好进行调整!
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/
import { useGLTF, useScroll } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import gsap from "gsap";
import React, { useLayoutEffect, useRef } from "react";
export const FLOOR_HEIGHT = 2.3;
export const NB_FLOORS = 3;
export function Office(props) {
const { nodes, materials } = useGLTF("./models/WawaOffice.glb");
const ref = useRef();
const tl = useRef();
const libraryRef = useRef();
const atticRef = useRef();
const scroll = useScroll();
useFrame(() => {
tl.current.seek(scroll.offset * tl.current.duration());
});
useLayoutEffect(() => {
tl.current = gsap.timeline();
// VERTICAL ANIMATION
tl.current.to(
ref.current.position,
{
duration: 2,
y: -FLOOR_HEIGHT * (NB_FLOORS - 1),
},
0
);
// Office Rotation
tl.current.to(
ref.current.rotation,
{ duration: 1, x: 0, y: Math.PI / 6, z: 0 },
0
);
tl.current.to(
ref.current.rotation,
{ duration: 1, x: 0, y: -Math.PI / 6, z: 0 },
1
);
// Office movement
tl.current.to(
ref.current.position,
{
duration: 1,
x: -1,
z: 2,
},
0
);
tl.current.to(
ref.current.position,
{
duration: 1,
x: 1,
z: 2,
},
1
);
// LIBRARY FLOOR
tl.current.from(
libraryRef.current.position,
{
duration: 0.5,
x: -2,
},
0.5
);
tl.current.from(
libraryRef.current.rotation,
{
duration: 0.5,
y: -Math.PI / 2,
},
0
);
// ATTIC
tl.current.from(
atticRef.current.position,
{
duration: 1.5,
y: 2,
},
0
);
tl.current.from(
atticRef.current.rotation,
{
duration: 0.5,
y: Math.PI / 2,
},
1
);
tl.current.from(
atticRef.current.position,
{
duration: 0.5,
z: -2,
},
1.5
);
}, []);
return (
<group
{...props}
dispose={null}
ref={ref}
position={[0.5, -1, -1]}
rotation={[0, -Math.PI / 3, 0]}
>
<mesh geometry={nodes["01_office"].geometry} material={materials["01"]} />
<group position={[0, 2.11, -2.23]}>
<group ref={libraryRef}>
<mesh
geometry={nodes["02_library"].geometry}
material={materials["02"]}
/>
</group>
</group>
<group position={[-1.97, 4.23, -2.2]}>
<group ref={atticRef}>
<mesh
geometry={nodes["03_attic"].geometry}
material={materials["03"]}
/>
</group>
</group>
</group>
);
}
useGLTF.preload("./models/WawaOffice.glb");
现在,您可以根据页面滚动效果获得漂亮的动画效果。
使用 Tailwind 准备 UI
我们来创建一个用户界面。你可以使用任何你喜欢的样式工具,但我选择了我的最爱——Tailwind!
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
它将生成一个tailwind.config.cjs替换内容的语句
/** @type {import('tailwindcss').Config} */
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
serif: ["Playfair Display", ...defaultTheme.fontFamily.sans],
sans: ["Poppins", ...defaultTheme.fontFamily.sans],
},
},
plugins: [],
};
它告诉 Tailwind 监视.html文件.jsx,并将默认字体更改为我从Google Fonts中选择的字体。
现在index.css补充:
@import url("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600&family=Poppins&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
第一行是导入 Google 字体。
好了,现在Tailwind已经安装好了,让我们来创建用户界面吧。
创建一个名为Overlay以下内容的组件
import { Scroll } from "@react-three/drei";
const Section = (props) => {
return (
<section className={`h-screen flex flex-col justify-center p-10 ${
props.right ? "items-end" : "items-start"
}`}
<div className="w-1/2 flex items-center justify-center">
<div className="max-w-sm w-full">
<div className="bg-white rounded-lg px-8 py-12">
{props.children}
</div>
</div>
</div>
</section>
);
};
export const Overlay = () => {
return (
<Scroll html>
<div class="w-screen">
<Section>
<h1 className="font-semibold font-serif text-2xl">
Hello, I'm Wawa Sensei
</h1>
<p className="text-gray-500">Welcome to my beautiful portfolio</p>
<p className="mt-3">I know:</p>
<ul className="leading-9">
<li>🧑💻 How to code</li>
<li>🧑🏫 How to learn</li>
<li>📦 How to deliver</li>
</ul>
<p className="animate-bounce mt-6">↓</p>
</Section>
<Section right>
<h1 className="font-semibold font-serif text-2xl">
Here are my skillsets 🔥
</h1>
<p className="text-gray-500">PS: I never test</p>
<p className="mt-3">
<b>Frontend 🚀</b>
</p>
<ul className="leading-9">
<li>ReactJS</li>
<li>React Native</li>
<li>VueJS</li>
<li>Tailwind</li>
</ul>
<p className="mt-3">
<b>Backend 🔬</b>
</p>
<ul className="leading-9">
<li>NodeJS</li>
<li>tRPC</li>
<li>NestJS</li>
<li>PostgreSQL</li>
</ul>
<p className="animate-bounce mt-6">↓</p>
</Section>
<Section>
<h1 className="font-semibold font-serif text-2xl">
🤙 Call me maybe?
</h1>
<p className="text-gray-500">
I'm very expensive but you won't regret it
</p>
<p className="mt-6 p-3 bg-slate-200 rounded-lg">
📞 <a href="tel:(+42) 4242-4242-424242">(+42) 4242-4242-424242</a>
</p>
</Section>
</div>
</Scroll>
);
};
请注意,我们的主 div 被包裹在一个Scroll带有 prop 的组件中html,以便能够将其添加html到我们的 Canvas 中,并稍后访问滚动条。
现在将Overlay组件添加到旁边Office
<ScrollControls pages={3} damping={0.25}>
<Overlay />
<Office />
</ScrollControls>
界面已经准备就绪,每个Section高度的100vh滚动效果也都不错。但我们再添加一些透明度动画。
滚动时为用户界面添加动画效果
我们将根据滚动情况改变各部分的透明度。
为此,我们将它们的透明度存储在一个状态中。
const [opacityFirstSection, setOpacityFirstSection] = useState(1);
const [opacitySecondSection, setOpacitySecondSection] = useState(1);
const [opacityLastSection, setOpacityLastSection] = useState(1);
然后useFrame我们使用可用的滚动钩子方法为它们添加动画效果(更多信息请点击这里)。
useFrame(() => {
setOpacityFirstSection(1 - scroll.range(0, 1 / 3));
setOpacitySecondSection(scroll.curve(1 / 3, 1 / 3));
setOpacityLastSection(scroll.range(2 / 3, 1 / 3));
});
我们将不透明度作为属性添加到各个部分。
<Section opacity={opacityFirstSection}>
...
<Section right opacity={opacitySecondSection}>
...
<Section opacity={opacityLastSection}>
...
现在,Section我们在组件中使用此属性来调整不透明度。
<section
className={`h-screen flex flex-col justify-center p-10 ${
props.right ? "items-end" : "items-start"
}`}
style={{
opacity: props.opacity,
}}
>
结论
恭喜!您现在已经拥有了一个很好的起点,可以使用 React Three Fiber 和 Tailwind 构建自己的作品集。
代码可在此处获取:
https://github.com/wass08/r3f-scrolling-animation-tutorial
我强烈建议您阅读React Three Fiber 的文档并查看其示例,以了解您可以实现什么以及如何实现。
想要学习更多 React Three Fiber 教程,可以查看我在 YouTube 上的Three.js/React Three Fiber播放列表。
谢谢,如有任何疑问,欢迎在评论区留言🙏
文章来源:https://dev.to/wawasensei/scroll-animations-with-react- Three-Fiber-and-gsap-273j



