three.js 页面如何做到任意尺寸响应式:窗口缩放时画布不拉伸不模糊?

发布时间:2026/9/11 12:00:56
three.js 页面如何做到任意尺寸响应式:窗口缩放时画布不拉伸不模糊? three.js 页面如何做到任意尺寸响应式窗口缩放时画布不拉伸不模糊【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js在 three.js 应用中一个常见的问题是用 CSS 让画布铺满页面后一旦调整窗口大小画面里的物体比如立方体会被拉宽拉高而且画面发虚、有颗粒感。这两类问题都出在同一个环节——画布的 CSS 显示尺寸和画布内部的实际分辨率drawingbuffer 尺寸没有随窗口变化而更新。这篇文章给出 three.js 官方案例中处理该问题的完整做法用 CSS 控制显示尺寸在渲染循环里按需同步camera.aspect和renderer的 drawingbuffer 尺寸窗口任意缩放时画面既不变形也不模糊。方案适用于任何浏览器环境下使用WebGLRenderer的页面。不处理时的两类现象官方案例中都有截图说明拉伸CSS 把画布放大后场景内容被按比例拉宽拉高立方体变成扁盒子。原因是对数相机的aspect仍停留在创建时的初始值画布默认 300×150 对应的比例。模糊画布在页面上以更大的 CSS 尺寸显示但内部像素数没变等于把一张低分辨率图放大显示窗口拉得越大颗粒感越明显。下面按处理顺序拆解。第一步用 CSS 决定画布的显示尺寸three.js 页面推荐把显示多大完全交给 CSS。让画布铺满整个页面时加上如下样式官方示例 responsive.html 使用的写法style html, body { height: 100%; margin: 0; } #c { width: 100%; height: 100%; display: block; } /stylecanvas idc/canvas三点原因官方文档 responsive 教程 中有说明body默认有 5px 的外边距margin: 0去掉它html和body的height默认只到内容高度设为100%才能撑满窗口canvas 默认display: inline内联元素可能带入额外空白设为block消除。关键点显示尺寸只由 CSS 控制JS 不写死画布像素尺寸。这样后面把画布放进文字段落中间、编辑器式可拖拽的布局里时HTML/CSS 变了而 JS 代码一行都不用改。官方案例 responsive-paragraph.html 和 responsive-editor.html 验证了这一点。第二步同步 camera.aspect消除拉伸相机宽高比必须跟随画布当前显示尺寸。用画布的clientWidth/clientHeight计算并在变化后调用camera.updateProjectionMatrix()function render( time ) { time * 0.001; const canvas renderer.domElement; camera.aspect canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); // ... 渲染逻辑 }只做到这一步时立方体不再随窗口变形但模糊问题依然存在因为画布内部分辨率还是初始值。第三步按需更新 drawingbuffer 尺寸消除模糊画布有两个尺寸CSS 控制的是显示尺寸canvas.width/canvas.height是内部实际像素数drawingbuffer 尺寸。three.js 中通过renderer.setSize设置后者。官方案例给出的核心函数function resizeRendererToDisplaySize( renderer ) { const canvas renderer.domElement; const width canvas.clientWidth; const height canvas.clientHeight; const needResize canvas.width ! width || canvas.height ! height; if ( needResize ) { renderer.setSize( width, height, false ); } return needResize; }两个细节第三个参数必须传false。renderer.setSize默认会同时修改画布的 CSS style但这里显示尺寸已经由 CSS 决定再让 three.js 去改 CSS 反而会让 canvas 脱离页面中其他元素的布局规则见 Renderer.js 中updateStyle参数的说明。先比较再设置。canvas 规范中重设尺寸有额外开销尺寸没变就不要重复设置。函数返回needResize渲染循环里用它决定是否需要连带更新相机宽高比——宽高比只在显示尺寸变化时才需要重算function render( time ) { time * 0.001; if ( resizeRendererToDisplaySize( renderer ) ) { const canvas renderer.domElement; camera.aspect canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } // ... 渲染逻辑 renderer.render( scene, camera ); requestAnimationFrame( render ); }完整的可直接运行的示例见 manual/examples/responsive.html其中场景部分是三个自转的立方体import * as THREE from three; const canvas document.querySelector( #c ); const renderer new THREE.WebGLRenderer( { antialias: true, canvas } ); const fov 75; const aspect 2; // the canvas default const near 0.1; const far 5; const camera new THREE.PerspectiveCamera( fov, aspect, near, far ); camera.position.z 2; const scene new THREE.Scene(); const light new THREE.DirectionalLight( 0xffffff, 3 ); light.position.set( - 1, 2, 4 ); scene.add( light ); // ... 场景内容省略完整版本见 responsive.html示例文件里的three模块通过 importmap 指向仓库中的../../build/three.module.js对应 build/three.module.js在自己项目中使用打包或模块服务时把映射换成本地或包管理器提供的 three 路径即可。验证窗口任意缩放时检查两点官方文档给出的验证方式是把示例页在独立窗口打开然后拖拽改变窗口大小观察立方体是否始终保持立方比例不再被拉宽拉高对应拉伸问题的修复把窗口拉得很大时画面是否不再出现低分辨率的模糊和颗粒感对应 drawingbuffer 与显示尺寸一致后。教程页 manual/pages/responsive.html 中每一步都嵌入了对应阶段的交互示例未处理拉伸的 responsive-no-resize.html、只修拉伸的 responsive-update-camera.html、完整的 responsive.html可以在浏览器中逐步对比效果。可选HD-DPI 屏幕下按设备像素渲染上面的方案按 1 CSS 像素 1 绘制像素工作。对于高 DPI 设备文档表述为大多数 Mac、部分 Windows 设备、几乎所有手机1 个 CSS 像素背后可能有多个物理像素直接按 1:1 渲染会偏软。官方文档指出三种选择什么都不做——这是文档认为最常见的做法。高 DPI 手机的比例可达 3x意味着要渲染 9 倍像素重负载的 three.js 应用这样做通常能保住帧率用renderer.setPixelRatio( window.devicePixelRatio )让 three.js 内部自动放大 drawingbuffer——文档明确标注强烈不推荐strongly NOT RECOMMENDED因为之后 drawingbuffer 的实际尺寸和你传给setSize的尺寸不一致做后处理、读像素、截图、gl_FragCoord相关着色器时都得多一层猜测推荐做法在 resize 函数里自己乘window.devicePixelRatio始终保证请求的尺寸 实际尺寸function resizeRendererToDisplaySize( renderer ) { const canvas renderer.domElement; const pixelRatio window.devicePixelRatio; const width Math.floor( canvas.clientWidth * pixelRatio ); const height Math.floor( canvas.clientHeight * pixelRatio ); const needResize canvas.width ! width || canvas.height ! height; if ( needResize ) { renderer.setSize( width, height, false ); } return needResize; }可运行版本见 manual/examples/responsive-hd-dpi.html验证方式是在高 DPI 显示设备上与前文示例对比边缘会更锐利。限制 drawingbuffer 的最大像素数文档还指出一个边界情况部分系统在分数 UI 缩放下例如 OSX / Linux 的 150%物理分辨率 尺寸 × devicePixelRatio的假设不成立可能导致 GPU 负载过高、帧率下降、耗电增加。缓解方式是给内部分辨率设一个上限文档示例以 3840×2160 为默认上限function resizeRendererToDisplaySize( renderer, maxPixelCount3840*2160 ) { const canvas renderer.domElement; const pixelRatio window.devicePixelRatio; let width Math.floor( canvas.clientWidth * pixelRatio ); let height Math.floor( canvas.clientHeight * pixelRatio ); const pixelCount width * height; const renderScale pixelCount maxPixelCount ? Math.sqrt( maxPixelCount / pixelCount ) : 1; width Math.floor( width * renderScale ); height Math.floor( height * renderScale ); const needResize canvas.width ! width || canvas.height ! height; if ( needResize ) { renderer.setSize( width, height, false ); } return needResize; }maxPixelCount是函数参数可按自己项目的画质与性能目标调整不传参时使用文档给出的 3840×2160。适用范围与限制主路径CSS 控制显示尺寸 resizeRendererToDisplaySize 相机宽高比同步对任何用 CSS 控制 canvas 大小的布局都成立包括画布嵌在文本中间、侧边面板可拖拽的编辑器布局JS 代码无需改动renderer.setSize在 XR 呈现期间不生效Renderer.js 中xr.isPresenting时直接返回XR 场景不在本方案范围内是否启用 devicePixelRatio 渲染是画质与帧率的权衡文档给出的判断依据是应用负载重负载应用可以保持 1:1轻量内容在高 DPI 设备上可乘 pixelRatio 获得更清晰的边缘。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考