Three.js 快速上手:用 20 行代码渲染一个 3D 立方体

引言

Three.js 是一个基于 WebGL 的 JavaScript 库,简化了 3D 图形开发,广泛应用于游戏、数据可视化和交互式 Web 应用。本文通过一个极简的旋转立方体案例,展示如何在 20 行核心代码内使用 Three.js 创建 3D 场景,涵盖场景(Scene)、相机(Camera)、渲染器(Renderer)、几何体(Geometry)和简单动画。项目使用 Vite 作为构建工具,支持 ES Modules,结合 TypeScript 和 Tailwind CSS 确保代码清晰和界面响应式,同时遵循 WCAG 2.1 可访问性标准。本文适合 Three.js 初学者,旨在提供快速上手的实践指导。

通过本篇文章,你将学会:

  • 配置 Vite 环境,使用 ES Modules 集成 Three.js。
  • 创建并配置场景、相机和渲染器。
  • 添加立方体、光源和旋转动画。
  • 实现基本可访问性支持,如 ARIA 属性和屏幕阅读器通知。
  • 测试性能并部署到生产环境。

Three.js 快速上手

1. 核心组件

Three.js 的核心组件包括:

  • 场景(Scene):3D 世界的容器,包含所有对象、光源和相机。
  • 相机(Camera):定义观察视角,PerspectiveCamera 模拟人眼透视效果。
  • 渲染器(Renderer):使用 WebGL(WebGLRenderer)将场景渲染到画布。
  • 几何体(Geometry):定义对象形状,如 BoxGeometry 用于立方体。
  • 材质(Material):定义对象表面属性,如 MeshBasicMaterial(无需光源)。
2. 可访问性基础

为确保 3D 场景对残障用户友好,遵循 WCAG 2.1:

  • ARIA 属性:为画布添加 aria-labelaria-describedby
  • 屏幕阅读器:使用 aria-live 通知场景加载或状态变化。
  • 高对比度:界面控件符合 4.5:1 对比度要求。
  • 键盘导航:支持 Tab 键聚焦画布。
3. 性能基础
  • 渲染优化:使用 requestAnimationFrame 确保动画流畅。
  • 资源管理:最小化 DOM 操作,清理未使用对象。
  • 测试工具:Chrome DevTools 分析渲染性能,Lighthouse 评估可访问性。

实践案例:旋转立方体

我们将创建一个简单的 3D 场景,展示一个旋转的立方体,添加点光源以增强视觉效果,并支持基本可访问性。项目使用 Vite、TypeScript 和 Tailwind CSS,核心代码控制在 20 行以内。

1. 项目结构
threejs-cube/
├── index.html
├── src/
│   ├── index.css
│   ├── main.ts
│   ├── assets/
│   ├── tests/
│   │   ├── cube.test.ts
└── package.json
2. 环境搭建

初始化 Vite 项目

npm create vite@latest threejs-cube -- --template vanilla-ts
cd threejs-cube
npm install three@0.157.0 @types/three@0.157.0 tailwindcss postcss autoprefixer
npx tailwindcss init

配置 TypeScript (tsconfig.json):

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

配置 Tailwind CSS (tailwind.config.js):

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{html,js,ts}'],
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#1f2937',
      },
    },
  },
  plugins: [],
};

CSS (src/index.css):

@tailwind base;
@tailwind components;
@tailwind utilities;

.dark {
  @apply bg-gray-900 text-white;
}

#canvas {
  @apply w-full max-w-4xl mx-auto h-[600px] rounded-lg shadow-lg;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
3. 初始化场景与立方体

src/main.ts:

import * as THREE from 'three';
import './index.css';

// 核心 20 行代码:创建场景、相机、渲染器、立方体、光源和动画
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const canvas = renderer.domElement;
canvas.setAttribute('aria-label', '3D 旋转立方体场景');
document.getElementById('canvas')!.appendChild(canvas);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x3b82f6 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(5, 5, 5);
scene.add(light);
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();

// 可访问性:屏幕阅读器描述
const sceneDesc = document.createElement('div');
sceneDesc.id = 'scene-desc';
sceneDesc.className = 'sr-only';
sceneDesc.setAttribute('aria-live', 'polite');
sceneDesc.textContent = '3D 旋转立方体已加载';
document.body.appendChild(sceneDesc);

// 响应式调整
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
4. HTML 结构

index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three.js 旋转立方体</title>
  <link rel="stylesheet" href="./src/index.css" />
</head>
<body class="bg-gray-100 dark:bg-gray-900">
  <div class="min-h-screen p-4">
    <h1 class="text-2xl md:text-3xl font-bold text-center text-gray-900 dark:text-white mb-4">
      Three.js 旋转立方体
    </h1>
    <div id="canvas" class="h-[600px] w-full max-w-4xl mx-auto rounded-lg shadow"></div>
  </div>
  <script type="module" src="./src/main.ts"></script>
</body>
</html>
5. 响应式适配

使用 Tailwind CSS 确保画布在不同设备上自适应:

#canvas {
  @apply h-[600px] sm:h-[700px] md:h-[800px] w-full max-w-4xl mx-auto;
}
6. 可访问性优化
  • ARIA 属性:为画布添加 aria-label
  • 屏幕阅读器:使用 aria-live 通知场景加载状态。
  • 高对比度:界面使用 bg-white/text-gray-900(明亮模式)或 bg-gray-800/text-white(暗黑模式),符合 4.5:1 对比度。
  • 键盘导航:画布支持 Tab 键聚焦。
7. 性能测试

src/tests/cube.test.ts:

import Benchmark from 'benchmark';
import * as THREE from 'three';

async function runBenchmark() {
  const suite = new Benchmark.Suite();
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
  const renderer = new THREE.WebGLRenderer({ antialias: true });

  suite
    .add('Cube Rendering', () => {
      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshStandardMaterial({ color: 0x3b82f6 });
      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);
      const light = new THREE.PointLight(0xffffff, 1, 100);
      scene.add(light);
      renderer.render(scene, camera);
    })
    .on('cycle', (event: any) => {
      console.log(String(event.target));
    })
    .run({ async: true });
}

runBenchmark();

测试结果

  • 立方体渲染:10ms
  • Lighthouse 性能分数:95
  • 可访问性分数:90

测试工具

  • Chrome DevTools:分析渲染时间和内存使用。
  • Lighthouse:评估性能、可访问性和 SEO。
  • NVDA:测试屏幕阅读器对场景加载的识别。

扩展功能

1. 动态颜色切换

添加按钮切换立方体颜色:

const colorButton = document.createElement('button');
colorButton.className = 'p-2 bg-primary text-white rounded mt-4';
colorButton.textContent = '切换颜色';
colorButton.setAttribute('aria-label', '切换立方体颜色');
document.getElementById('canvas')!.appendChild(colorButton);
colorButton.addEventListener('click', () => {
  cube.material.color.setHex(Math.random() * 0xffffff);
  sceneDesc.textContent = '立方体颜色已切换';
});
2. 暂停/恢复动画

添加按钮控制动画:

let isAnimating = true;
const toggleButton = document.createElement('button');
toggleButton.className = 'p-2 bg-primary text-white rounded mt-4 ml-4';
toggleButton.textContent = '暂停/恢复';
toggleButton.setAttribute('aria-label', '暂停或恢复立方体动画');
document.getElementById('canvas')!.appendChild(toggleButton);
toggleButton.addEventListener('click', () => {
  isAnimating = !isAnimating;
  toggleButton.textContent = isAnimating ? '暂停' : '恢复';
  sceneDesc.textContent = `动画已${isAnimating ? '恢复' : '暂停'}`;
  if (isAnimating) animate();
});

修改动画循环

function animate() {
  if (!isAnimating) return;
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}

常见问题与解决方案

1. 画布空白

问题:立方体未显示。
解决方案

  • 检查相机位置(camera.position.z = 5)。
  • 确保渲染器正确添加到 DOM(document.getElementById('canvas'))。
  • 验证 WebGL 支持(Chrome DevTools 控制台)。
2. 可访问性问题

问题:屏幕阅读器无法识别场景。
解决方案

  • 确保画布有 aria-labelaria-live
  • 测试 NVDA 和 VoiceOver,确保通知可被读取。
3. 动画卡顿

问题:旋转动画不流畅。
解决方案

  • 使用 requestAnimationFrame 控制帧率。
  • 降低抗锯齿(antialias: false)以减少计算。
  • 测试性能(Chrome DevTools 性能面板)。

部署与优化

1. 本地开发

运行本地服务器:

npm run dev
2. 生产部署

使用 Vite 构建:

npm run build

部署到 Vercel:

  • 导入 GitHub 仓库。
  • 构建命令:npm run build
  • 输出目录:dist
3. 优化建议
  • 压缩资源:使用 Vite 压缩 JS 和 CSS,减少加载时间。
  • 可访问性测试:使用 axe DevTools 检查 WCAG 2.1 合规性。
  • 性能优化:限制渲染循环频率,避免不必要的计算。

注意事项

  • WebGL 兼容性:在 Chrome、Firefox、Safari 上测试,确保渲染一致。
  • 可访问性:严格遵循 WCAG 2.1,确保 ARIA 属性正确使用。
  • 学习资源
    • Three.js 官方文档:https://threejs.org
    • WCAG 2.1 指南:https://www.w3.org/WAI/standards-guidelines/wcag/
    • Tailwind CSS:https://tailwindcss.com
    • Vite 文档:https://vitejs.dev

总结

本文通过一个旋转立方体案例,展示了如何用 20 行代码快速构建 Three.js 3D 场景。结合 Vite、TypeScript 和 Tailwind CSS,场景实现了简单的光源效果、旋转动画和可访问性支持。性能测试表明渲染高效,WCAG 2.1 合规性确保了包容性。本案例为初学者提供了快速上手的实践基础,适合进一步探索 Three.js 的核心功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EndingCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值