Three.js教程
r3f
Leva

Leva:更自然的参数控制面板

在做可视化或 3D 项目时,我们常常需要一组可交互的参数控制界面。
比如调节相机角度、光照强度、材质颜色,或者控制动画速度。

最初很多人会选择 dat.GUI —— 它简单、轻量,却显得有些“过时”。
随着 React 在可视化领域的普及,一个更现代的替代方案出现了:Leva

一、为什么需要 Leva

在 React Three Fiber 或其他前端可视化项目中,参数调试往往是个痛点。

  • dat.GUI 的 UI 风格陈旧,缺少类型推导与组件化能力;
  • 自己做面板,又容易陷入重复造轮子的陷阱;
  • 调参的过程常常脱离业务逻辑,难以与组件状态保持同步。

Leva 的设计目标正是解决这些问题。它提供了一套与 React 生态天然契合的参数控制机制:
声明式、响应式、可组合。

二、核心思路:useControls

Leva 的核心是一个 Hook:useControls()
它通过对象描述来自动生成交互控件,并将结果与 React 状态绑定。

import { useControls } from "leva";
 
function Scene() {
  const { light, color } = useControls({
    light: { value: 1.2, min: 0, max: 2, step: 0.1 },
    color: "#ffcc00",
  });
 
  return (
    <mesh>
      <meshStandardMaterial color={color} />
      <pointLight intensity={light} />
    </mesh>
  );
}

没有任何额外的状态管理逻辑,useControls 的返回值本身就能直接驱动组件渲染。
参数一旦在面板中修改,组件会自动响应更新。

这意味着 调参与渲染完全同步,无需手动 setState 或 context 传递。

三、设计理念:声明式控制面板、

Leva 和传统的参数面板最大的不一样在于:它不让你去“创建控件”,而是直接声明参数

在 dat.GUI 里,你通常要这样做:

gui.add(light, "intensity", 0, 3);

这是一种命令式的写法——告诉库“现在加一个滑块,范围是 0 到 3”。

而在 Leva 中,你只需写出参数结构:

const { intensity } = useControls({ intensity: { value: 1, min: 0, max: 3 } });

控件会自动出现、自动绑定、自动更新。
组件中的状态和面板中的值,始终保持同步。

这种声明式方式带来几个明显好处:

  1. 更轻的心智负担 —— 你只需要描述“想要什么”,不用写“怎么做”;
  2. 更自然的组件逻辑 —— 参数定义和使用都在同一个组件里;
  3. 更容易保存状态 —— Leva 支持保存、恢复、外部控制面板数据。

示例:通过分组组织参数结构

const { intensity, color, speed } = useControls({
  Lighting: folder({
    intensity: { value: 1, min: 0, max: 3 },
    color: "#ffaa00",
  }),
  Animation: folder({
    speed: { value: 0.5, min: 0, max: 2 },
  }),
});

这不仅让面板结构更清晰,也方便团队协作时快速理解每个参数的作用。


四、与 R3F 的自然配合

Leva 的出现,几乎就是为 React Three Fiber(R3F)量身定制的。

在 R3F 中,我们经常需要动态控制:

  • 相机视角与位置;
  • 光照强度与方向;
  • 材质参数、动画速率等。

Leva 的响应式机制让这一切变得自然。
下面的示例展示了如何将它用于控制 OrbitControls 和环境光:

import { OrbitControls } from "@react-three/drei";
import { useControls } from "leva";
 
function App() {
  const { zoom, ambient } = useControls({
    zoom: { value: 3, min: 1, max: 10 },
    ambient: { value: 0.4, min: 0, max: 1 },
  });
 
  return (
    <>
      <ambientLight intensity={ambient} />
      <OrbitControls enableZoom={true} maxDistance={zoom} />
    </>
  );
}

一行代码即可实现控制面板到渲染效果的直连。
对于调试与演示场景,这是一个非常高效的工作流。

五、案例演示

Leva 能根据参数声明自动生成对应的输入控件,包括字符串、布尔值、数值、区间、按钮、颜色、选择和向量等类型。
每个字段都会映射为直观的界面元素,修改值的同时即可驱动组件状态更新,无需手动编写事件或绑定逻辑。

useControls({
  Inputs: folder({
    // STRING
    String: { value: "Hello Leva" },
    "String (Non Editable)": { value: "Read only", editable: false },
 
    // BOOLEAN / NUMBER
    Boolean: true,
    Number: { value: 42, min: 0, max: 100, step: 1 },
 
    // INTERVAL (returns { min, max })
    Interval: { value: { min: 20, max: 60 }, min: 0, max: 100 },
 
    // BUTTONS
    Button: button(() => {
      alert("Single Button clicked");
    }),
    // Button Group helper (>= leva 0.9). Each key is a button.
    "Button Group": buttonGroup({
      Play: () => console.log("Play"),
      Pause: () => console.log("Pause"),
      Stop: () => console.log("Stop"),
    }),
 
    // COLORS
    "Color (Hex)": "#ffcc00",
 
    // SELECT
    Select: {
      value: "Option A",
      options: ["Option A", "Option B", "Option C"],
    },
 
    // VECTORS
    Vector2: { value: { x: 40, y: 20 }, step: 1 },
    Vector3: { value: { x: 1, y: 2, z: 3 }, step: 1 },
    Vector4: { value: { x: 0, y: 0, z: 0, w: 1 }, step: 0.1 },
  }),
});

六、总结

Leva 不是一个复杂的库,它的核心思想很简单:
让参数调节回归到 React 的声明式语法中

对前端可视化开发者而言,它介于“工具”与“框架”之间——
轻量、直觉,却能在工程实践中带来实实在在的效率提升。

如果你的项目中还在使用 dat.GUI,不妨试着用 Leva 重写一部分控制逻辑。
也许你会发现,调参这件小事,也能变得优雅。