Three.js 进阶之旅:页面平滑滚动-塞尔达王国之泪

声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。


摘要
浏览网页时,常被一些基于鼠标滚轮控制的页面动画所惊艳到,比如 greensock 官网这些 showcase
案例页面就非常优秀,它们大多数都是使用 Tween.js
、gasp
及 greensock
提供的一些动画扩展库实现的。使用 Three.js
也能很容易实现丝滑的滚动效果,本文使用 React
+ Three.js
+ React Three Fiber
技术栈,实现一个《塞尔达传说:王国之泪》主题风格基于滚动控制的平滑滚动图片展示页面。通过本文的阅读,你将学习到的知识点包括:了解 R3F
中 useFrame hook
及 useThree hook
基本原理及用法;了解 @react-three/drei
库的基本组成,学习使用它提供的 Preload
、useIntersect
、ScrollControls
、Scroll
、及 Image
等组件和方法;用 CSS
生成简单的循环悬浮动画等。
效果
本文案例的实现效果如下图所示,当页面每个模块滚动进入视区时,每个模块会具有平滑向上移动的视差效果,并且伴随着由大到小的缩放动画,当鼠标悬浮到当前模块时,模块会产生高亮 ✨
效果。除此之外,页面还有一些其他的装饰,比如塞尔达风格的页面背景和边框、具有缓动动画效果的希卡之石以及同样具有平滑滚动效果的文字装饰王国之泪四个字。

页面的整体布局是这样的,总共有 7
页,即高度为 700vh
,每一页都具有不同的布局风格样式,滚动时都会具有缓动效果。

打开以下链接,在线预览效果,本文中的 gif
造成丢帧和画质损失,大屏访问效果更佳。
👁🗨
在线预览地址:https://dragonir.github.io/tearsOfTheKingdom/
本专栏系列代码托管在 Github
仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。
🔗
代码仓库地址:git@github.com:dragonir/threejs-odessey.git
原理
本文是使用 React Three Fiber
实现的,它不仅可以非常容易实现漂亮的三维图形,在二维平面页面开发中也能大放异彩。在开始实现本文案例之前,我们先来汇总下本文中需要应用到的知识点。掌握这些原理和方法,可以帮助我们迅速构建一个交互体验极佳的平滑滚动页面。
useFrame
此 hook
允许在页面每一帧渲染的时候运行代码,比如更新渲染效果、控件等,与 Three.js
中调用 requestAnimationFrame
实行重绘动画效果是一样的。你将接收到状态值 state
和时钟增量 delta
。回调函数将在渲染帧之前被调用,当组件卸载时,它会自动从渲染循环中注销。
💡
注意,在 useFrame 中不能使用 setState 更新状态值!
控制渲染循序
如果你需要更多的控制,你可以传递一个数字渲染优先级值。这将导致 React Three Fiber
完全禁用自动渲染。现在,渲染顺序将由我们自己控制,这在后期渲染通道处理以及在多个视图渲染的场景下非常有用。
💡
回调将按优先级值的升序(最低在前,最高在后)执行,类似于DOM
的层级顺序。
负索引
使用负索引无法接管渲染循环控制,但如果确实必须对组件树中的 useFrame
序列进行排序,使用负索引将很有用。

useThree
此 hook
允许访问的状态模型包括默认渲染器 renderer
、场景 scene
、相机 camera
等。它还提当前画布 canvas
在屏幕和视区中的坐标位置大小。它是动态自适应的,如果调整浏览器大小,将返回新的测量值,它适用于所有可能更改的状态对象。
State 属性值

选择属性
可以通过选择属性,避免对仅对关注的组件进行不必要的重新渲染,需要注意的是无法响应式地获得 Three.js
深层次的动态属性。
从组件循环外部读取状态
交换默认值

@react-three/drei
@react-three/drei
是一个正在不断扩充的,用于 @react-three/fiber
的由实用的辅助工具、完整的功能性方法以及现成的抽象构成的库。可以通过如下方法进行安装。下图列出了当前该仓库中包含的所有组件和方法,本文案例中将通过 @react-three/drei
的以下几个组件来实现平滑滚动效果。
npm install @react-three/drei

Preload
WebGLRenderer
只有材质被触发时才会进行编译,这可能会导致卡顿。此组件使用 gl.compile
预编译场景,确保应用从一开始就具有响应性。默认情况下,gl.compile
只会预加载可见对象,如果你提供了所有属性,它们可能会被忽略。
useIntersect
它可以非常方便的检测三维元素是否可见,当对象进入视图或者处于视图之外时,可以通过它获得对象的可见性参考值,useIntersect
依赖于 THREE.Object3D.onBeforeRender
实现,因此它仅适用于有效渲染的对象,如 mesh
、line
、sprite
等,在 group
及 object3D
的骨骼中无法生效。
ScrollControls 和 Scroll
ScrollControls
可以在 canvas
前方创建一个 HTML
滚动容器,你放入滚动组件 <Scroll>
中的所有元素都将受到影响。你可以使用 useScroll
钩子对滚动事件进行监听并响应,它提供很多有用的数据,例如当前的滚动偏移量、增量以及用于范围查找的函数:range
、curve
及 visible
。如果需要对滚动偏移做出响应,如对象进入或移除视图时添加淡入淡出效果等,则后面的方法非常有用。
ScrollControls 的可配置属性:
可以像下面这样使用:
Image
是一个自动开启平铺效果的基于着色器的图片组件,图片填充效果类似于 CSS
中的 background-size: cover
。
给材质增加透明度:

实现
现在,我们就应用上述原理知识,实现预览效果所示的 《塞尔达传说:王国之泪》
主题的平滑滚动页面。
资源引入
原理篇幅 👆
已经详细讲解了本文用到的功能库和组件,我们在代码顶部像下面这样引入它们。
场景初始化
专栏文章《Three.js 进阶之旅:物理效果-3D乒乓球小游戏》已经详细介绍过 React Three Fiber
入门知识。使用 R3F
初始化三维场景非常简单,像下面这样一行代码就能完成场景初始化。本次实现不需要精细的三维效果,因此渲染器的抗锯齿属性 antialias
可以设置为 false
。

💡
为了可以看见 canvas 画布区域,在 css 中设置了一个渐变背景色。
页面装饰
接着,使用 R3F
实现平滑的滚动效果之前,我们先来装饰一下页面,为了符合 《塞尔达传说:王国之类》
的主题,我在本页面中添加了游戏主题背景、边框、以及 希卡之石
动画。由于这些内容不是本文的重点,本文不再赘述,具体实现可以查看源码 📜
。
首屏页面
首屏页面主要有 2
个元素,一个是背景图、另一个是 ZELDA 图片 logo
,当页面加载时背景图有一个由大到小的缩放效果,鼠标悬浮到图片上时,当前鼠标所处图片会变为高亮状态。它们我们可以使用原理部分了解到的 Image
元素实现。

图片组件封装
页面其他图片采用的动画效果也是类似的,为了可以复用,我们先对 Image
元素封装一下,将由大到小的缩放效果以及鼠标悬浮的 hover
高亮效果添加到每个 Image
元素中:
然后我们再封装一个名为 Images
的 group
元素,用来统一管理页面上的所有图片,分别设置每个图片的链接、在页面上的位置、大小、透明度等一些个性化属性。
图片组件使用
然后,使用 <ScrollControls>
和 <Scroll>
来直接生成滚动页面,在其中添加上述封装的 <Image>
组件,也可以在其中添加一些装饰性文字 王国之泪,同样可以进行滚动控制,使用 Preload
进行预加载,提升页面渲染性能。
此时我们可以看看实现效果,首先是图片元素进入视区时由大到小的缩放动画效果。中间的透明 logo
图片似乎有点问题,我们可以像下面这样修复 😂
:
鼠标悬浮高亮效果:

页面平滑滚动:
其他页面
到这里,其他页面的实现就非常简单了,我们只需按自己的页面设计,在 Images
中像下面这样排好页面上所有需要平滑滚动的图片即可,可以通过 position
、scale
等属性设置个性化调整图片在页面上的位置、大小、加载时机等,比如本文示例中第 2
页有 3
张图片、第 3
页有 1
张图片……
下图是本文示例所有图片的页面布局,总共有 7
页,每页图片都有不同的排版样式。

结束页面
最后一张页面,林克
由小变大平滑滚动进入视区,与背景形成视差效果,是通过调整它的 position.z
来实现这一效果的,大家在动手实践时可以尝试设置不同的值,以达到自己的预期效果。

🔗
源码地址: https://github.com/dragonir/threejs-odessey
总结
本文中主要包含的知识点包括:
了解
useFrame hook
基本原理及使用它控制渲染顺序和使用负索引。了解
useThree hook
基本原理、基本属性值,使用它选择属性、从组件循环外部读取状态、交换默认值等。了解
@react-three/drei
库的基本组成,学习使用它提供的Preload
、useIntersect
、ScrollControls
、Scroll
、及Image
等组件和方法。用
CSS
生成简单的循环悬浮动画。使用上述
R3F
知识原理,生成一个具有视差效果的平滑滚动页面。
想了解其他前端知识或其他未在本文中详细描述的 Web 3D 开发技术相关知识,可阅读我往期的文章。如果有疑问可以在评论中留言,如果觉得文章对你有帮助,不要忘了一键三连哦 👍。

附录
欢迎关注我的【Three.js 进阶之旅】系列专栏 👈
参考
[1]. threejs.org
[2]. React Three Filber
[3]. greensock 官网案例
