
本文详细讲解如何在现代 es 模块环境下正确引入并使用 cannon-es(cannon.js 的现代重构版)与 three.js 协同工作,解决因版本冲突、加载方式错误或初始化缺失导致的页面空白问题,并提供可直接运行的完整示例。
本文详细讲解如何在现代 es 模块环境下正确引入并使用 cannon-es(cannon.js 的现代重构版)与 three.js 协同工作,解决因版本冲突、加载方式错误或初始化缺失导致的页面空白问题,并提供可直接运行的完整示例。
在初学者使用物理引擎时,最常见的报错现象是“页面变白”或控制台抛出 ReferenceError: CANNON is not defined、Cannot find module 'cannon-es' 或 world.step is not a function 等错误——这通常并非代码逻辑错误,而是环境配置与依赖加载方式不匹配所致。原问题中用户混合使用了旧版 Cannon.js CDN、过时的 importmap 配置、未声明的 clock 实例,以及未同步物理世界与渲染帧的 world.step() 调用,最终导致脚本中断、渲染器无法初始化。
✅ 正确集成 cannon-es 的关键要点
统一使用 cannon-es(非 cannon.js)
cannon.js(v0.6.x)已停止维护,其全局变量注入方式(如 <script src="...cannon.js">)与 ES 模块环境(type="module")互斥,极易引发 CANNON is not defined。必须改用现代 ESM 兼容的 cannon-es(TypeScript 重写,Tree-shakable,API 更清晰)。-
通过 importmap 精确声明 cannon-es 源
原代码中 "cannon-es": "/cannon-es" 是无效路径;应使用稳定 CDN(如 jsDelivr)指向 .min.js 构建产物:<script type="importmap"> { "imports": { "cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js" } } </script>⚠️ 注意:cannon-es 版本需与 three 及其 addons 兼容(推荐 cannon-es@0.19.x + three@0.160+)。
Comprehensive Three.js 3D graphics reference下载详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
必须创建 Clock 并在每帧调用 world.step(delta)
物理模拟需时间步长驱动。遗漏 clock.getDelta() 或未调用 world.step() 将导致物体静止、碰撞失效,甚至因未处理的异步错误使整个模块加载失败。-
物理体与 Three.js 网格需手动同步位置/旋转
Cannon 与 Three.js 独立运行,需在渲染循环中显式同步:cube.position.copy(cubeBody.position); cube.quaternion.copy(cubeBody.quaternion);
? 完整可运行示例(单 HTML 文件)
以下为精简、健壮、零外部依赖的实现(复制即用):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Three.js + cannon-es Physics</title>
<style>body { margin: 0; overflow: hidden; }</style>
<!-- Import Maps -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.1/examples/jsm/standalone/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.1/examples/jsm/",
"cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js"
}
}
</script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import * as CANNON from 'cannon-es';
// === 1. 初始化物理世界 ===
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // 地球重力
// 创建通用物理材质(减少重复定义)
const defaultMaterial = new CANNON.Material('default');
const contactMaterial = new CANNON.ContactMaterial(
defaultMaterial, defaultMaterial,
{ friction: 0.1, restitution: 0.7 }
);
world.addContactMaterial(contactMaterial);
// === 2. 创建物理物体 ===
const groundBody = new CANNON.Body({ mass: 0 }); // mass=0 → 静态刚体
groundBody.addShape(new CANNON.Plane());
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);
const boxBody = new CANNON.Body({ mass: 1, material: defaultMaterial });
boxBody.addShape(new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)));
boxBody.position.set(0, 2, 0);
world.addBody(boxBody);
// === 3. 初始化 Three.js 场景 ===
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 3, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// 简单绿色立方体(视觉表现)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
// 地面(视觉)
const groundGeo = new THREE.PlaneGeometry(20, 20);
const groundMat = new THREE.MeshStandardMaterial({
color: 0xcccccc,
roughness: 0.8,
metalness: 0.2
});
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.receiveShadow = true;
scene.add(groundMesh);
// 光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7);
light.castShadow = true;
scene.add(light);
// 控制器(可选)
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// === 4. 主循环 ===
const clock = new THREE.Clock(); // 注意:使用 THREE.Clock(非 CANNON.Clock)
function animate() {
requestAnimationFrame(animate);
// 更新物理世界(关键!)
const delta = Math.min(clock.getDelta(), 0.1); // 防止大时间步长导致不稳定
world.step(delta);
// 同步 Three.js 网格位置
mesh.position.copy(boxBody.position);
mesh.quaternion.copy(boxBody.quaternion);
// 更新控制器 & 渲染
controls.update();
renderer.render(scene, camera);
}
// 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>? 常见错误排查清单
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 页面全白,控制台无报错 | importmap 未生效或浏览器不支持(需 Chrome 117+/Edge 117+) | 添加 <script nomodule>alert("请使用新版 Chrome 或 Edge");</script> 提示;或改用传统 <script type="module" src="..."> 分离文件 |
| Uncaught TypeError: Cannot read properties of undefined (reading 'World') | cannon-es 加载失败(CDN 404 或 CORS) | 检查 importmap 中 URL 是否可访问;改用 https://cdn.skypack.dev/cannon-es@0.19.0 备用源 |
| 方块下落但穿地/不反弹 | 未设置 contactMaterial 或 restitution=0 | 确保 world.addContactMaterial() 被调用,且 restitution > 0 |
| 移动卡顿或物理抖动 | delta 过大(如 world.step(0.5)) | 始终使用 clock.getDelta(),并添加 Math.min(delta, 0.1) 限幅 |
? 总结
成功集成 cannon-es 的核心在于:环境一致性(全 ESM)、依赖精确声明(cannon-es CDN)、物理-渲染双循环同步(world.step() + mesh.copy())。避免混用旧版 Cannon.js、全局脚本与模块化导入,是新手绕过“空白屏陷阱”的最短路径。后续可扩展方向包括:添加 CannonDebugRenderer 可视化碰撞体、接入 THREE.MeshStandardMaterial PBR 材质增强真实感,或使用 use-cannon(React)等高级封装进一步简化开发。
✅ 提示:本例已移除所有外部文件引用(如 /utils/cannonDebugRenderer.js),完全满足“单文件内实现”需求。

















