帝國(guó)cms做笑話網(wǎng)站宣傳渠道和宣傳方式有哪些
最近在看three.js相關(guān)的東西,想著學(xué)習(xí)一下threejs給的examples。源碼是用html結(jié)合js寫的,恰好最近也在學(xué)習(xí)react,就用react框架學(xué)習(xí)一下。
本文參考的是threeJs給的第一個(gè)示例
?three.js examples (threejs.org)
?一、下載threeJS源碼
通常我們只用通過npm引入threejs的包就可以使用threejs了。為什么這里需要下載源碼呢?因?yàn)槲覀円獜?fù)刻源碼給的示例,相關(guān)的模型我們是沒有的,需要使用源碼里用到的模型及解析工具
GitHub - mrdoob/three.js: JavaScript 3D Library.
?從git上拉取代碼后可以找到示例一的源碼
?閱讀源碼可以發(fā)現(xiàn),完成示例需要引入jsm/libs/draco/gltf/路徑以及models/gltf/LittlestTokyo.glb模型。
拷貝threeJS的必要的模型和方法
為了方便后續(xù)學(xué)習(xí),我們直接將這兩個(gè)文件夾jsm和models拷貝到react項(xiàng)目中;注意路徑最好是public下,public是默認(rèn)的靜態(tài)資源加載入口
?
?二、功能解析與改寫
react搭建及threejs引入可以參考我的之前的博客,這里不多贅述
Three.js機(jī)器人與星系動(dòng)態(tài)場(chǎng)景:實(shí)現(xiàn)3D渲染與交互式控制-CSDN博客
?引入必要信息
import { useEffect, useRef } from "react";
import * as THREE from "three";
import Stats from "three/examples/jsm/libs/stats.module.js";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
初始化Render渲染器/Scene場(chǎng)景/?camer相機(jī)/controls軌道控制器
// 初始化渲染器的函數(shù)
/*** 初始化 WebGL 渲染器* @returns {THREE.WebGLRenderer} 創(chuàng)建并配置好的渲染器實(shí)例*/
// 初始化渲染
function initRender(): THREE.WebGLRenderer {// 創(chuàng)建一個(gè)WebGL渲染器const renderer = new THREE.WebGLRenderer({ antialias: true });// 根據(jù)設(shè)備像素比設(shè)置渲染器像素比renderer.setPixelRatio(window.devicePixelRatio);// 設(shè)置渲染器大小renderer.setSize(window.innerWidth, window.innerHeight);return renderer;
}// 初始化場(chǎng)景的函數(shù)
/*** 初始化場(chǎng)景* @param {THREE.WebGLRenderer} renderer - 渲染器實(shí)例* @returns {THREE.Scene} 創(chuàng)建并配置好的場(chǎng)景實(shí)例*/
function initScene(renderer: THREE.WebGLRenderer) {// 創(chuàng)建 PMREM 生成器const pmremGenerator = new THREE.PMREMGenerator(renderer);// 創(chuàng)建場(chǎng)景const scene = new THREE.Scene();// 設(shè)置場(chǎng)景背景scene.background = new THREE.Color(0xbfe3dd);// 設(shè)置場(chǎng)景環(huán)境scene.environment = pmremGenerator.fromScene(new RoomEnvironment(renderer), 0.04).texture;return scene;
}// 初始化相機(jī)的函數(shù)
/*** 初始化相機(jī)* @param {number} x - 相機(jī)在 x 軸的位置* @param {number} y - 相機(jī)在 y 軸的位置* @param {number} z - 相機(jī)在 z 軸的位置* @returns {THREE.PerspectiveCamera} 創(chuàng)建并配置好位置的相機(jī)實(shí)例*/
function initCamera(x: number, y: number, z: number) {const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);camera.position.set(x, y, z);return camera;
}// 初始化控制器的函數(shù)
/*** 初始化軌道控制器* @param {THREE.PerspectiveCamera} camera - 相機(jī)實(shí)例* @param {THREE.WebGLRenderer} renderer - 渲染器實(shí)例* @returns {OrbitControls} 創(chuàng)建并配置好的軌道控制器實(shí)例*/
function initControls(camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) {const controls = new OrbitControls(camera, renderer.domElement);controls.update();controls.enablePan = false;controls.enableDamping = true;return controls;
}
?組件核心方法Keyframes
采用react的函數(shù)式組件寫法,首字母大寫作為組件名,并導(dǎo)出
整個(gè)流程是初始化渲染器、scene場(chǎng)景、camera相機(jī)、controls軌道控制器;在場(chǎng)景中引入模型,并使用dracoLoader解壓GLTFLoader引入的模型,開啟模型上的動(dòng)畫,設(shè)置場(chǎng)景動(dòng)畫。
/*** Keyframes 組件函數(shù)*/
function Keyframes() {const containerRef = useRef<HTMLDivElement>(null); // 創(chuàng)建用于引用 HTML 元素的 refconst clock = new THREE.Clock(); // 創(chuàng)建時(shí)鐘實(shí)例const statsRef = useRef<Stats>(); // 創(chuàng)建用于引用統(tǒng)計(jì)信息的 refconst mixerRef = useRef<THREE.AnimationMixer>(); // 創(chuàng)建用于引用動(dòng)畫混合器的 refconst renderer = initRender(); // 初始化渲染器const scene = initScene(renderer); // 初始化場(chǎng)景const camera = initCamera(5, 2, 10); // 初始化相機(jī)const controls = initControls(camera, renderer); // 初始化控制器controls.target.set(0, 0.5, 0); // 設(shè)置控制器的目標(biāo)const dracoLoader = new DRACOLoader(); // 創(chuàng)建 Draco 加載器dracoLoader.setDecoderPath("jsm/libs/draco/gltf/"); // 設(shè)置 Draco 解碼器路徑const loader = new GLTFLoader(); // 創(chuàng)建 GLTF 加載器loader.setDRACOLoader(dracoLoader); // 為 GLTF 加載器設(shè)置 Draco 加載器// 加載 GLTF 模型loader.load("models/gltf/LittlestTokyo.glb",(gltf: GLTF) => {const model = gltf.scene; // 獲取模型的場(chǎng)景model.position.set(1, 1, 0); // 設(shè)置模型的位置model.scale.set(0.01, 0.01, 0.01); // 設(shè)置模型的縮放scene.add(model); // 將模型添加到場(chǎng)景mixerRef.current = new THREE.AnimationMixer(model); // 創(chuàng)建動(dòng)畫混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放動(dòng)畫renderer.setAnimationLoop(animate); // 設(shè)置渲染循環(huán)},undefined,(e) => {console.error(e); // 處理加載錯(cuò)誤},);// 渲染循環(huán)函數(shù)/*** 每一幀的更新和渲染邏輯*/function animate() {const delta = clock.getDelta(); // 獲取時(shí)間間隔mixerRef.current && mixerRef.current.update(delta); // 更新動(dòng)畫混合器controls.update(); // 更新控制器statsRef.current && statsRef.current.update(); // 更新統(tǒng)計(jì)信息renderer.render(scene, camera); // 渲染場(chǎng)景和相機(jī)}// 處理窗口大小改變的函數(shù)/*** 處理窗口大小改變時(shí)的相機(jī)和渲染器更新*/function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight; // 更新相機(jī)的寬高比camera.updateProjectionMatrix(); // 更新相機(jī)的投影矩陣controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 鉤子useEffect(() => {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 將渲染器的 DOM 元素添加到引用的元素中statsRef.current = new Stats(); // 創(chuàng)建統(tǒng)計(jì)信息實(shí)例containerRef.current.appendChild(statsRef.current.dom); // 將統(tǒng)計(jì)信息的 DOM 元素添加到引用的元素中window.addEventListener("resize", onWindowResize); // 添加窗口大小改變的監(jiān)聽事件return () => {window.removeEventListener("resize", onWindowResize); // 清除窗口大小改變的監(jiān)聽事件renderer.setAnimationLoop(null); // 清除渲染循環(huán)};}, []);return <div ref={containerRef}></div>; // 返回一個(gè)帶有 ref 的 div 元素
}
export default Keyframes; // 導(dǎo)出 Keyframes 組件
通過<div ref={containerRef}></div>?創(chuàng)建一個(gè)dom元素,用于3D場(chǎng)景掛載
?模型加載與Draco解碼
示例模型提供的是壓縮后的模型,在頁面加載時(shí)需要進(jìn)行解壓,必須使用dracoLoader方法,設(shè)置解碼方法所在路徑。在通過GLTFLoader導(dǎo)入。示例如下:
const dracoLoader = new DRACOLoader(); // 創(chuàng)建 Draco 加載器dracoLoader.setDecoderPath("jsm/libs/draco/gltf/"); // 設(shè)置 Draco 解碼器路徑const loader = new GLTFLoader(); // 創(chuàng)建 GLTF 加載器loader.setDRACOLoader(dracoLoader); // 為 GLTF 加載器設(shè)置 Draco 加載器// 加載 GLTF 模型loader.load("models/gltf/LittlestTokyo.glb",(gltf: GLTF) => {//處理模型},undefined,(e) => {console.error(e); // 處理加載錯(cuò)誤},);
AnimationMixer 動(dòng)畫混合器?
AnimationMixer動(dòng)畫混合器是用于場(chǎng)景中特定對(duì)象的動(dòng)畫的播放器。當(dāng)場(chǎng)景中的多個(gè)對(duì)象獨(dú)立動(dòng)畫時(shí),每個(gè)對(duì)象都可以使用同一個(gè)動(dòng)畫混合器。
- 參數(shù):
rootObject
混合器播放的動(dòng)畫所屬的對(duì)象。就是包含動(dòng)畫模型的場(chǎng)景對(duì)象。- 常用參數(shù)和屬性:
.time
全局的混合器時(shí)間。.clipAction(AnimationClip)
返回所傳入的剪輯參數(shù)的AnimationAction
對(duì)象。AnimationAction
用來調(diào)度存儲(chǔ)在AnimationClip
中的動(dòng)畫。
AnimationClip
動(dòng)畫剪輯,是一個(gè)可重用的關(guān)鍵幀軌道集,它代表動(dòng)畫。
.getRoot()
返回混合器的根對(duì)象。.update()
推進(jìn)混合器時(shí)間并更新動(dòng)畫。在渲染函數(shù)中調(diào)用更新動(dòng)畫。
?在我們的示例中模型加載到場(chǎng)景時(shí)默認(rèn)時(shí)沒有動(dòng)畫的,也就是模型自身的動(dòng)畫比如小火車和風(fēng)扇小人都是不動(dòng)的。
?在模型加載的時(shí)候通過AnimationMixer開啟模型動(dòng)畫
// 加載 GLTF 模型loader.load("models/gltf/LittlestTokyo.glb",(gltf: GLTF) => {const model = gltf.scene; // 獲取模型的場(chǎng)景model.position.set(1, 1, 0); // 設(shè)置模型的位置model.scale.set(0.01, 0.01, 0.01); // 設(shè)置模型的縮放scene.add(model); // 將模型添加到場(chǎng)景mixerRef.current = new THREE.AnimationMixer(model); // 創(chuàng)建動(dòng)畫混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放動(dòng)畫renderer.setAnimationLoop(animate); // 設(shè)置渲染循環(huán)},undefined,(e) => {console.error(e); // 處理加載錯(cuò)誤},);
setAnimationLoop動(dòng)畫循環(huán)
??
在Three.js中,
setAnimationLoop
?方法是用來設(shè)置一個(gè)函數(shù),這個(gè)函數(shù)會(huì)在每一幀被調(diào)用來進(jìn)行渲染。這是必須的,因?yàn)樵赥hree.js中,渲染循環(huán)不是自動(dòng)開始的,你需要告訴渲染器何時(shí)以及如何進(jìn)行渲染。
以下是為什么加載模型時(shí)必須使用?setAnimationLoop
?的一些原因:
-
渲染控制:通過?
setAnimationLoop
,你可以控制渲染循環(huán)的開始和結(jié)束。如果你不設(shè)置它,即使模型加載完成,也不會(huì)自動(dòng)開始渲染過程。 -
動(dòng)畫播放:在你的代碼中,你使用了?
AnimationMixer
?來播放模型中的動(dòng)畫。這個(gè)動(dòng)畫需要在每一幀更新,以確保動(dòng)畫的連貫性和流暢性。setAnimationLoop
?允許你在每一幀更新動(dòng)畫狀態(tài)。 -
性能優(yōu)化:使用?
setAnimationLoop
?可以讓你在不需要渲染的時(shí)候停止渲染,比如在瀏覽器標(biāo)簽頁不可見時(shí),這樣可以節(jié)省資源并提高性能。 -
邏輯更新:在?
animate
?函數(shù)中,你可以執(zhí)行除了渲染之外的其他邏輯,比如更新動(dòng)畫、控制器和統(tǒng)計(jì)信息等。這些更新是渲染過程的一部分,需要在每一幀進(jìn)行。
如果你不使用?
setAnimationLoop
,你需要自己手動(dòng)創(chuàng)建一個(gè)循環(huán)來不斷調(diào)用?renderer.render(scene, camera)
,并且確保在合適的時(shí)機(jī)更新動(dòng)畫和其他邏輯。這通常是通過?requestAnimationFrame
?函數(shù)來實(shí)現(xiàn)的,但Three.js提供了?setAnimationLoop
?來簡(jiǎn)化這一過程。總之,
setAnimationLoop
?是Three.js中用來啟動(dòng)和維持渲染循環(huán)的關(guān)鍵方法,特別是在涉及到動(dòng)畫的情況下,它是必須的。
?可以看到模型自身的多個(gè)動(dòng)畫都動(dòng)起來了
Stats.js幀檢測(cè)工具?
不管是做游戲還是做普通網(wǎng)頁,在這個(gè)時(shí)代基本都離不開動(dòng)畫。說到動(dòng)畫,第一個(gè)聯(lián)想到的概念就是“幀”。這是用來衡量和描述動(dòng)畫是否流暢的一個(gè)單位。
示例程序的左上角有個(gè)工具窗口持續(xù)監(jiān)測(cè)FPS數(shù)值?
FPS是“Frames Per Second”的縮寫,意為“每秒幀數(shù)”。在視頻游戲和計(jì)算機(jī)圖形學(xué)中,FPS用來衡量顯示設(shè)備每秒鐘能夠顯示的靜止圖像(幀)的數(shù)量。這個(gè)數(shù)值越高,表示圖像更新得越快,視覺效果就越流暢。
在游戲領(lǐng)域,高FPS通常意味著更平滑的游戲體驗(yàn),尤其是在快速移動(dòng)或復(fù)雜場(chǎng)景中。然而,FPS并不是唯一影響游戲體驗(yàn)的因素,圖像質(zhì)量、響應(yīng)時(shí)間和系統(tǒng)穩(wěn)定性也同樣重要。
一般來說,人眼能夠感知到的流暢動(dòng)畫大約需要30FPS以上,而60FPS或更高則被認(rèn)為是高質(zhì)量游戲體驗(yàn)的標(biāo)準(zhǔn)。不過,這也取決于個(gè)人的視覺感知能力和對(duì)流暢度的要求。
用法?
在使用?
npm install three?
下載的依賴包中已經(jīng)包含了?Stats.js
?了
可以這樣引入到項(xiàng)目中
import Stats from "three/examples/jsm/libs/stats.module.js";
通過new Stats()方法創(chuàng)建一個(gè)stats實(shí)例?。默認(rèn)showPanel是0,顯示FPS面板。
?通過showPanel方法切換顯示方式;可以根據(jù)dom改變stats面板的位置,使用示例如下
const statsRef = useRef<Stats>(); // 創(chuàng)建用于引用統(tǒng)計(jì)信息的 refstatsRef.current = new Stats(); // 創(chuàng)建統(tǒng)計(jì)信息實(shí)例statsRef.current.showPanel(1);statsRef.current.dom.style.position = "absolute"; // 設(shè)置統(tǒng)計(jì)信息的 DOM 元素的位置statsRef.current.dom.style.top = "0px"; // 設(shè)置統(tǒng)計(jì)信息的 DOM 元素的位置statsRef.current.dom.style.left = "0px"; // 設(shè)置統(tǒng)計(jì)信息的 DOM 元素的位置
通過操作dom的方式將stats節(jié)點(diǎn)追加到3D場(chǎng)景中
containerRef.current.appendChild(statsRef.current.dom); // 將統(tǒng)計(jì)信息的 DOM 元素添加到引用的元素中
?默認(rèn)就顯示在屏幕的左上角
當(dāng)點(diǎn)擊該面板時(shí)還可以切換監(jiān)聽的類型
?響應(yīng)式窗口
頁面加載時(shí)給了初始的renderer的寬高,但是如果用戶使用過程中可視區(qū)域發(fā)生了變化renderer無法自動(dòng)使用屏幕
?可以在useEffect里通過事件監(jiān)聽瀏覽器的resize事件,當(dāng)瀏覽器尺寸變化時(shí)重新以最新的寬高設(shè)為renderer的尺寸信息
// 處理窗口大小改變的函數(shù)/*** 處理窗口大小改變時(shí)的相機(jī)和渲染器更新*/function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight; // 更新相機(jī)的寬高比camera.updateProjectionMatrix(); // 更新相機(jī)的投影矩陣controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 鉤子useEffect(() => {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 將渲染器的 DOM 元素添加到引用的元素中statsRef.current = new Stats(); // 創(chuàng)建統(tǒng)計(jì)信息實(shí)例containerRef.current.appendChild(statsRef.current.dom); // 將統(tǒng)計(jì)信息的 DOM 元素添加到引用的元素中window.addEventListener("resize", onWindowResize); // 添加窗口大小改變的監(jiān)聽事件return () => {window.removeEventListener("resize", onWindowResize); // 清除窗口大小改變的監(jiān)聽事件renderer.setAnimationLoop(null); // 清除渲染循環(huán)};}, []);
完整代碼?
import { useEffect, useRef } from "react";
import * as THREE from "three";
import Stats from "three/examples/jsm/libs/stats.module.js";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";// 初始化渲染器的函數(shù)
/*** 初始化 WebGL 渲染器* @returns {THREE.WebGLRenderer} 創(chuàng)建并配置好的渲染器實(shí)例*/
// 初始化渲染
function initRender(): THREE.WebGLRenderer {// 創(chuàng)建一個(gè)WebGL渲染器const renderer = new THREE.WebGLRenderer({ antialias: true });// 根據(jù)設(shè)備像素比設(shè)置渲染器像素比renderer.setPixelRatio(window.devicePixelRatio);// 設(shè)置渲染器大小renderer.setSize(window.innerWidth, window.innerHeight);return renderer;
}// 初始化場(chǎng)景的函數(shù)
/*** 初始化場(chǎng)景* @param {THREE.WebGLRenderer} renderer - 渲染器實(shí)例* @returns {THREE.Scene} 創(chuàng)建并配置好的場(chǎng)景實(shí)例*/
function initScene(renderer: THREE.WebGLRenderer) {// 創(chuàng)建 PMREM 生成器const pmremGenerator = new THREE.PMREMGenerator(renderer);// 創(chuàng)建場(chǎng)景const scene = new THREE.Scene();// 設(shè)置場(chǎng)景背景scene.background = new THREE.Color(0xbfe3dd);// 設(shè)置場(chǎng)景環(huán)境scene.environment = pmremGenerator.fromScene(new RoomEnvironment(renderer), 0.04).texture;return scene;
}// 初始化相機(jī)的函數(shù)
/*** 初始化相機(jī)* @param {number} x - 相機(jī)在 x 軸的位置* @param {number} y - 相機(jī)在 y 軸的位置* @param {number} z - 相機(jī)在 z 軸的位置* @returns {THREE.PerspectiveCamera} 創(chuàng)建并配置好位置的相機(jī)實(shí)例*/
function initCamera(x: number, y: number, z: number) {const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);camera.position.set(x, y, z);return camera;
}// 初始化控制器的函數(shù)
/*** 初始化軌道控制器* @param {THREE.PerspectiveCamera} camera - 相機(jī)實(shí)例* @param {THREE.WebGLRenderer} renderer - 渲染器實(shí)例* @returns {OrbitControls} 創(chuàng)建并配置好的軌道控制器實(shí)例*/
function initControls(camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) {const controls = new OrbitControls(camera, renderer.domElement);controls.update();controls.enablePan = false;controls.enableDamping = true;return controls;
}/*** Keyframes 組件函數(shù)*/
function Keyframes() {const containerRef = useRef<HTMLDivElement>(null); // 創(chuàng)建用于引用 HTML 元素的 refconst clock = new THREE.Clock(); // 創(chuàng)建時(shí)鐘實(shí)例const statsRef = useRef<Stats>(); // 創(chuàng)建用于引用統(tǒng)計(jì)信息的 refconst mixerRef = useRef<THREE.AnimationMixer>(); // 創(chuàng)建用于引用動(dòng)畫混合器的 refconst renderer = initRender(); // 初始化渲染器const scene = initScene(renderer); // 初始化場(chǎng)景const camera = initCamera(5, 2, 10); // 初始化相機(jī)const controls = initControls(camera, renderer); // 初始化控制器controls.target.set(0, 0.5, 0); // 設(shè)置控制器的目標(biāo)const dracoLoader = new DRACOLoader(); // 創(chuàng)建 Draco 加載器dracoLoader.setDecoderPath("jsm/libs/draco/gltf/"); // 設(shè)置 Draco 解碼器路徑const loader = new GLTFLoader(); // 創(chuàng)建 GLTF 加載器loader.setDRACOLoader(dracoLoader); // 為 GLTF 加載器設(shè)置 Draco 加載器// 加載 GLTF 模型loader.load("models/gltf/LittlestTokyo.glb",(gltf: GLTF) => {const model = gltf.scene; // 獲取模型的場(chǎng)景model.position.set(1, 1, 0); // 設(shè)置模型的位置model.scale.set(0.01, 0.01, 0.01); // 設(shè)置模型的縮放scene.add(model); // 將模型添加到場(chǎng)景mixerRef.current = new THREE.AnimationMixer(model); // 創(chuàng)建動(dòng)畫混合器mixerRef.current.clipAction(gltf.animations[0]).play(); // 播放動(dòng)畫renderer.setAnimationLoop(animate); // 設(shè)置渲染循環(huán)},undefined,(e) => {console.error(e); // 處理加載錯(cuò)誤},);// 渲染循環(huán)函數(shù)/*** 每一幀的更新和渲染邏輯*/function animate() {const delta = clock.getDelta(); // 獲取時(shí)間間隔mixerRef.current && mixerRef.current.update(delta); // 更新動(dòng)畫混合器controls.update(); // 更新控制器statsRef.current && statsRef.current.update(); // 更新統(tǒng)計(jì)信息renderer.render(scene, camera); // 渲染場(chǎng)景和相機(jī)}// 處理窗口大小改變的函數(shù)/*** 處理窗口大小改變時(shí)的相機(jī)和渲染器更新*/function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight; // 更新相機(jī)的寬高比camera.updateProjectionMatrix(); // 更新相機(jī)的投影矩陣controls.update(); // 更新控制器renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器的大小}// 使用 useEffect 鉤子useEffect(() => {if (!containerRef.current) return;containerRef.current.appendChild(renderer.domElement); // 將渲染器的 DOM 元素添加到引用的元素中statsRef.current = new Stats(); // 創(chuàng)建統(tǒng)計(jì)信息實(shí)例containerRef.current.appendChild(statsRef.current.dom); // 將統(tǒng)計(jì)信息的 DOM 元素添加到引用的元素中window.addEventListener("resize", onWindowResize); // 添加窗口大小改變的監(jiān)聽事件return () => {window.removeEventListener("resize", onWindowResize); // 清除窗口大小改變的監(jiān)聽事件renderer.setAnimationLoop(null); // 清除渲染循環(huán)};}, []);return <div ref={containerRef}></div>; // 返回一個(gè)帶有 ref 的 div 元素
}export default Keyframes; // 導(dǎo)出 Keyframes 組件