尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从5MB预算到轻量3D作品集:Three.js性能优化实战

从5MB预算到轻量3D作品集:Three.js性能优化实战 一个 3D 个人作品集网站3D portfolio website会在打开页面的一瞬间形成视觉冲击但代价也很明显模型、贴图、脚本、字体一旦失控首页体积就会膨胀到几十 MB。Show HN 上有人展示了自己花 2.5 年打磨的 3D 作品集网站整站控制在 5MB 以内。这个数据比很多普通图文站还要克制也让“3D 必然重”这个印象变得值得重新审视。如果你也在做个人主页、WebGL 创意项目或 Three.js 场景下面会沿着从零开始搭建一个轻量 3D 作品集的路径把体积控制、性能验证和上线排查一起讲透。读者最好先掌握基本的 HTML、CSS、JavaScript 和 npm 命令不需要事先熟悉 Three.js。为了避开“3D 作品集一定很复杂”的惯性本文会先解释 5MB 预算的意义再给出可复现的工程结构然后逐步加入场景、相机、交互、模型加载和构建验证。整个过程中会反复强调一个判断标准所有新增内容都要回答“它比原来的方案值得多少体积”。1. 先理解“3D 作品集网站”和“5MB 预算”到底在解决什么问题1.1 作品集形态演进从图文页到 3D 交互场景个人作品集网站最常见的形态有三种。第一种是纯图文页面用图片、文字和视频展示项目优点是开发成本低、SEO 友好缺点是容易淹没在大量同质化页面中。第二种是带前端动效的页面使用滚动动画、粒子效果或 WebGL 背景视觉表现更好但仍然以内容浏览为主。第三种就是本文关注的 3D 交互场景用户进入页面后可以旋转视角、靠近观察模型、点击作品并查看详情。第三种形态的优势是记忆点强。访客在几秒内就能判断这个页面的创作者是否具备空间设计、交互设计和前端工程能力。但它的风险同样集中在体积上一个高精度 glTF 模型可能超过 20MB一套 2K 纹理可能达到十几 MB再加上 Three.js 运行时、交互脚本和自定义字体首屏很容易超过 10MB。3D 作品集因此经常陷入“效果不错但加载太慢”的尴尬。真正优秀的 3D 作品集不是把所有 3D 能力都堆到首页而是先用一个轻量场景建立印象再把重量级作品放到用户主动点击后才加载。这样既保留了 3D 的独特性也守住了基础的加载体验。1.2 5MB 不是玄学数字而是一条性能预算“整站小于 5MB”听起来像是一个拍脑袋决定的审美标准但在前端性能领域这是典型的性能预算Performance Budget。性能预算的意思是在项目开始前就定义好资源体积上限所有新增功能都必须在这个预算内完成如果超限就要优化旧资源或放弃不重要的新资源。5MB 作为个人作品集预算有其合理性。普通 4G 网络下5MB 的总资源体积配合 gzip/Brotli 压缩通常能在数秒内完成首屏加载如果部署在 CDN 节点较近的位置体验会更好。相比之下一个 30MB 的 3D 页面即使用缓存第二次进入也会产生明显的资源解析压力在移动端更容易卡顿。这里需要区分“总资源体积”和“首屏请求体积”。5MB 预算建议包含所有初始加载资源但不一定包含用户点击某个作品后才异步加载的高精度模型。合理的设计是整站初始资源控制在 4MB 左右预留 1MB 给必需交互资源重量级模型全部走按需加载。资源类型典型体积范围对加载的影响HTML/CSS/JS100KB - 500KB阻塞渲染直接影响首次内容展示3D 模型GLB1MB - 20MB影响场景可交互时间纹理贴图500KB - 10MB影响表面细节和显存占用字体200KB - 3MB影响文字渲染和页面体积图片作品截图1MB - 10MB影响作品列表加载速度1.3 2.5 年打磨的真正难点取舍、一致性和可维护性花 2.5 年做一个作品集网站重点不在于写了多少代码而在于长期迭代中如何守住一致性。很多 3D 项目第一个月就能跑通但半年后回来看灯光参数、模型比例、交互手感、色调风格可能已经无法统一。打磨的本质是对每一条曲线、每一个转场、每一次加载反馈做决策并且形成文档或代码注释免得三个月后又推翻自己。从工程角度看2.5 年也意味着技术栈会经历多次升级。Three.js 的版本更新、打包工具的迁移、模型压缩工具的演进都可能让旧代码失效。所以作品集项目从一开始就要模块化让模型、场景配置、交互逻辑和页面内容分离。这样即使底层技术升级也不需要重写整站。2. 技术选型和环境准备用 Three.js Vite 搭一个可控制体积的工程2.1 WebGL 方案选型Three.js、Babylon.js、React Three Fiber 怎么选3D 作品集在 Web 端通常基于 WebGL 实现。原生 WebGL 能最精确控制性能但开发效率太低不适合一个人长期维护。Three.js 是目前社区最成熟、示例最多的库适合快速搭建自定义场景。Babylon.js 在游戏化场景和编辑器能力上更强但学习曲线和包体并不比 Three.js 更优。React Three Fiber 是 Three.js 在 React 世界的声明式封装适合熟悉 React 的团队但会增加一层抽象和依赖体积。如果你的项目使用 Vue也可以把 Three.js 场景封装成一个 Vue 组件通过 ref 暴露相机、渲染器或需要被外部控制的对象。社区中常见的“three.js Vue 制作的 3D 场景编辑器”实际也是这种思路编辑器本身是重应用但作品集页面只使用编辑器导出的 JSON 或 GLB避免把编辑器代码打进用户浏览器。方案适合场景体积注意事项难度原生 WebGL只需要极简绘制代码量低但开发成本高高Three.js通用 3D 场景按需引入模块避免整包引入中Babylon.js游戏化、复杂交互引擎本身较大需要 Tree Shaking中高React Three FiberReact 团队多一层依赖拆包要仔细中2.2 环境准备Node.js、Vite 和 three 的版本配合3D 作品集的开发环境不需要太多组件Node.js 和包管理器是基础。建议使用 Node.js 的 LTS 版本因为 Vite 和 Three.js 的新版本对 Node 版本有要求长期维护时 LTS 更稳定。包管理器可以选择 npm、pnpm 或 yarn本文使用 npm 示例。Three.js 的版本迭代较快不同版本之间 API 可能变化。安装时不要只写npm install three还应该让包管理器生成锁文件并在提交代码时把package-lock.json或pnpm-lock.yaml一起提交。这样换机器、换环境时能保证依赖版本一致避免因为 Three.js 升级导致addons引入路径或 API 行为变化。2.3 基于 Vite 初始化项目并锁定依赖打开终端执行下面的命令创建一个基于原生 JavaScript 的 Vite 工程npm create vitelatest 3d-portfolio -- --template vanilla cd 3d-portfolio npm install npm install three安装完成后package.json中会新增three依赖。为了让后续体积分析和调试更可靠建议在首次安装后检查依赖版本并提交锁文件。下面是一个示例{ name: 3d-portfolio, private: true, version: 0.1.0, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { three: ^0.160.0 }, devDependencies: { vite: ^5.0.0 } }版本号只作为示例实际安装时以 npm 解析到的版本为准。type: module表示项目使用 ESM这也是 Three.js 当前推荐的方式。2.4 首次构建先记录体积基线再谈优化在写任何 3D 代码前先运行一次构建npm run buildVite 会生成dist目录里面包含要发布到服务器的静态文件。此时体积很小但它是一个重要的基线。后续每加入一个模型、一张纹理、一段代码都可以通过对比这个基线判断“增加了多少体积”。更好的做法是给build脚本增加--sourcemap方便后面用体积分析工具定位具体模块。不过生产环境不建议发布 sourcemap分析后要把 sourcemap 关闭。3. 实现最小 3D 作品集页面场景、相机、动画与交互3.1 页面骨架入口 HTML、页面样式和项目入口脚本Vite 的 vanilla 模板会生成index.html、src/main.js和src/style.css。为了让 3D 画布成为背景层HTML 中要包含两层结构一层是 WebGL 渲染器要挂载的容器一层是覆盖在画布上的作品列表和文字说明。!doctype html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title3D Portfolio/title /head body div idapp header classhero h1Frontend / WebGL Developer/h1 pDrag to rotate, scroll to zoom./p /header section classprojects a href# classproject-card>import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import ./style.css; const scene new THREE.Scene(); scene.background new THREE.Color(0x0d0d12); const camera new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.set(4, 3, 6); camera.lookAt(0, 0, 0); const renderer new THREE.WebGLRenderer({ antialias: true, alpha: false, powerPreference: high-performance, }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const ambientLight new THREE.AmbientLight(0xffffff, 1.2); scene.add(ambientLight); const dirLight new THREE.DirectionalLight(0xffffff, 2.0); dirLight.position.set(5, 10, 7); scene.add(dirLight); const boxGeometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshStandardMaterial({ color: 0x3b82f6, roughness: 0.4, metalness: 0.1, }); const cube new THREE.Mesh(boxGeometry, material); scene.add(cube); const controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; controls.dampingFactor 0.08; controls.minDistance 2; controls.maxDistance 15; controls.maxPolarAngle Math.PI / 2; function animate() { requestAnimationFrame(animate); cube.rotation.y 0.003; controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });这里有几个参数需要解释。antialias抗锯齿可以有效减少几何体边缘的锯齿但会增加 GPU 开销。pixelRatio设置为Math.min(window.devicePixelRatio, 2)是为了避免在 3 倍屏手机上渲染 3 倍像素造成不必要的性能浪费。maxPolarAngle限制相机不能转到地面以下避免透视穿帮。3.3 用 OrbitControls 实现鼠标拖拽和滚轮缩放OrbitControls 是 Three.js 官方提供的相机控制器让用户通过鼠标拖拽旋转视角滚轮缩放。它来自three/addons/controls/OrbitControls.js使用模块化路径引入。控制器的参数直接影响交互手感。enableDamping开启阻尼后旋转会有惯性感觉更细腻dampingFactor越大惯性结束越快。minDistance和maxDistance限制相机与观察目标的距离避免用户贴到模型内部或拉得太远。在实际作品集中很少让整页永远处于重力控制状态。可以只对某个“展示台”应用 OrbitControls或者给控制器加上边界让用户永远看到完整作品。如果后续希望用户点击卡片后相机飞到指定位置可以参考camera.position的 Tween 动画但要注意不要和 OrbitControls 的阻尼逻辑冲突。3.4 作品卡片与 Hover 交互从 3D 场景连接到页面内容作品集不能只有 3D 场景还要有可点击的作品卡片。常见的交互方式是页面浮动展示卡片用户悬停卡片时3D 场景中对应的模型被高亮或缩放。这个交互可以通过 Raycaster 实现也可以直接绑定卡片事件。使用 Raycaster 的典型思路是鼠标移动时计算屏幕坐标对应的相机射线检测射线是否碰撞到场景中的模型数组。如果碰撞到某个模型就切换模型的 emissive 颜色或修改 scale。示例const raycaster new THREE.Raycaster(); const pointer new THREE.Vector2(); const projectMeshes []; window.addEventListener(pointermove, (event) { pointer.x (event.clientX / window.innerWidth) * 2 - 1; pointer.y -(event.clientY / window.innerHeight) * 2 1; }); function updateHover() { raycaster.setFromCamera(pointer, camera); const hits raycaster.intersectObjects(projectMeshes, false); document.body.classList.toggle(has-hover-project, hits.length 0); }这里需要注意射线检测不是免费的如果场景中有几十个高面数模型每帧都做全量检测会消耗主线程。不要把updateHover放在requestAnimationFrame里每帧调用建议配合pointermove事件节流或者只检测场景中的少量关键模型。3.5 响应式适配桌面、移动端和 DPR 处理移动端访问 3D 作品集时触摸事件替代鼠标事件OrbitControls 会自动处理触摸旋转和缩放但页面布局仍要适配。渲染器的 canvas 需要跟随容器宽度变化文字层要保证在窄屏下不遮挡核心模型。DPR设备像素比是移动端性能的关键。iPhone 等设备的 DPR 可能达到 3如果直接设置renderer.setPixelRatio(window.devicePixelRatio)GPU 渲染压力会增加数倍。建议统一使用Math.min(window.devicePixelRatio, 2)在清晰度和性能之间取平衡。resize监听器里除了更新camera.aspect和renderer.setSize还要考虑容器尺寸变化时画布的位置。如果 3D 画布铺满全屏需要让canvas的 CSS 尺寸跟随窗口如果是局部容器读取容器getBoundingClientRect()会更准确。4. 把体积压到 5MB 以内纹理、模型、代码和服务端压缩4.1 纹理尺寸、压缩格式和 Mipmap 的选择3D 作品集体积超标通常不是 HTML 造成的而是纹理和模型。一张 2048x2048 的 PNG 图片可能超过 5MB相同的 JPEG 可能只有 500KBWebP 会更小。因此纹理的第一个规则是不要直接使用原图先缩放尺寸再转成适合网页的压缩格式。Three.js 中可以通过TextureLoader加载图片但要注意纹理尺寸最好是 2 的幂256、512、1024、2048这样渲染时能正确生成 Mipmap同时避免 NPOT 纹理带来的兼容问题。如果只是作为背景色或简单材质甚至可以不使用贴图而是用渐变、几何体颜色和光照来模拟效果。对于需要更多细节的表面可以使用 KTX2 等 GPU 压缩纹理格式。这类格式会占用更少显存也能降低加载体积但需要额外的解码器增加一小部分代码体积。在 5MB 预算下优先考虑 WebP 纹理只有显存占用成为瓶颈时才引入 KTX2。4.2 模型glTF/GLB、Draco 压缩与多边形数量Three.js 加载 3D 模型首推 glTF/GLB 格式。GLB 是二进制形式一个文件包含网格、材质、动画和纹理很适合 Web 端加载。常见的 DCC 工具Blender、C4D、3ds Max都可以导出 glTF/GLB但导出前需要清理场景中不需要的灯光、相机、空对象和隐藏物体。强烈建议在构建流程中加入 Draco 压缩。Draco 是 Google 的网格压缩算法可以大幅减少几何体数据体积。使用 gltf-transform/cli 可以快速处理模型npx gltf-transform/cli optimize input.glb output.glb npx gltf-transform/cli draco input.glb output.glb更精细的做法是先执行optimize再执行draco。但需要注意Draco 压缩的模型在浏览器端加载时需要使用DRACOLoader并配置解码器路径。解码器文件也会增加体积所以要按需引入不要一直放在首屏。面数控制同样重要。一个作品展示模型如果是给手机浏览几十万面在桌面端可能流畅在移动端就会卡顿。模型中的细碎小物体、不可见背面、重叠面都会消耗渲染性能。建模阶段就要养成清理习惯运行时不要对所有模型开启castShadow和receiveShadow阴影计算是性能热点。4.3 代码按需引入 three 模块避免整包打入Three.js 虽然支持 Tree Shaking但使用import * as THREE from three时很多打包器仍然会把大量模块包含进去。更推荐使用命名导入import { Scene, PerspectiveCamera, WebGLRenderer } from three;对于 OrbitControls、GLTFLoader、DRACOLoader 这种扩展模块使用three/addons/...路径引入。Vite 会把这些模块拆到独立 chunk 中。如果首屏并不需要加载模型可以进一步用动态import()async function loadProjectModel(url) { const { GLTFLoader } await import(three/addons/loaders/GLTFLoader.js); const { DRACOLoader } await import(three/addons/loaders/DRACOLoader.js); const loader new GLTFLoader(); // ... }这样模型加载器和解码器只会在用户点击作品后下载首屏体积自然下降。代码层面还要避免把整个three/examples/jsm路径全部引入。有些旧示例会从three/examples/jsm/controls/OrbitControls导入新版推荐使用three/addons别名两者指向同一个模块但混用会加大打包结果。保持统一的导入路径打包后的 chunk 更干净。4.4 字体、CSS、图标这些“非 3D”体积也不能忽视3D 作品集的体积问题往往不只来自模型。自定义字体的 woff2 文件动辄几百 KB如果引入三四套字重整体体积就多出 1MB 以上。减少字体数量的建议是正文使用系统字体栈只有标题或品牌字使用自定义字体并限制在两种字重以内。CSS 动画和图标库也可能成为隐藏负担。一个完整的图标字体文件可能有数百 KB但页面实际只用其中几个图标。更推荐使用内联 SVG 图标或者只提取用到的 Icon。如果遇到无法避免的第三方库尽量用按需引入插件避免把整个库打进主 chunk。style.css中还有一个容易被忽略的规则不要直接为 3D canvas 设置超大背景图。有些开发者为了让页面“更有氛围”加一张 1920x1080 的背景图片这会让首屏体积翻倍。氛围完全可以通过 Three.js 的渐变背景、雾效和灯光实现。4.5 服务端开启 gzip/Brotli真正影响网络传输体积资源体积和网络传输体积是两回事。一个 300KB 的 JavaScript 文件开启 gzip 后可能只有 80KB开启 Brotli 后可能更小。静态部署平台通常会自动开启压缩但自建 Nginx 时需要显式配置。Nginx 配置示例location ~* \.(js|css|json|glb|gltf|png|jpg|jpeg|webp|ktx2)$ { gzip_static on; brotli_static on; expires 30d; add_header Cache-Control public, immutable; }gzip_static会优先使用打包阶段生成的.gz文件brotli_static使用.br文件。这要求构建阶段提前生成预压缩文件。如果部署平台不支持自动预压缩也可以在构建脚本中加入vite-plugin-compression一类的插件在dist目录生成对应压缩包。注意glb本身已经包含压缩纹理或 Draco 网格时再套一层 gzip 仍然有效但压缩率取决于内容。不要为了压缩而把所有图片都转成 gzip优先处理文本类资源和 JSON。5. 构建、验证与体积分析5.1 执行构建并查看产物结构当页面和模型都准备好后运行npm run buildVite 会把src下的代码打包到dist/assets静态模型如果放在public/models目录会被原样复制到dist/models。用以下命令可以查看各文件大小du -sh dist/* du -h dist/assets/*在 Unix 系统上du能快速列出文件和目录大小。如果发现dist整体超过 5MB立刻进入体积分析阶段。注意du显示的是磁盘块大小和实际下载体积略有差异但足以判断大头在哪。5.2 用 source-map-explorer 或 rollup-plugin-visualizer 定位体积定位 JavaScript 体积异常时推荐使用打包分析工具。先安装npm install -D rollup-plugin-visualizer在vite.config.js中启用import { defineConfig } from vite; import { visualizer } from rollup-plugin-visualizer; export default defineConfig({ plugins: [ visualizer({ gzipSize: true, brotliSize: true, }), ], });重新构建后dist/report.html会展示每个 chunk 中包含的模块和体积。看到three模块占 500KB并不代表必须优化因为 Three.js 核心就是有固定体积需要关注的是那些因为错误导入而多出来的重复模块。对模型和纹理资源可以直接查看dist/models和dist/images目录。如果某个 GLB 特别大可以用 gltf-transform 查看模型统计信息npx gltf-transform/cli inspect scene.glb这个命令会输出每个 buffer、mesh、accessor 的占用方便判断是顶点数据、纹理还是动画占用了大头。5.3 用浏览器 Network 和 Lighthouse 验证真实加载表现打包体积只是静态数字真实体验还需要用浏览器验证。打开 Chrome DevTools 的 Network 面板把网络调整为 Slow 4G刷新页面记录每个资源的加载时长和总传输体积。重点看首屏到达用户可以旋转视角的时间。Lighthouse 可以生成性能报告但它不会替代 Network 面板。Lighthouse 的 Performance 分数受多项指标影响对 3D 作品集尤其要注意“Total Blocking Time”和“Largest Contentful Paint”。如果 3D canvas 在首屏渲染但内容为空Lighthouse 可能认为页面加载完成而用户实际上还没有看到可用内容。手动验证时要覆盖三个场景桌面浏览器、普通移动设备、低端移动设备。低端移动设备上即使加载完成首次交互也可能卡顿。此时要降低pixelRatio、减少阴影、降低后处理效果。5.4 一份可执行的体积预算表为了不把“5MB”变成口号可以给项目设置明确预算。下面是一份适合单个 3D 作品集网站的参考预算按网络传输体积计算类别预算上限说明HTML50KB不包含内联大资源CSS100KB压缩后通常远低于此值JavaScript 初始加载600KB已包含 gzip/Brotli 估计首屏 3D 模型2.5MB展示台或核心作品首屏纹理/贴图1.5MBWebP 或压缩格式字体150KB最多两种字重其他图标、JSON100KB项目数据和占位内容预留缓冲500KB应对未知开销合计约 5MB。这个表适合作为检查清单每次新增资源都要说明属于哪个类别超预算时必须从同一类别或其他类别扣除同等体积。5.5 学习环境与生产环境的验证差异学习环境跑通很容易生产环境仍有差异。开发环境下npm run dev使用 Vite Dev Server所有模块按需编译体积不真实npm run preview虽然预览构建产物但服务端配置和真实 CDN 仍不同。例如 gzip/Brotli 是否开启、HTTP/2 是否启用、缓存头是否正确这些只有部署后才能验证。生产环境还要检查dist中是否有过期文件因为长期迭代会留下旧版本 JS 和模型占用服务器空间的同时也可能被浏览器缓存导致用户看到旧资源。建议部署平台自动清理旧资源或者给文件名加 hash。6. 常见问题与排查链路6.1 模型不显示或页面黑屏怎么办黑屏是 Three.js 新手最容易遇到的现象但黑屏不等于代码完全崩溃。第一步打开浏览器 Console看是否有红色报错。常见报错包括THREE.WebGLRenderer: Context Lost、RuntimeError: GL_INVALID_OPERATION、模型加载 404 等。如果 Console 无报错但页面全黑需要逐步排查灯光是否够亮相机是否对着模型模型是否位于相机远裁剪面之外材质是否因为缺少贴图而显示黑色渲染器的alpha和scene.background是否搭配错误。先移除所有灯光添加一个基础MeshBasicMaterial如果能显示说明是灯光或材质问题。模型加载后不显示也可能是 glTF 的单位或坐标轴不一致。blender 导出的模型可能使用米制单位而 Three.js 默认单位是米但缩放差异可能让模型缩小到看不见。尝试把camera.position拉远不断缩小或放大模型 scale观察是否出现。6.2 WebGL 上下文丢失的原因与恢复策略移动端打开页面后切到后台再切回来可能触发 WebGL 上下文丢失。此时如果没有监听webglcontextlost事件页面会停留在一帧黑屏状态。标准做法是阻止默认行为并在上下文恢复时重新初始化渲染资源const canvas renderer.domElement; canvas.addEventListener(webglcontextlost, (event) { event.preventDefault(); }); canvas.addEventListener(webglcontextrestored, () { renderer.dispose(); initScene(); });不是所有状态都能自动恢复。如果使用了复杂的后处理链或外部纹理需要在initScene中重新创建。这也是为什么 3D 作品集代码适合封装成可重入的初始化函数。6.3 构建体积还是超过 5MB如何逐层定位当dist总大小超过 5MB先看du -sh dist/*找出最大的目录。如果是assets目录再看 JavaScript 体积和图片体积如果是models目录直接检查 GLB 文件。JavaScript 超限的常见原因是导入方式不对。例如在某个模块中写了import * as THREE from three又用THREE.GLTFLoader但实际上GLTFLoader在three/addons中错误导入会让打包器把整个 three 模块和重复模块都打入。模型超限的常见原因是纹理嵌在 GLB 内部。很多建模工具导出 GLB 时会把纹理图片内嵌为 PNG 或 JPEG即使这些纹理在页面中可以通过 CSS 或共享图片处理。处理方式是把纹理从 GLB 中拆出转成 WebP 后单独压缩再在 GLTF 中重新引用。6.4 本地正常、部署后资源 404 或加载失败本地开发时模型文件放在public/models下Vite 会把它们复制到dist/models。浏览器加载路径应为/models/xxx.glb。如果部署到子目录比如https://example.com/portfolio/则需要设置 Vite 的base配置export default defineConfig({ base: /portfolio/, });如果没有设置baseJS 中的资源路径会从根目录开始查找导致部署后 404。另外模型加载器中的解码器路径也可能因为子目录问题失效需要把DRACOLoader.setDecoderPath指向完整的绝对路径或 CDN 路径。6.5 3D 作品集最容易踩的五个坑问题现象常见原因检查方式处理建议点击作品后页面卡顿所有模型首屏加载且面数过高查看 Network 中模型文件大小按需加载降低面数使用 LOD贴图非常模糊纹理尺寸过小或压缩过度检查纹理实际分辨率和压缩格式根据展示距离选择 1024 或 2048光影闪烁阴影贴图分辨率不足或模型面重叠观察阴影区域是否抖动提高 shadow map 分辨率清理重叠面移动端发热严重DPR 过高且没有限制阴影Chrome 性能面板查看 GPU 占用限制 DPR 为 2减小阴影范围刷新后加载旧版本缓存策略过强查看 Network 中文件是否返回 200/304文件名加 hash配置正确的缓存头7. 部署上线与长期维护7.1 部署到静态托管平台并配置压缩和缓存3D 作品集本质是静态站点可以部署到 Vercel、Netlify、Cloudflare Pages 或任意 Nginx。静态托管平台通常自动处理 HTTPS、压缩和 CDN 缓存节省大量运维时间。自建服务器需要额外配置压缩、缓存和安全响应头。部署前建议做一次发布前检查是否已经执行npm run build并且dist中没有无用文件。是否已经检查所有模型纹理路径子目录 base 是否正确。是否已经确认 gzip/Brotli 生效Network 中Content-Encoding不是空值。是否已经验证移动端加载和交互。是否已经配置 404 页和robots.txt。这些检查项看似琐碎但在长期迭代中能避免线上回滚。7.2 增加降级方案无 WebGL 环境也能看到作品不是所有用户设备都能运行 WebGL部分企业浏览器会禁用硬件加速。为了保证作品集可访问应该添加降级方案。在初始化 Three.js 前先做能力检测function hasWebGL() { const canvas document.createElement(canvas); const gl canvas.getContext(webgl2) || canvas.getContext(webgl); return !!gl; } if (!hasWebGL()) { document.body.classList.add(no-webgl); }CSS 中让.no-webgl隐藏 3D 画布显示普通作品卡片列表。这样用户即使看不到 3D 效果也能了解你的作品内容。对作品集网站来说内容可访问性比炫技更重要。7.3 维护“5MB 纪律”从一次上线到持续迭代一次上线低于 5MB 并不难难的是后续每次加内容都保持这个预算。建议把体积检查接入构建流程。可以在package.json中加入一个简单脚本先构建再检查dist大小npm run build du -sh dist如果变成 CI 环境可以使用size-limit这类工具设置阈值。只要超过 5MB 就构建失败这样团队或个人都能在提交前发现问题而不是等到部署后才发现。长期维护还要给src目录建立清晰结构。模型放在public/models纹理放在src/assets每个作品对应一个独立配置文件。新增作品时不修改旧场景逻辑只增加配置文件减少回归风险。7.4 从作品集延伸到 3D 场景编辑器、数据可视化等方向3D 作品集是很好的练习项目但它也能扩展成更大工程。如果后续不满足于手写每个场景可以基于 Three.js 和 Vue/React 做一个简单的场景编辑器左侧组件列表、中间画布、右侧属性面板最终把场景数据导出为 JSON。作品集页面只负责加载这个 JSON不需要把编辑器代码打包给访客。另一个方向是数据可视化。Three.js 可以渲染 3D 饼图、散点图、关系图例如 echarts-gl 里的 3D 饼图就是很常见的展示形式。但这些可视化库体积较大使用前要评估是否值得占用 5MB 预算。像 3D Gaussian Splatting 这类新技术虽然观感惊艳但点云数据通常从几十 MB 起步更适合作为单独链接展示而不是放进作品集首页。技术选型永远服务于目标。一个小于 5MB 的 3D 作品集核心价值不是“我用了多新的 3D 技术”而是“我能在资源受限的情况下做出完整、流畅、可维护的交互体验”。这种克制恰恰是长期打磨最值得保留的部分。如果你正准备做自己的 3D 作品集建议先从 5MB 预算表开始把每个资源都当作需要论证的提案。等页面真正运行起来再把精力放在光照细节、动画节奏和用户引导上。这样既不会让项目失控也能逐步形成适合自己内容体系的 3D 展示模板。
返回列表