如何根据容器计算画布大小?为了避免滚动。
如果我根据窗口设置尺寸,画布太大了。
答案 0 :(得分:18)
可以说,调整three.js大小的最佳方法是使用它来编码它,因此它只接受CSS设置的画布大小。这样,无论你如何使用画布,你的代码都可以工作,不需要在不同的情况下改变它。
首先设置初始宽高比时,没有理由设置它,因为我们要根据画布大小的不同来设置它,所以它只是浪费代码设置两次
// There's no reason to set the aspect here because we're going
// to set it every frame anyway so we'll set it to 2 since 2
// is the the aspect for the canvas default size (300w/150h = 2)
const camera = new THREE.PerspectiveCamera(70, 2, 1, 1000);
然后我们需要一些代码来调整画布大小以匹配其显示大小
function resizeCanvasToDisplaySize() {
const canvas = renderer.domElement;
// look up the size the canvas is being displayed
const width = canvas.clientWidth;
const height = canvas.clientHeight;
// adjust displayBuffer size to match
if (canvas.width !== width || canvas.height !== height) {
// you must pass false here or three.js sadly fights the browser
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// update any render target sizes here
}
}
在渲染之前在渲染循环中调用它
function animate(time) {
time *= 0.001; // seconds
resizeCanvasToDisplaySize();
mesh.rotation.x = time * 0.5;
mesh.rotation.y = time * 1;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
这里有3个例子,3个例子之间的唯一区别是CSS以及我们是否制作画布或者是三个.js制作画布
示例1:全屏,我们制作画布
"use strict";
const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});
// There's no reason to set the aspect here because we're going
// to set it every frame anyway so we'll set it to 2 since 2
// is the the aspect for the canvas default size (300w/150h = 2)
const camera = new THREE.PerspectiveCamera(70, 2, 1, 1000);
camera.position.z = 400;
const scene = new THREE.Scene();
const geometry = new THREE.BoxGeometry(200, 200, 200);
const material = new THREE.MeshPhongMaterial({
color: 0x555555,
specular: 0xffffff,
shininess: 50,
shading: THREE.SmoothShading
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light1 = new THREE.PointLight(0xff80C0, 2, 0);
light1.position.set(200, 100, 300);
scene.add(light1);
function resizeCanvasToDisplaySize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width ||canvas.height !== height) {
// you must pass false here or three.js sadly fights the browser
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// set render target sizes here
}
}
function animate(time) {
time *= 0.001; // seconds
resizeCanvasToDisplaySize();
mesh.rotation.x = time * 0.5;
mesh.rotation.y = time * 1;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }

<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
&#13;
示例2:全屏画布,three.js制作画布
"use strict";
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
// There's no reason to set the aspect here because we're going
// to set it every frame anyway so we'll set it to 2 since 2
// is the the aspect for the canvas default size (300w/150h = 2)
const camera = new THREE.PerspectiveCamera(70, 2, 1, 1000);
camera.position.z = 400;
const scene = new THREE.Scene();
const geometry = new THREE.BoxGeometry(200, 200, 200);
const material = new THREE.MeshPhongMaterial({
color: 0x555555,
specular: 0xffffff,
shininess: 50,
shading: THREE.SmoothShading
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light1 = new THREE.PointLight(0xff80C0, 2, 0);
light1.position.set(200, 100, 300);
scene.add(light1);
function resizeCanvasToDisplaySize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width ||canvas.height !== height) {
// you must pass false here or three.js sadly fights the browser
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// set render target sizes here
}
}
function animate(time) {
time *= 0.001; // seconds
resizeCanvasToDisplaySize();
mesh.rotation.x = time * 0.5;
mesh.rotation.y = time * 1;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
&#13;
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
&#13;
示例3:内联画布
"use strict";
const renderer = new THREE.WebGLRenderer({canvas: document.querySelector(".diagram canvas")});
// There's no reason to set the aspect here because we're going
// to set it every frame anyway so we'll set it to 2 since 2
// is the the aspect for the canvas default size (300w/150h = 2)
const camera = new THREE.PerspectiveCamera(70, 2, 1, 1000);
camera.position.z = 400;
const scene = new THREE.Scene();
const geometry = new THREE.BoxGeometry(200, 200, 200);
const material = new THREE.MeshPhongMaterial({
color: 0x555555,
specular: 0xffffff,
shininess: 50,
shading: THREE.SmoothShading
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light1 = new THREE.PointLight(0xff80C0, 2, 0);
light1.position.set(200, 100, 300);
scene.add(light1);
function resizeCanvasToDisplaySize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width ||canvas.height !== height) {
// you must pass false here or three.js sadly fights the browser
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// set render target sizes here
}
}
function animate(time) {
time *= 0.001; // seconds
resizeCanvasToDisplaySize();
mesh.rotation.x = time * 0.5;
mesh.rotation.y = time * 1;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
&#13;
body { font-size: x-large; }
.diagram { width: 150px; height: 150px; float: left; margin: 1em; }
canvas { width: 100%; height: 100%; }
&#13;
<p>
Pretend this is a diagram in a physics lesson and it's inline. Notice we didn't have to change the code to handle this case.
<span class="diagram"><canvas></canvas></span>
The same code that handles fullscreen handles this case as well. The only difference is the CSS and how we look up the canvas. Otherwise it just works. We didn't have to change the code because we cooperated with the browser instead of fighting it.
</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
&#13;
示例4:50%宽度画布(如实时编辑器)
"use strict";
const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});
// There's no reason to set the aspect here because we're going
// to set it every frame anyway so we'll set it to 2 since 2
// is the the aspect for the canvas default size (300w/150h = 2)
const camera = new THREE.PerspectiveCamera(70, 2, 1, 1000);
camera.position.z = 400;
const scene = new THREE.Scene();
const geometry = new THREE.BoxGeometry(200, 200, 200);
const material = new THREE.MeshPhongMaterial({
color: 0x555555,
specular: 0xffffff,
shininess: 50,
shading: THREE.SmoothShading
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light1 = new THREE.PointLight(0xff80C0, 2, 0);
light1.position.set(200, 100, 300);
scene.add(light1);
function resizeCanvasToDisplaySize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width ||canvas.height !== height) {
// you must pass false here or three.js sadly fights the browser
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
// set render target sizes here
}
}
function animate(time) {
time *= 0.001; // seconds
resizeCanvasToDisplaySize();
mesh.rotation.x = time * 0.5;
mesh.rotation.y = time * 1;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
&#13;
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body { margin: 0; }
.outer {
}
.frame {
display: flex;
width: 100vw;
height: 100vh;
}
.frame>* {
flex: 1 1 50%;
}
#editor {
font-family: monospace;
padding: .5em;
background: #444;
color: white;
}
canvas {
width: 100%;
height: 100%;
}
&#13;
<div class="frame">
<div id="result">
<canvas></canvas>
</div>
<div id="editor">
explaintion of example on left or the code for it would go here
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
&#13;
通知window.innerWidth
和window.innerHeight
从未在上面的代码中引用,但它适用于所有情况。
答案 1 :(得分:3)
嗯,这并不困难。设置渲染的大小会起作用。
container = document.getElementById('container');
renderer.setSize($(container).width(), $(container).height());
container.appendChild(renderer.domElement);
答案 2 :(得分:1)
也许我在这里没有重点-但是为什么现有建议涉及从动画循环中调整画布的大小?
您希望动画循环尽可能少地执行,理想情况下每秒将重复执行30次以上,并且应进行优化以使其尽可能高效地运行-为运行该系统的最慢系统提供最大fps
我认为从resize事件侦听器中调用resize函数没有害处-就像下面的Meindert Stijfhals建议的那样。
类似这样的东西:
var container = renderer.domElement.parentElement;
container.addEventListener('resize', onContainerResize);
function onContainerResize() {
var box = container.getBoundingClientRect();
renderer.setSize(box.width, box.height);
camera.aspect = box.width/box.height
camera.updateProjectionMatrix()
// optional animate/renderloop call put here for render-on-changes
}
如果您设置了某种仅更改时渲染,则可以在resize函数的结尾处调用render函数。否则,下一次渲染循环触发时,应使用新设置进行渲染。
答案 3 :(得分:0)
我认为调整画布大小的最佳方法不是上面公认的答案。 每个animationframe @gman都将运行resizeCanvasToDisplaySize函数,进行多次计算。
我认为最好的方法是创建一个窗口事件列表器“调整大小”,并用布尔值表示用户是否调整大小。在动画框架中,您可以检查用户是否调整了大小。如果他调整了尺寸,则可以在animationframe中调整画布的尺寸。
let resized = false
// resize event listener
window.addEventListener('resize', function() {
resized = true
})
function animate(time) {
time *= 0.001
if (resized) resize()
// rotate the cube
cube.rotation.x += 0.01
cube.rotation.y += 0.01
// render the view
renderer.render(scene, camera)
// animate
requestAnimationFrame(animate)
}
function resize() {
resized = false
// update the size
renderer.setSize(window.innerWidth, window.innerHeight)
// update the camera
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth/canvas.clientHeight
camera.updateProjectionMatrix()
}
答案 4 :(得分:0)
@gman提供的答案当然是完整的答案。但是,要概括可能的情况,必须假定这两种可能性,这就是答案完全不同的原因。
第一种可能性是当容器是global(head)对象时:window
或<body>
或<frameset>
,在这种情况下,最方便的方法是使用{ {1}}。
第二种可能性是最初被问到的一种可能性:Three.js WebGL输出的容器何时应该响应。在这种情况下,无论容器是画布,分区还是div,处理调整大小的最佳方法是将容器定位为DOM,并通过将容器的window.addEventListener('resize', onResize()) { ... }
传递给容器来使用renderer.setsize()
方法和clientWidth
,然后在渲染循环函数中调用clientHeight
函数,@ gman提到了其完整实现。在这种情况下,无需使用onResize()
。
答案 5 :(得分:0)
以下代码使我能够掌握画布渲染尺寸的大小:
首先,场景在<div id="your_scene">
内部进行渲染,该场景具有您选择的尺寸:
<div class="relative h-512 w-512">
<div id="your_scene"></div>
</div>
CSS:
.relative {
position: relative;
}
h-512 {
height: 51.2rem;
}
w-512 {
width: 51.2rem;
}
#your_scene {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
然后,抓住它的尺寸:
this.mount = document.querySelector('#your_scene');
this.width = document.querySelector('#your_scene').offsetWidth;
this.height = document.querySelector('#your_scene').offsetHeight;
然后,(在我的情况下)您将渲染器背景设置为透明,设置像素比率,然后将容器的大小应用于渲染器。 Three.js的工作方式是在your_scene
容器中附加渲染器<canvas>
元素,因此这就是为什么这里的最后一步是到appendChild
:
this.renderer = new THREE.WebGLRenderer({ alpha: true });
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.width, this.height);
this.mount.appendChild(this.renderer.domElement);
希望这应该足以允许某人对其现场进行取证。完成工作最多的部分是setSize
,但您需要具有正确的尺寸,此外,必须将相机对准正确的位置。
如果您的代码类似于我的代码,但仍然无法正常工作,请查看您的相机透视图并确保场景对象实际上在视图中。
答案 6 :(得分:0)
我不确定以上评论或 API 文档是否对每个人都显而易见,但以防万一其他人遇到我遇到的问题:请注意,渲染器的 setSize()
方法在以下情况下采用 CSS 像素设置画布的大小,而不是实际的设备像素。这意味着在高 DPI 屏幕上,它创建的实际像素数似乎基于 width * window.devicePixelRatio
和 height * window.devicePixelRatio
。这很重要,例如,当您尝试执行我所做的事情时:根据来自 Three.js 的帧捕获特定大小的 GIF。我最终做了以下事情:
camera.aspect = gifWidth / gifHeight;
camera.updateProjectionMatrix();
var scale = window.devicePixelRatio;
renderer.setSize( gifWidth / scale, gifHeight / scale, true );
这可能不是处理它的理想方式,但它对我有用。