Skip to content

Three.js - 响应式设计

现代网页体验必须是响应式的,这意味着它应该在任何屏幕尺寸下都能良好地显示和运行,无论是大型桌面显示器还是小型智能手机。静态的Three.js场景在不同设备上可能会出现裁剪或拉伸。本章将介绍使你的3D场景完全响应式的基本技术。

默认情况下,Three.js 渲染场景的 <canvas> 元素具有固定尺寸。当浏览器窗口大小调整时,需要更新两项内容:

  1. 渲染器(Renderer):它需要知道新的尺寸来进行绘制。
  2. 相机(Camera):透视相机的 aspect 宽高比必须与新的视口尺寸匹配,以防止场景看起来被挤压或拉伸。

解决方案是监听浏览器的 resize 事件,并在处理函数中更新必要的组件。首先,我们来设置一个现代的项目结构。

我们将使用包含独立HTML、CSS和JavaScript文件的标准设置。强烈建议使用像 npm 这样的包管理器和像 Vite 这样的构建工具来管理依赖项和进行开发。

# 1. 创建项目文件夹并进入
mkdir my-threejs-app
cd my-threejs-app
# 2. 初始化Node项目并安装依赖
npm init -y
npm install three lil-gui
npm install --save-dev vite
# 3. 创建文件
touch index.html style.css main.js

HTML文件非常简洁。它提供了一个用于容纳 canvas 的容器,并以模块形式加载我们的主脚本。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式 Three.js 场景</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas class="webgl"></canvas>
<script type="module" src="./main.js"></script>
</body>
</html>

CSS确保我们的 canvas 填充整个视口。

* {
margin: 0;
padding: 0;
}
html, body {
overflow: hidden; /* 防止滚动条 */
}
.webgl {
position: fixed;
top: 0;
left: 0;
outline: none;
}

奇迹在此发生。我们设置场景并创建尺寸调整处理器。

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 尺寸
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
// 场景
const scene = new THREE.Scene();
// 对象
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1, 5, 5, 5),
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
);
scene.add(mesh);
// 相机
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);
camera.position.z = 3;
scene.add(camera);
// 渲染器
const canvas = document.querySelector('canvas.webgl');
const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// --- 尺寸调整处理器 ---
window.addEventListener('resize', () => {
// 更新尺寸
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// 更新相机
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
// 动画循环
const tick = () => {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
}
tick();

高密度屏幕(例如苹果的Retina显示屏)比逻辑像素拥有更多的物理像素。为了在这些屏幕上获得清晰锐利的渲染效果,你必须设置渲染器的像素比例。window.devicePixelRatio 提供了这个值。我们使用 Math.min() 将其上限设置为2,因为更高的值在视觉效果上提升有限,但会严重影响性能。

// 为高DPI屏幕设置像素比例以实现清晰渲染
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

锯齿(Aliasing)会导致3D对象的边缘看起来不平滑或呈“阶梯状”。通过在创建渲染器时启用抗锯齿(anti-aliasing),我们可以平滑这些边缘。请注意,这会带来性能开销,因此请谨慎使用。

// 在实例化渲染器时启用抗锯齿
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
  • 忘记调用 camera.updateProjectionMatrix():在更改相机的 aspect 宽高比(或任何其他基本属性,如 fov)之后,你必须调用此方法来应用这些更改。
  • 性能:resize 事件触发可能非常频繁。对于复杂的尺寸调整逻辑,你可能需要考虑对事件处理器进行“防抖”(debouncing),以防止它运行过于频繁并导致性能问题。
  • 可访问性:<canvas> 元素默认情况下无法被屏幕阅读器访问。为了更好的可访问性,请提供备用内容:<canvas>一个旋转立方体的3D可视化。</canvas>