3D個人簡歷網站 4.小島

1.模型素材

在Sketchfab上下載狐貍島模型,然后轉換為素材資源asset,嫌麻煩直接在網盤鏈接下載素材,

  • Fox’s islands
  • https://sketchfab.com/3d-models/foxs-islands-163b68e09fcc47618450150be7785907
  • https://gltf.pmnd.rs/

素材夸克網盤:

鏈接:https://pan.quark.cn/s/f02d30f07286

提取碼:Yn3k


在 vite.config.js 或 vite.config.ts 文件里添加 assetsInclude 配置項,讓 Vite 把 .glb 文件當作靜態資源處理。
vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'// https://vite.dev/config/
export default defineConfig({plugins: [react()],assetsInclude: ['**/*.glb']
})

2.小島代碼

src下創建文件夾models,models下創建Island.jsx

Island.jsx

/*** IMPORTANT: 將 glTF 模型加載到 Three.js 場景中是一項復雜的工作。* 在我們能夠配置或動畫化模型的網格之前,需要遍歷模型網格的每個部分并單獨保存。** 但幸運的是,有一個工具可以將 gltf 或 glb 文件轉換為 jsx 組件。* 對于這個模型,請訪問 https://gltf.pmnd.rs/ * 獲取代碼,然后添加其余內容。* 你不必從零開始編寫所有代碼*/// 從 @react-spring/three 庫導入 a 組件,用于創建動畫效果
import { a } from "@react-spring/three";
// 從 react 庫導入 useEffect 和 useRef 鉤子,useEffect 用于處理副作用,useRef 用于創建可變引用
import { useEffect, useRef } from "react";
// 從 @react-three/drei 庫導入 useGLTF 鉤子,用于加載 glTF 模型
import { useGLTF } from "@react-three/drei";
// 從 @react-three/fiber 庫導入 useFrame 和 useThree 鉤子,useFrame 用于在每一幀更新時執行代碼,useThree 用于獲取 Three.js 上下文
import { useFrame, useThree } from "@react-three/fiber";// 導入島嶼模型的 glb 文件
import islandScene from "../assets/3d/island.glb";/*** Island 組件,用于渲染 3D 島嶼模型,并處理模型的旋轉交互和階段設置。* @param {Object} props - 組件的屬性對象* @param {boolean} props.isRotating - 指示島嶼是否正在旋轉的狀態* @param {Function} props.setIsRotating - 用于設置島嶼旋轉狀態的函數* @param {Function} props.setCurrentStage - 用于設置當前階段的函數* @param {*} props.currentFocusPoint - 當前焦點點* @returns {JSX.Element} 渲染的 3D 島嶼模型元素*/
export function Island({isRotating,setIsRotating,setCurrentStage,currentFocusPoint,...props
}) {// 創建一個 ref 用于引用島嶼模型const islandRef = useRef();// 使用 useThree 鉤子獲取 Three.js 渲染器和視口信息const { gl, viewport } = useThree();// 使用 useGLTF 鉤子加載島嶼模型,獲取模型的節點和材質const { nodes, materials } = useGLTF(islandScene);// 創建一個 ref 用于存儲上一次鼠標的 x 坐標const lastX = useRef(0);// 創建一個 ref 用于存儲旋轉速度const rotationSpeed = useRef(0);// 定義阻尼因子,用于控制旋轉減速效果const dampingFactor = 0.95;// 處理指針(鼠標或觸摸)按下事件const handlePointerDown = (event) => {// 阻止事件冒泡和默認行為event.stopPropagation();event.preventDefault();// 設置島嶼為旋轉狀態setIsRotating(true);// 根據事件類型(觸摸或鼠標)獲取當前指針的 x 坐標const clientX = event.touches ? event.touches[0].clientX : event.clientX;// 存儲當前指針的 x 坐標,供后續計算使用lastX.current = clientX;};// 處理指針(鼠標或觸摸)抬起事件const handlePointerUp = (event) => {// 阻止事件冒泡和默認行為event.stopPropagation();event.preventDefault();// 設置島嶼為停止旋轉狀態setIsRotating(false);};// 處理指針(鼠標或觸摸)移動事件const handlePointerMove = (event) => {// 阻止事件冒泡和默認行為event.stopPropagation();event.preventDefault();if (isRotating) {// 如果島嶼正在旋轉,根據事件類型(觸摸或鼠標)獲取當前指針的 x 坐標const clientX = event.touches ? event.touches[0].clientX : event.clientX;// 計算指針在水平方向上的移動距離,相對于視口寬度的比例const delta = (clientX - lastX.current) / viewport.width;// 根據指針移動距離更新島嶼的旋轉角度islandRef.current.rotation.y += delta * 0.01 * Math.PI;// 更新上一次指針的 x 坐標lastX.current = clientX;// 更新旋轉速度rotationSpeed.current = delta * 0.01 * Math.PI;}};// 處理鍵盤按下事件const handleKeyDown = (event) => {if (event.key === "ArrowLeft") {// 如果按下左箭頭鍵,且島嶼未旋轉,則設置為旋轉狀態if (!isRotating) setIsRotating(true);// 向左旋轉島嶼islandRef.current.rotation.y += 0.005 * Math.PI;// 設置旋轉速度rotationSpeed.current = 0.007;} else if (event.key === "ArrowRight") {// 如果按下右箭頭鍵,且島嶼未旋轉,則設置為旋轉狀態if (!isRotating) setIsRotating(true);// 向右旋轉島嶼islandRef.current.rotation.y -= 0.005 * Math.PI;// 設置旋轉速度rotationSpeed.current = -0.007;}};// 處理鍵盤抬起事件const handleKeyUp = (event) => {if (event.key === "ArrowLeft" || event.key === "ArrowRight") {// 如果松開左箭頭鍵或右箭頭鍵,設置島嶼為停止旋轉狀態setIsRotating(false);}};// 處理觸摸開始事件,用于移動設備const handleTouchStart = (e) => {// 阻止事件冒泡和默認行為e.stopPropagation();e.preventDefault();// 設置島嶼為旋轉狀態setIsRotating(true);// 獲取觸摸點的 x 坐標const clientX = e.touches ? e.touches[0].clientX : e.clientX;// 存儲當前觸摸點的 x 坐標lastX.current = clientX;}// 處理觸摸結束事件,用于移動設備const handleTouchEnd = (e) => {// 阻止事件冒泡和默認行為e.stopPropagation();e.preventDefault();// 設置島嶼為停止旋轉狀態setIsRotating(false);}// 處理觸摸移動事件,用于移動設備const handleTouchMove = (e) => {// 阻止事件冒泡和默認行為e.stopPropagation();e.preventDefault();if (isRotating) {// 如果島嶼正在旋轉,獲取觸摸點的 x 坐標const clientX = e.touches ? e.touches[0].clientX : e.clientX;// 計算觸摸點在水平方向上的移動距離,相對于視口寬度的比例const delta = (clientX - lastX.current) / viewport.width;// 根據觸摸移動距離更新島嶼的旋轉角度islandRef.current.rotation.y += delta * 0.01 * Math.PI;// 更新上一次觸摸點的 x 坐標lastX.current = clientX;// 更新旋轉速度rotationSpeed.current = delta * 0.01 * Math.PI;}}// 使用 useEffect 鉤子添加和移除事件監聽器useEffect(() => {// 獲取 Three.js 渲染器的畫布元素const canvas = gl.domElement;// 添加指針按下、抬起、移動事件監聽器canvas.addEventListener("pointerdown", handlePointerDown);canvas.addEventListener("pointerup", handlePointerUp);canvas.addEventListener("pointermove", handlePointerMove);// 添加鍵盤按下、抬起事件監聽器window.addEventListener("keydown", handleKeyDown);window.addEventListener("keyup", handleKeyUp);// 添加觸摸開始、結束、移動事件監聽器canvas.addEventListener("touchstart", handleTouchStart);canvas.addEventListener("touchend", handleTouchEnd);canvas.addEventListener("touchmove", handleTouchMove);// 組件卸載時移除事件監聽器,避免內存泄漏return () => {canvas.removeEventListener("pointerdown", handlePointerDown);canvas.removeEventListener("pointerup", handlePointerUp);canvas.removeEventListener("pointermove", handlePointerMove);window.removeEventListener("keydown", handleKeyDown);window.removeEventListener("keyup", handleKeyUp);canvas.removeEventListener("touchstart", handleTouchStart);canvas.removeEventListener("touchend", handleTouchEnd);canvas.removeEventListener("touchmove", handleTouchMove);};}, [gl, handlePointerDown, handlePointerUp, handlePointerMove]);// 使用 useFrame 鉤子在每一幀更新時執行代碼useFrame(() => {// 如果島嶼未旋轉,應用阻尼效果使旋轉逐漸減速if (!isRotating) {// 應用阻尼因子,降低旋轉速度rotationSpeed.current *= dampingFactor;// 當旋轉速度非常小時,停止旋轉if (Math.abs(rotationSpeed.current) < 0.001) {rotationSpeed.current = 0;}// 根據旋轉速度更新島嶼的旋轉角度islandRef.current.rotation.y += rotationSpeed.current;} else {// 當島嶼正在旋轉時,根據島嶼的朝向確定當前階段const rotation = islandRef.current.rotation.y;/*** 對旋轉值進行歸一化處理,確保其保持在 [0, 2 * Math.PI] 范圍內。* 目的是保證旋轉值在特定范圍內,避免出現非常大或負的旋轉值導致的潛在問題。* 以下是這段代碼的分步解釋:* 1. rotation % (2 * Math.PI) 計算旋轉值除以 2 * Math.PI 的余數。*    這實際上會在旋轉值達到一整圈(360 度)時將其環繞,使其保持在 0 到 2 * Math.PI 的范圍內。* 2. (rotation % (2 * Math.PI)) + 2 * Math.PI 將步驟 1 的結果加上 2 * Math.PI。*    這樣做是為了確保即使在步驟 1 的取模運算后結果為負,該值仍然為正且在 0 到 2 * Math.PI 的范圍內。* 3. 最后,((rotation % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI) 對步驟 2 得到的值再次應用取模運算。*    這一步保證了該值始終保持在 0 到 2 * Math.PI 的范圍內,這在弧度制中相當于一整圈。*/const normalizedRotation =((rotation % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);// 根據島嶼的朝向設置當前階段switch (true) {case normalizedRotation >= 5.45 && normalizedRotation <= 5.85:setCurrentStage(4);break;case normalizedRotation >= 0.85 && normalizedRotation <= 1.3:setCurrentStage(3);break;case normalizedRotation >= 2.4 && normalizedRotation <= 2.6:setCurrentStage(2);break;case normalizedRotation >= 4.25 && normalizedRotation <= 4.75:setCurrentStage(1);break;default:setCurrentStage(null);}}});return (// {島嶼 3D 模型來源: https://sketchfab.com/3d-models/foxs-islands-163b68e09fcc47618450150be7785907}// 使用 a.group 組件包裹島嶼模型,支持動畫效果<a.group ref={islandRef} {...props}><meshgeometry={nodes.polySurface944_tree_body_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.polySurface945_tree1_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.polySurface946_tree2_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.polySurface947_tree1_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.polySurface948_tree_body_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.polySurface949_tree_body_0.geometry}material={materials.PaletteMaterial001}/><meshgeometry={nodes.pCube11_rocks1_0.geometry}material={materials.PaletteMaterial001}/></a.group>);
}// 導出 Island 組件作為默認導出,方便其他文件引入使用
export default Island

3.主頁代碼

Home.jsx

// 導入 React 庫和 Suspense 組件,Suspense 用于處理異步組件加載
// 當異步組件還未加載完成時,可顯示一個 fallback 組件
import React, { Suspense } from 'react'
// 從 @react-three/fiber 庫中導入 Canvas 組件,用于創建 Three.js 渲染上下文,
// 借助該組件能在 React 應用里渲染 3D 場景
import { Canvas } from '@react-three/fiber'
// 從 ../components/Loader 路徑導入 Loader 組件,該組件會在異步加載時顯示加載狀態
import Loader from '../components/Loader'
// 從 ../models/Island 路徑導入 Island 組件,此組件用于渲染 3D 島嶼模型
import { Island } from "../models/Island"// <div className='absolute top-28 left-0 right-0 z-10 flex items-center justify-center'>
//   彈出窗口
// </div>/*** Home 組件,作為應用的主頁組件。* 該組件會依據屏幕尺寸對 Island 組件的縮放、位置和旋轉進行調整,* 并且在 Canvas 中渲染 Island 組件,同時處理異步加載狀態。* @returns {JSX.Element} 渲染后的 JSX 元素*/
const Home = () => {/*** 根據屏幕尺寸調整 Island 組件的縮放、位置和旋轉。* @returns {Array} 包含屏幕縮放比例、位置和旋轉值的數組*/const adjustIslandForScreenSize = () => {// 初始化屏幕縮放比例,初始值設為 nulllet screenScale = null// 初始化 Island 組件的位置,默認值為 [0, -6.5, -43]let screenPosition = [0, -6.5, -43]// 初始化 Island 組件的旋轉值,默認值為 [0.1, 4.7, 0]let rotation = [0.1, 4.7, 0]// 判斷當前窗口寬度是否小于 768pxif (window.innerWidth < 768) {// 若窗口寬度小于 768px,將屏幕縮放比例設置為 [0.9, 0.9, 0.9]screenScale = [0.9, 0.9, 0.9];} else {// 若窗口寬度大于等于 768px,將屏幕縮放比例設置為 [1, 1, 1]screenScale = [1, 1, 1];}// 返回包含屏幕縮放比例、位置和旋轉值的數組return [screenScale, screenPosition, rotation];}// 調用 adjustIslandForScreenSize 函數,獲取調整后的島嶼縮放、位置和旋轉參數const [islandScale, islandPosition, islandRotation] = adjustIslandForScreenSize();return (// 創建一個 section 元素,寬度和高度占滿整個屏幕,且采用相對定位<section className='w-full h-screen relative'>{/* 創建 Three.js 渲染畫布,寬度和高度占滿整個屏幕,背景透明,并設置相機的近裁剪面和遠裁剪面 */}<CanvasclassName='w-full h-screen bg-transparent'camera={{ near:0.1, far:1000 }}>{/* 使用 Suspense 組件處理異步加載,當 Island 組件未加載完成時,顯示 Loader 組件 */}<Suspense fallback={<Loader/>}>{/* 添加定向光,為場景提供有方向的光照 */}<directionalLight/>{/* 添加環境光,為場景提供全局均勻的光照 */}<ambientLight />{/* 添加點光源,從一個點向四周發射光線 */}<pointLight />{/* 添加聚光燈,發射出類似圓錐形的光線 */}<spotLight />{/* 添加半球光,模擬天空和地面的光照效果 */}<hemisphereLight />{/* 渲染 Island 組件,設置其位置、縮放和旋轉屬性 */}<Islandposition={islandPosition}scale={islandScale}rotation={islandRotation}/></Suspense></Canvas></section>)
}// 導出 Home 組件,供其他文件引入使用
export default Home

4.安裝依賴運行

npm install @react-spring/three
npm run dev

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83713.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83713.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83713.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

智能開發工具PhpStorm v2025.1——增強AI輔助編碼功能

PhpStorm是一個輕量級且便捷的PHP IDE&#xff0c;其旨在提高用戶效率&#xff0c;可深刻理解用戶的編碼&#xff0c;提供智能代碼補全&#xff0c;快速導航以及即時錯誤檢查。可隨時幫助用戶對其編碼進行調整&#xff0c;運行單元測試或者提供可視化debug功能。 立即獲取PhpS…

Spark 的運行模式(--master) 和 部署方式(--deploy-mode)

Spark 的 運行模式&#xff08;--master&#xff09; 和 部署方式&#xff08;--deploy-mode&#xff09;&#xff0c;兩者的核心區別在于 資源調度范圍 和 Driver 進程的位置。 一、核心概念對比 維度--master&#xff08;運行模式&#xff09;--deploy-mode&#xff08;部署…

sqli—labs第八關——布爾盲注

一&#xff1a;確定注入類型 按照我們之前的步驟來 輸入 ?id1 and 11-- ?id1 and 12-- 界面正常 第二行界面異常空白 所以注入類型為單引號閉合型 二&#xff1a; 布爾盲注 1.判斷是否使用條件 &#xff08;1&#xff09;&#xff1a;存在注入但不會直接顯示查詢結果 …

ARP 原理總結

&#x1f310; 一、ARP 原理總結 ARP&#xff08;Address Resolution Protocol&#xff09;是用于通過 IP 地址解析 MAC 地址的協議&#xff0c;工作在 鏈路層 與 網絡層之間&#xff08;OSI 模型的第三層與第二層之間&#xff09;。 &#x1f501; ARP通信過程&#xff1a; …

SpringCloud——EureKa

目錄 1.前言 1.微服務拆分及遠程調用 3.EureKa注冊中心 遠程調用的問題 eureka原理 搭建EureKaServer 服務注冊 服務發現 1.前言 分布式架構&#xff1a;根據業務功能對系統進行拆分&#xff0c;每個業務模塊作為獨立項目開發&#xff0c;稱為服務。 優點&#xff1a; 降…

機頂盒刷機筆記

疑難雜癥解決 hitool線刷網口不通tftp超時--》關閉防火墻cm201-2卡刷所有包提示失敗abort install--》找個卡刷包只刷fastboot分區再卡刷就能通過了&#xff08;cm201救磚包 (M8273版子&#xff09;&#xff09; 刷機工具 海兔燒錄工具HiTool-STB-5.3.12工具&#xff0c;需要…

Linux動靜態庫制作與原理

什么是庫 庫是寫好的現有的&#xff0c;成熟的&#xff0c;可以復用的代碼。現實中每個程序都要依賴很多基礎的底層庫&#xff0c;不可能每個人的代碼都從零開始&#xff0c;因此庫的存在意義非同尋常。 本質上來說庫是一種可執行代碼的二進制形式&#xff0c;可以被操作系統…

如何通過小智AI制作會說話的機器人玩具?

一、硬件準備與組裝 1. 核心硬件選擇 主控芯片&#xff1a;選擇支持無線網絡連接、音頻處理和可編程接口的嵌入式開發板 音頻模塊&#xff1a;配備拾音麥克風與小型揚聲器&#xff0c;確保語音輸入/輸出功能 顯示模塊&#xff1a;選擇適配的交互顯示屏用于可視化反饋 擴展模…

如何控制郵件發送頻率避免打擾用戶

一、用戶行為 監測用戶與郵件的互動數據&#xff0c;如打開率、點擊率下滑或退訂申請增多&#xff0c;可能是發送頻率過高的警示信號。利用郵件營銷平臺的分析工具&#xff0c;識別這些指標的變動趨勢&#xff0c;為調整提供依據。 二、行業特性與受眾差異 不同行業用戶對郵…

定積分的“偶倍奇零”性質及其使用條件

定積分的“偶倍奇零”性質是針對對稱區間上的奇偶函數積分的重要簡化方法。以下是其核心內容和應用要點&#xff1a; ?一、基本性質 ?偶函數&#xff08;偶倍&#xff09;? 若 f(x) 在 [?a,a] 上為偶函數&#xff08;即 f(?x)f(x)&#xff09;&#xff0c;則&#xff1a; …

如何在 Windows 11 或 10 上安裝 Fliqlo 時鐘屏保

了解如何在 Windows 11 或 10 上安裝 Fliqlo,為您的 PC 或筆記本電腦屏幕添加一個翻轉時鐘屏保以顯示時間。 Fliqlo 是一款適用于 Windows 和 macOS 平臺的免費時鐘屏保。它也適用于移動設備,但僅限于 iPhone 和 iPad。Fliqlo 的主要功能是在用戶不活動時在 PC 或筆記本電腦…

【C/C++】C++并發編程:std::async與std::thread深度對比

文章目錄 C并發編程&#xff1a;std::async與std::thread深度對比1 核心設計目的以及區別2 詳細對比分析3 代碼對比示例4 適用場景建議5 總結 C并發編程&#xff1a;std::async與std::thread深度對比 在 C 中&#xff0c;std::async 和 std::thread 都是用于并發編程的工具&am…

Axure疑難雜癥:垂直菜單展開與收回(4大核心問題與專家級解決方案)

親愛的小伙伴,在您瀏覽之前,煩請關注一下,在此深表感謝!如有幫助請訂閱專欄! Axure產品經理精品視頻課已登錄CSDN可點擊學習https://edu.csdn.net/course/detail/40420 課程主題:垂直菜單展開與收回 主要內容:超長菜單實現、展開與收回bug解釋、Axure9版本限制等問題解…

ASIC和FPGA,到底應該選擇哪個?

ASIC和FPGA各有優缺點。 ASIC針對特定需求&#xff0c;具有高性能、低功耗和低成本&#xff08;在大規模量產時&#xff09;&#xff1b;但設計周期長、成本高、風險大。FPGA則適合快速原型驗證和中小批量應用&#xff0c;開發周期短&#xff0c;靈活性高&#xff0c;適合初創企…

DAY 30 模塊和庫的導入

知識點回顧&#xff1a; 1.導入官方庫的三種手段 2.導入自定義庫/模塊的方式 3.導入庫/模塊的核心邏輯&#xff1a;找到根目錄&#xff08;python解釋器的目錄和終端的目錄不一致&#xff09; 作業&#xff1a;自己新建幾個不同路徑文件嘗試下如何導入 import math # 導入…

MyBatis:動態SQL

文章目錄 動態SQLif標簽trim標簽where標簽set標簽foreach標簽include標簽和sql標簽 Mybatis動態SQL的官方文檔&#xff1a; https://mybatis.net.cn/dynamic-sql.html 動態SQL 動態SQL是 MyBatis的強大特性之一,如果是使用JDBC根據不同條件拼接sql很麻煩&#xff0c;例如拼接…

Java - Junit框架

單元測試&#xff1a;針對最小的功能單元(方法)&#xff0c;編寫測試代碼對該功能進行正確性測試。 Junit&#xff1a;Java語言實現的單元測試框架&#xff0c;很多開發工具已經集成了Junit框架&#xff0c;如IDEA。 優點 編寫的測試代碼很靈活&#xff0c;可以指某個測試方法…

學生成績管理系統Java實戰(Spring Boot+MyBatis Plus)

文章目錄 一、系統需求分析&#xff08;避坑指南&#xff09;二、技術選型&#xff08;2024新版&#xff09;三、數據庫設計&#xff08;三大核心表&#xff09;1. 學生表&#xff08;student&#xff09;2. 課程表&#xff08;course&#xff09;3. 成績表&#xff08;score&a…

MySQL安裝實戰指南:Mac、Windows與Docker全平臺詳解

MySQL作為世界上最流行的開源關系型數據庫&#xff0c;是每位開發者必須掌握的基礎技能。本指南將手把手帶你完成三大平臺的MySQL安裝&#xff0c;從下載到配置&#xff0c;每個步驟都配有詳細說明和截圖&#xff0c;特別適合新手學習。 一、Mac系統安裝MySQL 1.1 通過Homebre…

多模態大語言模型arxiv論文略讀(七十九)

AIM: Let Any Multi-modal Large Language Models Embrace Efficient In-Context Learning ?? 論文標題&#xff1a;AIM: Let Any Multi-modal Large Language Models Embrace Efficient In-Context Learning ?? 論文作者&#xff1a;Jun Gao, Qian Qiao, Ziqiang Cao, Zi…