12k
All articles

ThreeUI 入门指南

了解如何在React中安装ThreeUI并添加动画3D背景,修复资源路径问题,并通过延迟加载和减少动态效果的备用方案管理性能。

OpenReplay Team
OpenReplay Team
ThreeUI 入门指南

ThreeUI 是由 Meng To 开发的 React 组件库,发布于 threeui.com,npm 包名为 @designcodeio/threeui。每个组件都自带动画渲染器(根据组件不同,分别基于 Three.js、原生 WebGL 或 Canvas 2D)。因此,要为页面添加带动画的 3D Hero 背景,只需导入一个组件,无需自行搭建场景、相机和材质。

你想要一个能让用户停下滚动的 Hero 区域,可 Three.js 教程往往要先讲场景图、灯光配置和渲染循环,屏幕上才会出现第一个画面。本指南将介绍如何安装 ThreeUI、在 Hero 区域后方渲染一个背景组件、解决资源路径问题,并帮你判断这些额外开销对你的页面是否值得。

核心要点

  • 使用 npm install @designcodeio/threeui 安装 ThreeUI,并在应用入口文件中导入一次 @designcodeio/threeui/style.css。
  • ThreeUI 将 react、react-dom(18 或 19)以及 three 列为对等依赖(peer dependencies)。
  • 会渲染完整 HTML 文档的组件通过根相对 URL 加载运行时文件,因此需要从 node_modules/@designcodeio/threeui/lib-dist/assets/ 复制这些文件,或设置 sourceUrl 或 assetBaseUrl。
  • 如需实现组件已暴露的变体(variants)和控制项之外的功能,就必须修改其源码。
  • 对 ThreeUI 组件进行懒加载;当用户设置了 prefers-reduced-motion 时跳过渲染;若 3D 效果并无实际增益,则改用静态图片。

ThreeUI 是什么?适合哪些人?

ThreeUI 提供开箱即用的动画区块(Hero、背景、交互元素),可以直接放进普通的 React 页面中。你无需了解场景图的工作原理,只需选择组件、渲染它并设置 props 即可。

有好几个互不相关的项目都叫 three-ui,包括 poki-archive/three-ui 和 nirholas/three-ui。本指南介绍的是 Meng To 在 threeui.com 发布的 ThreeUI,对应 MengTo/threeui 仓库。

ThreeUI 分为两个版本:Community 版和 Pro 版。Community 组件采用 MIT 许可证,可通过 npm 安装。Pro 组件不在 npm 上发布:你需要在拥有有效付费订阅的前提下登录,然后使用 npx @designcodeio/threeui-cli 将其源码下载到项目中。该项目尚处于早期阶段(Community 版于 2026 年 8 月开源),API 可能会发生变化。

安装 ThreeUI 包

只需安装一个 npm 包,然后导入一次其样式表。ThreeUI 安装页面详细介绍了配置步骤。已发布的包将 react 和 react-dom(版本 18 及以上、低于 20)以及 three(0.149 及以上)列为对等依赖。npm 7 及更高版本会自动安装缺失的对等依赖。Pro CLI 需要 Node.js 20 或更高版本。

npm install @designcodeio/threeui

样式表的导入位置取决于你使用的框架:

框架CSS 导入位置
Vitesrc/main.jsx
Next.js App Routerapp/layout.jsx
// src/main.jsx (Vite)
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "@designcodeio/threeui/style.css";
import App from "./App";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);

添加一个组件

要渲染 ThreeUI 组件,只需按名称导入并放入 JSX 中。README 中的示例使用的是 AtTheHorizon,但当前的 1.2.0 版本并未导出该组件,因此本指南改用 EmeraldHorizonBackground——一个放置在 Hero 区域后方的 Three.js 动画背景:

import { EmeraldHorizonBackground } from "@designcodeio/threeui";

export function Hero() {
  return <EmeraldHorizonBackground />;
}

从根路径导入是最简单的方式。该包还提供按组件划分的子路径导入,在开发过程中可以减少引入的代码量:

import { EmeraldHorizonBackground } from "@designcodeio/threeui/components/EmeraldHorizonBackground";

不同组件的 props 各不相同,请在 threeui.com 上查阅各组件的页面。例如,Brand Orbs 接受 variant、size、mode、speed 和 paused。不要凭猜测使用 props。如果想在不依赖任何 props 的情况下添加交互,可以在外层组件中使用 state:

import { useState } from "react";
import { EmeraldHorizonBackground } from "@designcodeio/threeui";

export function Hero() {
  const [show3D, setShow3D] = useState(true);

  return (
    <section>
      {show3D ? (
        <EmeraldHorizonBackground />
      ) : (
        <img src="/hero-fallback.jpg" alt="Product hero" />
      )}
      <button onClick={() => setShow3D((v) => !v)}>
        {show3D ? "Show static image" : "Show 3D"}
      </button>
    </section>
  );
}

ThreeUI 的资源路径陷阱

部分 ThreeUI 组件会渲染完整的 HTML 文档。它们从站点根目录下的固定路径获取运行时文件,这些路径与 ThreeUI 预览站点使用的路径相同。例如,Landscape 默认会请求 /landscape.html。如果你的应用没有在该路径下提供文件,组件就会失效。有两种解决方法:一是将所需文件从 node_modules/@designcodeio/threeui/lib-dist/assets/ 复制到你的 public 目录;二是如果组件提供了 sourceUrl 或 assetBaseUrl prop,将其指向你托管这些文件的位置。

# Check the network tab for the exact paths the component requests,
# then mirror that layout under public/.
cp -R node_modules/@designcodeio/threeui/lib-dist/assets/. public/
// Only if the component's page lists this prop:
// <SomeComponent assetBaseUrl="/threeui/" />
症状解决方法
组件区域渲染为空;网络面板显示根路径下出现 404将所需资源复制到 public/ 中对应的路径
资源托管在 CDN 或子路径下在组件提供的情况下设置 sourceUrl 或 assetBaseUrl

ThreeUI 与原生 Three.js 的关系

ThreeUI 隐藏了渲染器。在基于 Three.js 的组件中,场景、相机、渲染器、材质和动画循环全部封装在组件内部;其他组件则使用原生 WebGL 或 Canvas 2D。这种便利的代价是可控性:ThreeUI 只暴露每个组件的变体和控制项,若要修改这些范围之外的内容,就必须编辑组件源码。Community 版的源码可以在 node_modules 和 GitHub 上查看,Pro CLI 则会将 Pro 版源码复制到你的项目中。

如果你预计需要超出已暴露控制项的定制能力,建议学习底层库。使用 JavaScript 和 Three.js 构建 3D 动画一文介绍了相关基础知识。

实话实说:ThreeUI 的性能

每个 ThreeUI 组件都会运行一个实时动画 canvas,这会带来 JavaScript 开销、资源下载和 GPU 占用,移动端 GPU 对此感受最为明显。请对组件进行懒加载,为设置了 prefers-reduced-motion 的访客跳过渲染,并在 3D 效果对页面毫无增益的地方使用静态图片。

React.lazy 要求模块提供默认导出,因此需要对具名导出做一次映射:

import { lazy, Suspense, useEffect, useState } from "react";

const EmeraldHorizonBackground = lazy(() =>
  import("@designcodeio/threeui").then((m) => ({ default: m.EmeraldHorizonBackground }))
);

function useReducedMotion() {
  const [reduced, setReduced] = useState(false);
  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    setReduced(mq.matches);
    const onChange = (e) => setReduced(e.matches);
    mq.addEventListener("change", onChange);
    return () => mq.removeEventListener("change", onChange);
  }, []);
  return reduced;
}

function hasWebGL() {
  const canvas = document.createElement("canvas");
  return Boolean(canvas.getContext("webgl2") || canvas.getContext("webgl"));
}

const fallback = <img src="/hero-fallback.jpg" alt="Product hero" />;

export function Hero() {
  const reduced = useReducedMotion();
  const [webgl, setWebgl] = useState(false);
  useEffect(() => setWebgl(hasWebGL()), []);

  if (reduced || !webgl) return fallback;
  return <Suspense fallback={fallback}><EmeraldHorizonBackground /></Suspense>;
}

上面的 Hero 组件通过 window.matchMedia 读取 prefers-reduced-motion 媒体特性,并使用 HTMLCanvasElement.getContext 检测是否支持相应的上下文。有些组件会自行处理减弱动画的需求。例如,Brand Orbs 提供了 speed 和 paused props,其源码会检查 prefers-reduced-motion,并在组件移出视口时暂停。具体覆盖了哪些情况,请查阅各组件的页面。

在 Next.js 中,请使用 next/dynamic。ssr: false 选项只能在 Client Component 中使用,如果在 Server Component 中使用,Next.js 会抛出错误。对于只在浏览器中运行的 canvas 代码,关闭服务端渲染是一个稳妥的默认做法:

"use client";
import dynamic from "next/dynamic";

const EmeraldHorizonBackground = dynamic(
  () => import("@designcodeio/threeui").then((m) => m.EmeraldHorizonBackground),
  { ssr: false, loading: () => <img src="/hero-fallback.jpg" alt="Product hero" /> }
);

export default function Hero() {
  return <EmeraldHorizonBackground />;
}

详情请参阅 Next.js 懒加载指南。运行构建工具的 bundle 分析器,查看 ThreeUI 实际为你的 bundle 增加了多少体积。不过,实验室测试只能反映部分情况。借助 OpenReplay 等会话回放工具,你可以观察真实用户的访问过程,从而发现在较慢的手机上一直空白或出现卡顿的 Hero 区域。如果你需要比预制组件更多的控制权,可以参考 WebGPU 与 WebGL:为什么业界正在转向一文中介绍的底层方案。

请锁定 ThreeUI 包的版本。根据 README 中的发布规则,当维护者合并经过审核的同步 Pull Request 时,就会发布一个新的 Community 版本;而移除任何公开的组件、变体或控制项都会触发主版本号升级。AtTheHorizon 就是一个例子:它在 0.3.0 版本中发布,但在 1.x 版本中已被移除。升级前请务必查看发布说明。

总结

借助 ThreeUI,只需一次 npm 安装、一次样式表导入和一个组件,就能得到一个可用的 3D Hero 背景。请为运行时资源的部署预留时间,并牢记它是以可控性换取开发速度。建议先在懒加载边界内渲染一个 Community 组件,并提供减弱动画时的降级方案;然后测量 bundle 体积,并在中端手机上进行测试,再决定是否将其保留在页面上。

常见问题

我可以在商业项目中使用 ThreeUI Community 组件吗?

可以。ThreeUI Community 的应用代码、组件代码以及 ThreeUI 原创图像均采用 MIT 许可证,只要保留许可声明即可用于商业用途。随附的开源字体仍遵循 SIL Open Font License 1.1,随附的 Three.js 运行时文件仍采用 MIT 许可证。Pro 组件是单独的付费产品,因此在发布前请查阅 Pro 版条款。

如果在修改已下载的组件后重新运行 ThreeUI Pro CLI,会发生什么?

除非添加 --force 标志,否则 ThreeUI Pro CLI 不会覆盖你修改过的文件,例如 npx @designcodeio/threeui-cli add cross-beam --dir ./src --force。使用 --force 会用下载的源码替换你的修改,因此请先提交改动,之后再手动合并回去。运行 npx @designcodeio/threeui-cli --help 可查看其他命令和标志,包括 login 和 logout。

为什么 ThreeUI Pro 组件不会出现在 package.json 中,也不受 npm update 管理?

Pro 组件并非 npm 依赖。ThreeUI CLI 会将其源码直接复制到你的项目中,因此它们不会列在 package.json 中,npm update、npm outdated 以及基于锁文件的依赖扫描工具也不会追踪它们。请将 Pro 文件视为第三方内置代码(vendored code):将其纳入版本控制,并在需要新版本时使用 CLI 重新下载。

所有 ThreeUI 组件都需要 WebGL 吗?

不是。部分 ThreeUI 组件使用 Canvas 2D 而非 WebGL 进行绘制。例如,Brand Orbs 的 DesignCode 预设就是用 Canvas 2D 绘制点阵的。一刀切的 WebGL 检测会让本可正常运行这些组件的浏览器无法显示它们,因此只应对 threeui.com 页面中注明使用 Three.js 或 WebGL 渲染器的组件添加 getContext('webgl2') 或 getContext('webgl') 检测。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.