threejs 3D元素周期表:CSS3D 与电子壳层动画

基于 Three.js CSS3DRenderer 构建的 3D 元素周期表:118 个元素卡片支持周期表 / 球形 / 螺旋 / 网格四种布局平滑变换,点击任意元素展开详情面板,内嵌 WebGL 实时渲染的电子壳层原子模型。

3D 元素周期表 (Periodic Table)

太阳系和宇宙尺度都讲”大”,这个模块反过来讲”小”——构成万物的 118 种化学元素。它和前两个模块最大的技术差异是:主体渲染不走 WebGL,而是 CSS3DRenderer——每个元素就是一张真正的 HTML 卡片,文字天然清晰、可以直接用 CSS 做悬浮高亮,同时仍能在三维空间里旋转、变换、纵深排布。

目录结构

1
2
3
4
5
6
7
src/pages/periodic-table/
├── App.vue # 主页面组件
└── components/
├── PeriodicTableScene.ts # CSS3D 周期表场景(四种布局)
├── ElectronShellScene.ts # WebGL 电子壳层原子模型
├── elementData.ts # 元素数据与分类配色
└── elements.json # 118 个元素的数据集

架构说明

1. CSS3D 周期表 (PeriodicTableScene.ts)

核心是一个 PeriodicTableScene 类:

  • 渲染层:CSS3DRenderer + CSS3DObject,每张元素卡片是一个 div(序号、中文、符号、原子量),由 Three.js 只负责算出它的三维变换矩阵
  • 布局目标:为四种视图各预计算一组 {x, y, z, rx, ry} 目标位姿
  • 布局变换:transform(view, duration) 用 requestAnimationFrame 手写缓动动画(easeInOutCubic),把 118 张卡片从当前位姿插值到目标位姿
  • 控制器:TrackballControls 提供自由旋转、缩放、平移

2. 电子壳层模型 (ElectronShellScene.ts)

详情面板内嵌的原子模型,是另一个独立的小 WebGL 场景:

  • 原子核:自发光的橙红色小球
  • 电子层:每层一个 TorusGeometry 轨道环(双层正交环增强纵深),半径按 1.0 + i * 1.2 递增,七种颜色循环
  • 电子:每层最多渲染 12 个发光小球,沿各自倾斜的椭圆轨道独立运动,整层再叠加缓慢的绕轴旋转

选中元素变化时销毁旧场景、按新的 shells 数组重建,层数和电子数完全来自元素数据。

3. 数据层 (elementData.ts + elements.json)

118 个元素的完整数据集,每个元素包含:

  • 基础信息:原子序数、符号、中英文名、原子量、电子构型、电子层分布
  • 物理性质:熔点、沸点、密度、物态、导电性、磁性
  • 原子性质:电负性、原子/共价/范德华半径
  • 丰度数据:宇宙 / 地壳 / 海洋 / 人体中的含量
  • 发现史与简介

10 个分类(碱金属、碱土金属、过渡金属、准金属、非金属、卤素、稀有气体、镧系、锕系等)各配一套配色方案,驱动卡片背景、边框、文字颜色和底部图例。

四种布局变换

视图 排布方式
⊞ 周期表 标准 18 列布局,镧系/锕系单独放置在第 8、9 行
◉ 球形 斐波那契球面均匀分布,半径 800
◎ 螺旋 沿纵轴盘旋上升的螺旋带,卡片朝向随切线旋转
▦ 网格 5×5×5 立方点阵

布局切换是 118 张卡片同时进行的位姿插值,1.2 秒内完成。周期表布局的行列号由 getElementPosition 统一计算,主表元素取 p(周期)/g(族),镧系(57-71)和锕系(89-103)映射到下方独立行。

一个实战问题:TrackballControls 劫持点击

开发中遇到一个典型问题:点击元素卡片偶尔失效。

原因是 TrackballControls 内部调用了 setPointerCapture,把后续的 pointerup 事件全部捕获到画布容器上,导致挂在卡片 div 上的 click 事件永远无法触发。

解决方案:自己区分”点击”与”拖拽”——

  1. 卡片上监听 mousedown / touchstart,记录候选元素、按下坐标和时间戳
  2. 在渲染容器的 pointerup 上判定:移动距离 < 8px 且按压时长 < 300ms 才算点击
  3. 命中则回调 onClick 选中该元素

这样拖拽旋转和点选元素两个手势就完全互不干扰了。

详情面板

点击元素后右侧滑出毛玻璃详情卡片(移动端从底部滑出):

  • Hero 区:大号元素符号、序号、中英文名、分类徽章(随分类变色)
  • 概述:原子量、电子构型、周期/族、分区、物态、氧化态
  • 物理性质:熔点、沸点、密度、导电性、磁性
  • 原子性质:电负性、三种半径
  • 含量:宇宙、地壳、海洋、人体中的丰度(有数据才显示)
  • 发现:发现史描述
  • 电子层结构:分层电子数标签 + 实时 3D 原子模型
  • 简介:元素科普

响应式交互

  • 桌面端:面板靠右,可收起到屏幕边缘(只留当前元素符号的展开按钮)
  • 移动端:面板占屏幕下方 55vh,支持下拉收起 / 上拉展开的手势判定(滑动位移 > 50px 触发)

技术要点

  • CSS3D vs WebGL 的取舍:需要大量清晰文字时,CSS3DRenderer 是比”Canvas 贴文字纹理”更好的选择——渲染质量、可访问性、交互成本全面占优,代价是每个对象都是 DOM,数量要克制(118 张卡片刚好)
  • 手写缓动动画:布局变换不引动画库,t < 0.5 ? 4t³ : 1 - (-2t+2)³/2 的 easeInOutCubic 十几行搞定
  • 双渲染器共存:CSS3D 主场景 + WebGL 电子壳层,两个独立场景各管各的,销毁时分别清理
  • 事件冒泡陷阱:使用带 setPointerCapture 的控制器时,DOM 点击检测要自己兜底

Three.js 特性与实现

CSS3DRenderer:把 DOM 放进三维空间

本模块的核心特性选择是 **CSS3DRenderer + CSS3DObject**——Three.js 家族里的特殊渲染器:它不画 WebGL,而是为每个 CSS3DObject 计算三维变换矩阵,直接应用到对应 DOM 元素的 transform 上。

和 WebGL 方案对比:

CSS3DRenderer WebGLRenderer
文字渲染 原生 DOM,永远清晰 Canvas 贴图,缩放会糊
交互 元素天然可绑事件 需要 Raycaster 拾取
样式 CSS 全套能力(圆角/边框/阴影) 需要着色器手写
性能 每对象一个 DOM,百级为上限 万级无压力

118 张元素卡片刚好在 CSS3D 的舒适区内:卡片就是 div,悬浮高亮用 mouseenter/boxShadow 两行 CSS 搞定,点击直接绑事件,符号、序号、原子量用绝对定位排版——这些如果走 WebGL,每一项都要多写不少代码。

四种布局:目标位姿 + 手写缓动插值

变换的实现没有用动画库,也没有用 Three.js 的 Tween:

  1. 启动时为四种视图各预计算一组目标位姿 {x, y, z, rx, ry}(周期表按行列号排布、球形用斐波那契球面分布 phi = acos(-1 + 2i/n)、螺旋按索引递增的极角、网格 5×5×5 点阵)
  2. transform(view, duration) 记录所有卡片当前位姿作起点,之后每帧算归一化进度 t,套 easeInOutCubic(t < 0.5 ? 4t³ : 1 - (-2t+2)³/2),对 118 个对象做位置和旋转的线性插值
  3. 用 isAnimating 标志位防止动画中重复触发

纯 requestAnimationFrame + 数学,十几个字段的结构体数组就是全部状态。

TrackballControls 与 pointer capture 冲突

TrackballControls 提供”轨迹球”式的自由旋转缩放平移(比 OrbitControls 少了方位角/俯仰角限制,更适合全向浏览点阵)。但它在内部调用 setPointerCapture 把 pointerup 捕获到画布容器,导致卡片上的 click 事件失效——这是 CSS3D + TrackballControls 组合的经典冲突,解法是自判”短点击”(位移 < 8px 且时长 < 300ms),已在正文详述。

WebGL 子场景:电子壳层模型

详情面板里的原子模型是同一个页面里的第二个独立 Three.js 场景,回归传统 WebGL 渲染:

  • WebGLRenderer({ alpha: true }) 透明背景嵌在卡片内
  • 轨道用 TorusGeometry 圆环(每层两个正交环增强纵深),电子是开了 emissive 自发光的小球
  • 动画纯参数方程:电子沿各自倾斜轨道转,整层 Group 再绕 y 轴慢旋,不同层转速不同(0.15 + i * 0.04)制造视差
  • 每次切换元素 dispose() 旧场景重建,几何体材质全部回收

一个页面、两个渲染器(CSS3D + WebGL)各管各的区域,互不干扰——这也是 Three.js 混合渲染的典型用法。

资源回收

两个场景都实现了完整 dispose():cancelAnimationFrame 停循环、removeEventListener 摘监听、renderer.dispose() 释放渲染上下文、从 DOM 移除画布。Vue 的 onUnmounted 里统一调用。

技术栈

  • Vue 3: 组件框架(Composition API)
  • Three.js: CSS3DRenderer(元素卡片)+ WebGLRenderer(电子壳层)
  • TypeScript: 类型安全
  • TrackballControls: 自由视角控制器

threejs 3D元素周期表:CSS3D 与电子壳层动画
https://cszy.top/20260715-threejs3D元素周期表/
作者
csorz
发布于
2026年7月15日
许可协议