聲明:本文涉及圖文和模型素材僅用于個人學(xué)習(xí)、研究和欣賞,請勿二次修改、非法傳播、轉(zhuǎn)載、出版、商用、及進行其他獲利行為。
摘要
專欄上篇文章《Three.js 進階之旅:頁面*滑滾動-王國之淚》 講解并實現(xiàn)了如何使用 R3F
進行頁面圖片*滑滾動,本文內(nèi)容在上節(jié)的基礎(chǔ)上,學(xué)習(xí)如何使用滾動控制 ScrollControls
來控制模型的的動畫播放和相機動畫,通過滾動鼠標(biāo)滾輪或者上下移動觸摸板,來控制模型的動畫播放進度或者相機的方位視角,從而呈現(xiàn)出驚艷的視覺效果。這種有趣的效果大家在*時瀏覽一些網(wǎng)頁的時候應(yīng)該經(jīng)常見到,如一些 3D產(chǎn)品
介紹頁向下滑動鼠標(biāo)滾輪時產(chǎn)品同時旋轉(zhuǎn)并根據(jù)產(chǎn)品的不同視角加載不同文案、或者 3D數(shù)字地球
根據(jù)滾輪的移動距離轉(zhuǎn)到某個國家或地區(qū)、還有一些 個人簡歷
頁面或時間軸頁面也經(jīng)常用到這種效果。通過本文的閱讀和案例頁面的實現(xiàn),你將學(xué)習(xí)到的知識包括:R3F
生態(tài)中的 ScrollControls
、Html
、useScroll
、useGLTF
、useAnimations
等組件和方法的基本用法、在 R3F
中加載模型并播放模型骨骼動畫、通過滾動控制模型動畫播放進程和相機參數(shù)、頁面元素的一些 CSS
動畫及頁面整體絲滑滾動動畫實現(xiàn)等。
效果
本文案例的實現(xiàn)效果如下圖所示,頁面主體元素由一個三維模型 ????
、及底部的 5
頁 HTML
頁面構(gòu)成,頁面初始加載時模型是靜止的,當(dāng)我們使用鼠標(biāo)或觸控板或直接拖動頁面滾動條時 ??
,相機鏡頭??
從正面*處*滑過渡到模型側(cè)面遠處,模型開始自動播放自帶骨骼動畫,模型動作根據(jù)頁面滾動距離和滾動時速率的大小而不同。當(dāng)我們點擊頁面頂部菜單時,頁面會*滑滾動到對應(yīng)位置,模型也會播放動畫。
當(dāng)頁面逆向 ??
滾動時,相機和模型動畫也會逆向變化和播放。
文章使用 GIF
可能會造成丟幀或卡頓,可以親自打開預(yù)覽鏈接試試,大屏訪問效果更佳。
-
?????
在線預(yù)覽地址:https://dragonir.github.io/dancingDuck/
本專欄系列代碼托管在 Github
倉庫【threejs-odessey】,后續(xù)所有目錄也都將在此倉庫中更新。
??
代碼倉庫地址:git@github.com:dragonir/threejs-odessey.git
原理
如果用原生 JavaScript
實現(xiàn)滾動動畫效果,就需要監(jiān)聽滾動事件和計算滾動距離。本文還是和上篇文章《hree.js 進階之旅:頁面*滑滾動-王國之淚》一樣,直接使用封裝好的組件 ScrollControls
和 Scroll
來實現(xiàn),它們的詳細(xì)用法和原理可前往上篇文章查看。本文中最終實現(xiàn)的頁面需要加載模型并播放它自帶的骨骼動畫,因此用到了以下幾個 @react-three/drei
中的組件和 hooks
。
Html
允許我們將 HTML
內(nèi)容綁定到場景中的任意對象,它將自動投影到對象上。
<Html
as='div' // 包裹元素,默認(rèn)為 'div'
wrapperClass // 包裹元素的類名,默認(rèn)為 undefined
prepend // 畫布后面的元素,默認(rèn)為 false
center // 添加 -50%/-50% css變換,默認(rèn)為 false
fullscreen // 與左上角對齊并填滿屏幕,默認(rèn)為 false
distanceFactor={10} // 如果設(shè)置該值,子元素將按與相機的距離進行縮放,默認(rèn)為 undefined
zIndexRange={[100, 0]} // Z階范圍,默認(rèn)為 [16777271, 0]
portal={domnodeRef} // 對目標(biāo)容器的引用,默認(rèn)為 undefined
transform // 若設(shè)置 true,將進行 3d 矩陣轉(zhuǎn)換,默認(rèn)為 false
sprite // 渲染為 sprite,僅在轉(zhuǎn)換模式下生效,默認(rèn)為 false
occlude // 遮擋模式,默認(rèn)為 false,當(dāng)設(shè)置為 blending 時將開啟真正混合遮擋
castShadow // 產(chǎn)生陰影
receiveShadow // 接收陰影
// 像 Mesh 一樣設(shè)置材質(zhì)
material={<meshPhysicalMaterial side={DoubleSide} opacity={0.1} />}
// 覆蓋默認(rèn)定位功能
calculatePosition={(el: Object3D, camera: Camera, size: { width: number; height: number }) => number[]}
occlude={[ref]} // 可以為真或 Ref<Object3D>,當(dāng)為 true 時遮擋整個場景,默認(rèn)為 undefined
onOcclude={(visible) => null} // 可見性修改時的回調(diào),默認(rèn)為 undefined
{...groupProps} // 支持所有 THREE.Group 屬性
{...divProps} // 支持所有 HTML DIV 元素屬性
>
<h1>hello</h1>
<p>world</p>
</Html>
Html
可以通過配置 occlude
屬性隱藏在幾何體后面。當(dāng) Html
組件隱藏時,它將在最內(nèi)部的 div
上設(shè)置 opacity
屬性,如果需要添加動畫效果或者控制過渡效果,可以使用 onOcclude
自定義方法。
<Html
occlude
onOcclude={set}
style={{
transition: 'all 0.5s',
opacity: hidden ? 0 : 1,
transform: `scale(${hidden ? 0.5 : 1})`
}}
/>
useGLTF
它是一個使用 useLoader
和 GLTFLoader
的方便鉤子函數(shù),它默認(rèn)使用 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
*滑地插入一個數(shù)字,以保持與幀速率無關(guān)的運動。
.damp (x : Float, y: Float, lambda: Float, dt: Float ): Float
-
x
:當(dāng)前點。 -
y
:目標(biāo)點。 -
lambda
:較高的lambda
值可以使運動更加突然,較低的值可以使運動更加*緩。 -
dt
:以秒為單位的增量時間。
實現(xiàn)
〇 資源引入
在文件頂部,我們按上述原理中所述,引入必需的組件和方法。
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
等屬性調(diào)整模型在頁面上的顯示大小和位置。在頁面渲染前,我們也可以使用 useGLTF.preload
對模型進行預(yù)加載,以提高頁面使用體驗。
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
中查看模型結(jié)構(gòu)和動畫信息,刪除修改不需要的元素,最后壓縮導(dǎo)出。
頁面中完成模型加載并渲染。
??
模型文件來源:https://sketchfab.com/3d-models/shuba-duck-54a6276ce06c4cc88fd497c8f1b8eb66
③ 播放模型骨骼動畫
我們從模型中拿到內(nèi)置的骨骼動畫 animations
,然后使用 useAnimations
獲取到所有的動作,可以根據(jù)動作的名稱進行動畫播放比如本例中是 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])
// ...
}
④ 滾動控制模型動畫
現(xiàn)在我們來添加通過鼠標(biāo)滾輪滾動來控制模型動畫播放進度的功能,即當(dāng)我們向下滾動頁面時,模型動畫正向播放,否則逆向播放,滾動速度越快,模型動畫播放速度也越快。我們先在 useEffect
中將模型動畫初始狀態(tài)設(shè)置為 pause
暫停,然后在 useFrame
頁面重繪動畫鉤子函數(shù)中拿到動畫動作和滾動百分比 scroll.offset
,設(shè)置動作播放時間 action.time
,使其從初始值*滑過渡到目標(biāo)值,目標(biāo)值的確定可以通過整個動畫播放周期時間以及頁面滾動距離的長度去計算,第三個參數(shù) lambda
也可以根據(jù)自己的頁面進行調(diào)整。
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>
此時使用滾動控制模型動畫功能就全部完成了,頁面初始加載時模型是靜止的,當(dāng)我們滾動頁面時,模型根據(jù)滾動速度和滾動距離就會自動播放對應(yīng)的動畫了。
??
scroll.offset 是一個處于 [0, 1] 之間的數(shù),表示滾動的百分比,當(dāng)頁面未滾動時值為 0,完全滾動到盡頭時值為 1.
⑤ 滾動控制相機
在 useFrame
鉤子函數(shù)中,我們同樣可以在頁面滾動時動態(tài)修改相機 ??
的位置,這樣在視覺上也能形成比如模型旋轉(zhuǎn)、放大縮小、顯示隱藏等動畫效果。本案例中使用了如下的設(shè)置,當(dāng)頁面從上往下滾動時,相機從模型正面變換到模型側(cè)面更遠的地方,在視覺上形成模型轉(zhuǎn)身并變小的效果 ?
。
useFrame((state, delta) => {
// ...
state.camera.position.set(Math.sin(-offset) * 50, 1, Math.cos((offset * Math.PI) / 5) * 15)
})
⑥ 頁面裝飾
模型部分已經(jīng)全部完成了,此時我們可以使用原理中介紹的 Html
組件,將其添加到頁面中,并直接用 HTML
和 CSS
添加一些好看的頁面,與模型主題呼應(yīng)。像下面這樣,本文中添加了 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
格式的開源卡通字體,旋轉(zhuǎn)的鵝 ??
是通過如下 CSS
動畫實現(xiàn)的。
@keyframes rotateY
from
transform: perspective(400px) rotateY(0deg)
to
transform: perspective(400px) rotateY(360deg)
第二頁
第二頁是 3
個色塊 ??
通過旋轉(zhuǎn)后形成的圖案,給它們添加了明暗變化的動畫效果。
其他頁
剩下的頁面使用了一些簡單的文案或圖片元素,等有時間再優(yōu)化小吧 ??
。其實還有很多細(xì)節(jié)樣式和功能,如鼠標(biāo)的樣式是一個 ??
、向下滾動的提示語、頂部半透明的導(dǎo)航菜單等,具體實現(xiàn)可查看源碼。
⑦ 點擊菜單欄頁面滑動動畫
最后,我們來給頁面頂部的菜單添加一下點擊操作 ??
。點擊頁面頂部導(dǎo)航欄菜單滑動到對應(yīng)頁面功能實現(xiàn)使用了 element.scrollIntoView
方法,可以像下面這樣實現(xiàn)并綁定到菜單的點擊事件中,此時點擊菜單頁面滾動時,模型動畫也會同時播放。
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
總結(jié)
本文中主要包含的知識點包括:
-
R3F
生態(tài)中的ScrollControls
、Html
、useScroll
、useGLTF
、useAnimations
等組件和方法的基本用法。 - 學(xué)會在
R3F
中加載模型并播放模型骨骼動畫。 - 通過滾動控制模型動畫播放進程和相機參數(shù)。
- 在滾動頁面中將模型和
HTML
元素結(jié)合起來。 - 頁面元素的一些
CSS
動畫及頁面整體絲滑滾動動畫實現(xiàn)等。
想了解其他前端知識或其他未在本文中詳細(xì)描述的Web 3D開發(fā)技術(shù)相關(guān)知識,可閱讀我往期的文章。如果有疑問可以在評論中留言,如果覺得文章對你有幫助,不要忘了一鍵三連哦 ??。文章來源:http://www.zghlxwxcb.cn/news/detail-458278.html
附錄
- [1]. ?? Three.js 打造繽紛夏日3D夢中情島
- [2]. ?? Three.js 實現(xiàn)炫酷的賽博朋克風(fēng)格3D數(shù)字地球大屏
- [3]. ?? Three.js 實現(xiàn)2022冬奧主題3D趣味頁面,含冰墩墩
- [4]. ?? Three.js 實現(xiàn)3D開放世界小游戲:阿貍的多元宇宙
- [5]. ?? Three.js 進階之旅:全景漫游-高階版在線看房
...
- 【Three.js 進階之旅】系列專欄訪問 ??
- 更多往期【3D】專欄訪問 ??
- 更多往期【前端】專欄訪問 ??
參考
- [1]. threejs.org
- [2]. drei.pmnd.rs
本文作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/17430114.html文章來源地址http://www.zghlxwxcb.cn/news/detail-458278.html
到了這里,關(guān)于Three.js 進階之旅:滾動控制模型動畫和相機動畫 ?的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!