
宣告:本文涉及圖文和模型素材僅用于個人學習、研究和欣賞,請勿二次修改、非法傳播、轉載、出版、商用、及進行其他獲利行為,
摘要
專欄上篇文章《Three.js 進階之旅:頁面*滑滾動-王國之淚》 講解并實作了如何使用 R3F 進行頁面圖片*滑滾動,本文內容在上節的基礎上,學習如何使用滾動控制 ScrollControls 來控制模型的的影片播放和相機影片,通過滾動滑鼠滾輪或者上下移動觸摸板,來控制模型的影片播放進度或者相機的方位視角,從而呈現出驚艷的視覺效果,這種有趣的效果大家在*時瀏覽一些網頁的時候應該經常見到,如一些 3D產品 介紹頁向下滑動滑鼠滾輪時產品同時旋轉并根據產品的不同視角加載不同文案、或者 3D數字地球 根據滾輪的移動距離轉到某個國家或地區、還有一些 個人簡歷 頁面或時間軸頁面也經常用到這種效果,通過本文的閱讀和案例頁面的實作,你將學習到的知識包括:R3F 生態中的 ScrollControls、Html、useScroll、useGLTF、useAnimations 等組件和方法的基本用法、在 R3F 中加載模型并播放模型骨骼影片、通過滾動控制模型影片播放行程和相機引數、頁面元素的一些 CSS 影片及頁面整體絲滑滾動影片實作等,
效果
本文案例的實作效果如下圖所示,頁面主體元素由一個三維模型 ????、及底部的 5 頁 HTML 頁面構成,頁面初始加載時模型是靜止的,當我們使用滑鼠或觸控板或直接拖動頁面滾動條時 ??,相機鏡頭?? 從正面*處*滑過渡到模型側面遠處,模型開始自動播放自帶骨骼影片,模型動作根據頁面滾動距離和滾動時速率的大小而不同,當我們點擊頁面頂部選單時,頁面會*滑滾動到對應位置,模型也會播放影片,

當頁面逆向 ?? 滾動時,相機和模型影片也會逆向變化和播放,

文章使用 GIF 可能會造成丟幀或卡頓,可以親自打開預覽鏈接試試,大屏訪問效果更佳,
?????在線預覽地址:https://dragonir.github.io/dancingDuck/
本專欄系列代碼托管在 Github 倉庫【threejs-odessey】,后續所有目錄也都將在此倉庫中更新,
??代碼倉庫地址:[email protected]:dragonir/threejs-odessey.git
原理
如果用原生 JavaScript 實作滾動影片效果,就需要監聽滾動事件和計算滾動距離,本文還是和上篇文章《hree.js 進階之旅:頁面*滑滾動-王國之淚》一樣,直接使用封裝好的組件 ScrollControls 和 Scroll 來實作,它們的詳細用法和原理可前往上篇文章查看,本文中最終實作的頁面需要加載模型并播放它自帶的骨骼影片,因此用到了以下幾個 @react-three/drei 中的組件和 hooks,
Html
允許我們將 HTML 內容系結到場景中的任意物件,它將自動投影到物件上,
<Html
as='div' // 包裹元素,默認為 'div'
wrapperClass // 包裹元素的類名,默認為 undefined
prepend // 畫布后面的元素,默認為 false
center // 添加 -50%/-50% css變換,默認為 false
fullscreen // 與左上角對齊并填滿螢屏,默認為 false
distanceFactor={10} // 如果設定該值,子元素將按與相機的距離進行縮放,默認為 undefined
zIndexRange={[100, 0]} // Z階范圍,默認為 [16777271, 0]
portal={domnodeRef} // 對目標容器的參考,默認為 undefined
transform // 若設定 true,將進行 3d 矩陣轉換,默認為 false
sprite // 渲染為 sprite,僅在轉換模式下生效,默認為 false
occlude // 遮擋模式,默認為 false,當設定為 blending 時將開啟真正混合遮擋
castShadow // 產生陰影
receiveShadow // 接收陰影
// 像 Mesh 一樣設定材質
material={<meshPhysicalMaterial side={DoubleSide} opacity={0.1} />}
// 覆寫默認定位功能
calculatePosition={(el: Object3D, camera: Camera, size: { width: number; height: number }) => number[]}
occlude={[ref]} // 可以為真或 Ref<Object3D>,當為 true 時遮擋整個場景,默認為 undefined
onOcclude={(visible) => null} // 可見性修改時的回呼,默認為 undefined
{...groupProps} // 支持所有 THREE.Group 屬性
{...divProps} // 支持所有 HTML DIV 元素屬性
>
<h1>hello</h1>
<p>world</p>
</Html>
Html 可以通過配置 occlude 屬性隱藏在幾何體后面,當 Html 組件隱藏時,它將在最內部的 div 上設定 opacity 屬性,如果需要添加影片效果或者控制過渡效果,可以使用 onOcclude 自定義方法,
<Html
occlude
onOcclude={set}
style={{
transition: 'all 0.5s',
opacity: hidden ? 0 : 1,
transform: `scale(${hidden ? 0.5 : 1})`
}}
/>
useGLTF
它是一個使用 useLoader 和 GLTFLoader 的方便鉤子函式,它默認使用 draco 來加載已壓縮的模型檔案,
useGLTF(url)
useGLTF(url, '/draco-gltf')
useGLTF.preload(url)
useAnimations
AnimationMixer 的抽象鉤子方法,可以像下面這樣獲取到模型自帶的影片資訊,
const { nodes, materials, animations } = useGLTF(url)
const { ref, mixer, names, actions, clips } = useAnimations(animations)
useEffect(() => {
actions?.jump.play()
})
THREE.MathUtils.damp
使用 dt 以類似彈簧的方式從 x 向 y *滑地插入一個數字,以保持與幀速率無關的運動,
.damp (x : Float, y: Float, lambda: Float, dt: Float ): Float
x:當前點,y:目標點,lambda:較高的lambda值可以使運動更加突然,較低的值可以使運動更加*緩,dt:以秒為單位的增量時間,
實作
〇 資源引入
在檔案頂部,我們按上述原理中所述,引入必需的組件和方法,
import * as THREE from 'three';
import { Suspense, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { ScrollControls, Html, useScroll, useGLTF, useAnimations } from '@react-three/drei';
① 場景初始化
場景初始化非常簡單,只需像下面這樣添加 R3F 的 <Canvas /> 組件,并初始化相機位置即可,
export default function Experience() {
return (
<>
<Canvas camera={{ position: [0, 0, 0] }}></Canvas>
</>
);
}

② 加載模型
我們先定義一個 ShubaDuck 類用來表示模型元素,然后使用 useGLTF 加載模型 ???? 并使用模型檔案的 scene 進行渲染,然后在 Canvas 中添加模型元素,并通過 scale 、position 等屬性調整模型在頁面上的顯示大小和位置,在頁面渲染前,我們也可以使用 useGLTF.preload 對模型進行預加載,以提高頁面使用體驗,
function ShubaDuck({ ...props }) {
const { scene, animations } = useGLTF('./models/duck.glb')
return <primitive object={scene} {...props} />
}
useGLTF.preload('./models/duck.glb');
<Canvas camera={{ position: [0, 0, 0] }}>
<ShubaDuck scale={8} position={[0, -7, 0]} />
</Canvas>
以下為原始模型檔案,下載完模型后可以我們可以在 Blender 中查看模型結構和影片資訊,洗掉修改不需要的元素,最后壓碩訓出,

頁面中完成模型加載并渲染,

??模型檔案來源:https://sketchfab.com/3d-models/shuba-duck-54a6276ce06c4cc88fd497c8f1b8eb66
③ 播放模型骨骼影片
我們從模型中拿到內置的骨骼影片 animations,然后使用 useAnimations 獲取到所有的動作,可以根據動作的名稱進行影片播放比如本例中是 LironShuba,可以在 useEffect 中對動作使用 play() 方法進行播放,本例中的 reset()、fadeIn() 方法都是可選的,它們的作用分別是開始前重置動作和模型入場影片型別,
function ShubaDuck({ ...props }) {
const { scene, animations } = useGLTF('./models/duck.glb')
const { actions } = useAnimations(animations, scene)
// 播放模型影片
useEffect(() => void (actions['LironShuba'].reset().fadeIn(0.5).play()), [actions])
// ...
}

④ 滾動控制模型影片
現在我們來添加通過滑鼠滾輪滾動來控制模型影片播放進度的功能,即當我們向下滾動頁面時,模型影片正向播放,否則逆向播放,滾動速度越快,模型影片播放速度也越快,我們先在 useEffect 中將模型影片初始狀態設定為 pause 暫停,然后在 useFrame 頁面重繪影片鉤子函式中拿到影片動作和滾動百分比 scroll.offset,設定動作播放時間 action.time,使其從初始值*滑過渡到目標值,目標值的確定可以通過整個影片播放周期時間以及頁面滾動距離的長度去計算,第三個引數 lambda 也可以根據自己的頁面進行調整,
function ShubaDuck({ ...props }) {
// ...
useEffect(() => void (actions['LironShuba'].play().paused = true), [actions])
useFrame((state, delta) => {
const action = actions['LironShuba']
const offset = scroll.offset
action.time = THREE.MathUtils.damp(action.time, (action.getClip().duration / 2) * offset, 100, delta)
state.camera.lookAt(0, 0, 0)
})
// ...
}
在頁面中,我們需要使用 <ScrollControls /> 組將將模型組件 <ShubaDuck /> 包裹起來,
<Canvas camera={{ position: [0, 0, 0] }}>
<Suspense fallback={null}>
<ScrollControls pages={4}>
<ShubaDuck scale={10} position={[0, -10, 0]} />
</ScrollControls>
</Suspense>
</Canvas>
此時使用滾動控制模型影片功能就全部完成了,頁面初始加載時模型是靜止的,當我們滾動頁面時,模型根據滾動速度和滾動距離就會自動播放對應的影片了,

??scroll.offset 是一個處于 [0, 1] 之間的數,表示滾動的百分比,當頁面未滾動時值為 0,完全滾動到盡頭時值為 1.
⑤ 滾動控制相機
在 useFrame 鉤子函式中,我們同樣可以在頁面滾動時動態修改相機 ?? 的位置,這樣在視覺上也能形成比如模型旋轉、放大縮小、顯示隱藏等影片效果,本案例中使用了如下的設定,當頁面從上往下滾動時,相機從模型正面變換到模型側面更遠的地方,在視覺上形成模型轉身并變小的效果 ?,
useFrame((state, delta) => {
// ...
state.camera.position.set(Math.sin(-offset) * 50, 1, Math.cos((offset * Math.PI) / 5) * 15)
})

⑥ 頁面裝飾
模型部分已經全部完成了,此時我們可以使用原理中介紹的 Html 組件,將其添加到頁面中,并直接用 HTML 和 CSS 添加一些好看的頁面,與模型主題呼應,像下面這樣,本文中添加了 5 個頁面,每個頁面都添加了不同的樣式,有時間的話,我們也可以使用 gsap 等影片庫,給 HTML 元素也添加一些滾動時的影片效果 ?,
<Canvas camera={{ position: [0, 0, 0] }}>
<Suspense fallback={null}>
<ScrollControls pages={4}>
<Html wrapperClass='articles' occlude>
<article className='page page1'></article>
<article className='page page2'></article>
<article className='page page3'></article>
<article className='page page4'></article>
<article className='page page5'></article>
</Html>
<ShubaDuck scale={10} position={[0, -10, 0]} />
</ScrollControls>
</Suspense>
</Canvas>
第一頁
第一頁有較多的頁面元素,其中底部白色文字使用了一種 woff2 格式的開源卡通字體,旋轉的鵝 ?? 是通過如下 CSS 影片實作的,
@keyframes rotateY
from
transform: perspective(400px) rotateY(0deg)
to
transform: perspective(400px) rotateY(360deg)

第二頁
第二頁是 3 個色塊 ?? 通過旋轉后形成的圖案,給它們添加了明暗變化的影片效果,

其他頁
剩下的頁面使用了一些簡單的文案或圖片元素,等有時間再優化小吧 ??,其實還有很多細節樣式和功能,如滑鼠的樣式是一個 ??、向下滾動的提示語、頂部半透明的導航選單等,具體實作可查看原始碼,



⑦ 點擊選單欄頁面滑動影片
最后,我們來給頁面頂部的選單添加一下點擊操作 ?? ,點擊頁面頂部導航欄選單滑動到對應頁面功能實作使用了 element.scrollIntoView 方法,可以像下面這樣實作并系結到選單的點擊事件中,此時點擊選單頁面滾動時,模型影片也會同時播放,
const handleMenuClick = (className) => {
const page = document.querySelector(className);
page.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
}
<span className='menu' onClick={handleMenuClick.bind(this, '.page1')}></span>

??原始碼地址: https://github.com/dragonir/threejs-odessey
總結
本文中主要包含的知識點包括:
R3F生態中的ScrollControls、Html、useScroll、useGLTF、useAnimations等組件和方法的基本用法,- 學會在
R3F中加載模型并播放模型骨骼影片, - 通過滾動控制模型影片播放行程和相機引數,
- 在滾動頁面中將模型和
HTML元素結合起來, - 頁面元素的一些
CSS影片及頁面整體絲滑滾動影片實作等,
想了解其他前端知識或其他未在本文中詳細描述的Web 3D開發技術相關知識,可閱讀我往期的文章,如果有疑問可以在評論中留言,如果覺得文章對你有幫助,不要忘了一鍵三連哦 ??,
附錄
- [1]. ?? Three.js 打造繽紛夏日3D夢中情島
- [2]. ?? Three.js 實作炫酷的賽博朋克風格3D數字地球大屏
- [3]. ?? Three.js 實作2022冬奧主題3D趣味頁面,含冰墩墩
- [4]. ?? Three.js 實作3D開放世界小游戲:阿貍的多元宇宙
- [5]. ?? Three.js 進階之旅:全景漫游-高階版在線看房
...- 【Three.js 進階之旅】系列專欄訪問 ??
- 更多往期【3D】專欄訪問 ??
- 更多往期【前端】專欄訪問 ??
參考
- [1]. threejs.org
- [2]. drei.pmnd.rs
本文作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/17430114.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/553344.html
標籤:其他
上一篇:Cesium開發案例整理
下一篇:返回列表
