MODKEYS 是一个完全运行在浏览器中的 3D 键盘可视化配置器:以 Three.js 作为渲染核心,以 GSAP 驱动动画过渡,将颜色、材质、键帽轮廓、布局与配件等选项直接交给用户操作,实现所见即所得的定制体验。
项目本身是一个单页应用,核心渲染逻辑集中在 index.html 的内联脚本中,依赖通过 CDN 引入,入口模块由 src/main.js 初始化。整体架构轻量,既适合作为学习 Three.js 工程化实践的参考案例,也可直接扩展为电商级的可视化定制工具。


几年前,做一个这样的交互式产品配置器,还需要一支专门的 3D 开发团队才能实现。而如今,Claude Fable 5 就能做到如此程度的交互与界面表现,实在令人感叹今非昔比。
一、核心功能概览
|
|
|
|---|---|
| 3D 实时预览 |
|
| 材质调节 |
roughness / metalness / clearcoat 等 PBR 参数精细控制质感 |
| 配色切换(Colorways) |
|
| 每键定制 |
|
| 布局重建 |
|
| 配件控制 |
|
| 动态定价 |
|
二、快速本地运行
项目使用现代前端工具链(Vite),本地启动步骤如下:

# 1. 克隆仓库
git clone https://github.com/thebuggeddev/modkeys.git
cd modkeys
# 2. 安装依赖
npm install
# 3. 启动开发服务器
npm run dev
启动后访问 Vite 默认地址(通常为 http://localhost:5173)即可看到配置器界面。
注意:Three.js 与 GSAP 通过 CDN 在
index.html中直接引入,无需额外配置。src/main.js作为 Vite 入口负责挂载 DOM 交互模块。
三、核心代码解读
3.1 模块化入口示例(src/counter.js)
项目通过 src/counter.js 演示了最基础的模块化写法——一个可复用的点击计数器:
// src/counter.js
export function setupCounter(element) {
let counter = 0
const setCounter = (count) => {
counter = count
element.innerHTML = `Count is ${counter}`
}
element.addEventListener('click', () => setCounter(counter + 1))
setCounter(0)
}
该函数在 src/main.js 中被引入并挂载到 DOM 元素。虽然功能简单,但它体现了项目中小功能单独封装、主入口统一编排的组织思路。
3.2 Three.js 场景分组(模型层次结构)
3D 场景中,整机被拆解为多个逻辑分组(THREE.Group),每个分组负责一类对象,便于独立控制动画、材质与可见性:

// index.html 内联脚本
const caseGroup = new THREE.Group(); // 机壳
const capsGroup = new THREE.Group(); // 键帽
const switchGroup = new THREE.Group(); // 开关
const knobGroup = new THREE.Group(); // 旋钮
const keyGlowGroup = new THREE.Group(); // 键位光效
root.add(caseGroup, capsGroup, switchGroup, knobGroup, keyGlowGroup);
为什么这样设计?
-
对某一类对象的整体操作(如隐藏所有键帽、批量更新颜色)只需操作对应 Group,无需遍历整个场景图 -
入场动画( popKeys)可以精准作用于capsGroup的子对象,不影响机壳 -
光效组( keyGlowGroup)可以单独控制混合模式与渲染顺序
地面阴影通过 ShadowMaterial 平面实现,配合额外的贴图层(AO/glow 贴花)让渲染结果更接近真实拍摄效果。
3.3 开关几何体构建(makeSwitch)
每个物理开关由多个 BoxGeometry 拼合而成,分别对应开关箱体、轴心等部件,各部件绑定独立材质:

// 概念性示例,对应 index.html 中 makeSwitch 函数逻辑
function makeSwitch(x, y, z) {
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.9, 0.4, 0.9),
matSwBody // 开关箱体材质
);
const stem = new THREE.Mesh(
new THREE.BoxGeometry(0.25, 0.3, 0.25),
matStem // 轴心材质(通常半透明)
);
stem.position.y = 0.3;
const swGroup = new THREE.Group();
swGroup.add(body, stem);
swGroup.position.set(x, y, z);
switchGroup.add(swGroup);
}
这种几何体组合方式使得每个开关都是独立的子场景,后续可以单独更新轴心颜色或为特定位置的开关应用不同材质。
3.4 颜色平滑过渡(tweenColor + GSAP)
配色切换是用户感知最直接的功能。项目使用 GSAP 直接操作 Three.js 材质的 color 属性,实现逐帧插值过渡:

// index.html 内联脚本
function tweenColor(mat, hex, duration) {
const c = sRGB(hex); // 将十六进制色值转换为线性 sRGB 空间
gsap.to(mat.color, {
r: c.r,
g: c.g,
b: c.b,
duration: duration,
ease: 'power2.out'
});
}
关键点:Three.js 内部使用线性色彩空间,因此 sRGB() 的转换步骤不可省略,否则过渡色会出现偏差。
3.5 状态驱动重建(apply3D)
这是整个配置器的核心调度函数。用户在界面上的每一次选择都会生成一个局部状态变更(patch),由 apply3D 决定如何响应:
// 简化示意
function apply3D(patch, animate = true) {
// 合并到全局状态
Object.assign(state, patch);
if (patch.layout !== undefined || patch.profile !== undefined) {
// 布局或键帽轮廓变化 → 完整重建
buildKeys();
rebuildBoard();
if (animate) popKeys(); // 键帽弹出入场动画
}
if (patch.caseColor !== undefined) {
tweenColor(matCase, patch.caseColor, 0.4);
}
if (patch.capsColor !== undefined) {
tweenColor(matCaps, patch.capsColor, 0.4);
}
// ... 其他属性处理
}
两种响应路径:
|
|
|
|---|---|
|
|
|
|
|
|
这种区分避免了不必要的几何体重建,在保证视觉效果的同时控制了性能开销。
3.6 品牌图案映射(BRAND_MARKS)
项目维护了一个品牌标记集合,将特定按键位置与品牌图案关联:
const BRAND_MARKS = {
'escape': { brand: 'default', symbol: '⎋' },
'logo': { brand: 'modkeys', texture: logoTexture },
// ...
};
切换品牌选项时,系统遍历 capsGroup 中对应键帽,替换其贴图或几何体上的图案标记。这个机制可以很方便地扩展为支持用户上传自定义 Logo 的功能。
四、界面使用功能很齐全

3D 视图交互
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
配置面板操作
- 配色(Colorways)
:在右侧面板选择预设配色或单项颜色(机壳 / 底板 / 键帽),颜色平滑过渡 - 布局切换
:触发机壳与键位完整重建,并播放键帽弹出动画 - 键帽轮廓(Profile)
:实时切换键帽高度与轮廓形状,同样触发重建 - 单键编辑
:打开 Per-Key Customizer,可上传纹理或调整图案的位置与缩放(Quad Editor) - 旋钮配件
:可独立显隐,旋钮有自己的几何体与材质,不依赖键帽组
五、扩展与二次开发建议
5.1 增加材质预设
当前 PBR 参数(metalness / roughness / clearcoat)已完全暴露。可在面板中添加"阳极铝""亚克力""磨砂塑料"等预设按钮,每个预设对应一组参数值,通过 GSAP 过渡到目标值:

const MATERIAL_PRESETS = {
anodized: { metalness: 0.9, roughness: 0.3, clearcoat: 0.0 },
acrylic: { metalness: 0.0, roughness: 0.1, clearcoat: 1.0 },
frosted: { metalness: 0.0, roughness: 0.8, clearcoat: 0.0 },
};
5.2 键位数据扩展(固件对接)
为 capsGroup 每个子对象的 userData 增加更多字段,便于导出配置或与 QMK/VIA 等固件对接:
keycap.userData = {
keyCode: 'KC_ESC',
macro: null,
rgbColor: '#ff0000',
layer: 0
};
5.3 性能优化方向
|
|
|
|---|---|
|
|
THREE.InstancedMesh 替代单独 Mesh |
|
|
castShadow = false)、降低 envMap 分辨率 |
|
|
|
|
|
|
5.4 理清状态同步机制(二次开发首要任务)
如果要做深度修改,建议先在浏览器控制台打印 state 对象,观察每次操作后 patch 的变化内容,再逆向追踪 apply3D 的处理分支。这比直接阅读渲染代码更高效。
最后
MODKEYS 的核心设计理念可以用三句话概括:
分组管理——场景对象按逻辑类型分组,互不干扰
状态驱动——所有视觉变化由状态变更触发,单向数据流
平滑过渡——几何重建与颜色过渡分离,用户体验流畅
无论是把它当作 Three.js 工程实践的学习案例,还是作为定制键盘电商的可视化底座,MODKEYS 都提供了一个完整且易于扩展的起点。建议从 apply3D 函数入手,理清状态流转后,再根据需求向材质层或几何层延伸。
预览地址:https://modkeys.vercel.app/
代码地址:https://github.com/thebuggeddev/modkeys