Javascript Threejs画布大小基于容器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29884485/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 04:13:39  来源:igfitidea点击:

Threejs canvas size based on container

javascriptthree.js

提问by Francesco1984

How can I calculate canvas size based on its container? To avoid scrolling.

如何根据其容器计算画布大小?避免滚动。

If I set the size based on window the canvas is too big.

如果我根据窗口设置大小,则画布太大。

采纳答案by aboutqx

Well,that's not difficult.Set your render's size will work.

嗯,这并不难。设置渲染的大小会起作用。

container = document.getElementById('container');
renderer.setSize($(container).width(), $(container).height());
container.appendChild(renderer.domElement);

回答by gman

Arguably the best way to resize three.js use to code it so it just accepts whatever size the canvas is as set by CSS. That way, no matter how you use the canvas your code will work, no need to change it for different situations.

可以说,调整three.js 大小的最佳方法是对其进行编码,因此它只接受CSS 设置的画布大小。这样,无论您如何使用画布,您的代码都可以正常工作,无需针对不同情况进行更改。

First off when setting the initial aspect ratio there's no reason to set it because we're going to set it in response to the size of the canvas being different so it's just a waste of code to set it twice

首先在设置初始纵横比时没有理由设置它,因为我们将设置它以响应画布的大小不同,所以设置它两次只是浪费代码

// 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);

Then we need some code that will resize the canvas to match its display size

然后我们需要一些代码来调整画布的大小以匹配其显示大小

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
  }
}

Call this in your render loop before rendering

在渲染之前在渲染循环中调用它

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);

Here's 3 examples, the only difference between the 3 examples is the CSS and whether we make the canvas or three.js makes the canvas

这里有 3 个例子,这 3 个例子之间唯一的区别是 CSS 以及是我们制作画布还是 Three.js 制作画布

Example 1: fullscreen, We make the canvas

示例 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>

Example 2: fullscreen canvas, three.js makes the canvas

示例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);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>

Example 3: inline canvas

示例 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);
body { font-size: x-large; }
.diagram { width: 150px; height: 150px; float: left; margin: 1em; }
canvas { width: 100%; height: 100%; }
<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>

Example 4: 50% width canvas (like a live editor)

示例 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);
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%;
}
<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>

notice window.innerWidthand window.innerHeightare never referenced in the code above and yet it works for all cases.

注意window.innerWidth并且window.innerHeight在上面的代码中从未被引用过,但它适用于所有情况。

回答by Meindert Stijfhals

I think that the best way to resize the canvas is not the accepted answer above. Every animationframe @gman will run the function resizeCanvasToDisplaySize, doing multiple calculations.

我认为调整画布大小的最佳方法不是上面接受的答案。每个动画帧@gman 都会运行resizeCanvasToDisplaySize 函数,进行多次计算。

I think that the best way is to create a window event lister 'resize' and a boolean saying wether the user resized or not. In the animation frame you can check wether the user resized. If he resized, you can resize the canvas in the 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()
}

回答by blueFrog

Maybe I'm missing the point here - but why do the existing suggestions involve resizing the canvas from within the animation loop?

也许我在这里忽略了这一点 - 但为什么现有的建议涉及从动画循环内调整画布的大小?

You'd want your animation loop to do as little as possible, as it will ideally be repeated 30+ times a second, and should be optimized to run as efficiently as possible - affording the maximum fps to the slowest system running it.

您希望您的动画循环尽可能少地执行,因为理想情况下它每秒重复 30 次以上,并且应该优化以尽可能高效地运行 - 为运行它的最慢系统提供最大 fps。

I think there's no harm in calling the resize function from within the resize event listener - much like Meindert Stijfhals suggested below.

我认为从 resize 事件侦听器中调用 resize 函数没有害处 - 就像下面建议的 Meindert Stijfhals 一样。

Something like this:

像这样的东西:

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
}

If you have some kind of render-only-on-changes set up, you can call the render function at the end of the resize function. Otherwise the next time the render loop does fire it should just render with the new settings.

如果您设置了某种仅在更改时渲染,则可以在调整大小函数的末尾调用渲染函数。否则,下次渲染循环确实触发时,它应该只使用新设置进行渲染。

回答by Abraham

Of course the answer provided by @gman is the full-fledged answer. However, to recap the possible scenarios, one has to assume the two possibilities, which is the reason why the answers differ drastically.

当然,@gman 提供的答案是成熟的答案。然而,要回顾一下可能的情况,我们必须假设这两种可能性,这就是答案大相径庭的原因。

The first possibility is when the container is the global(head) object: window, or maybe <body>or <frameset>, in which case, the most convenient method would be using window.addEventListener('resize', onResize()) { ... }.

第一种可能性是当容器是 global(head) 对象时:window,或者可能<body><frameset>,在这种情况下,最方便的方法是使用window.addEventListener('resize', onResize()) { ... }.

The second possibility, is the one which has been originally asked: when the container of the Three.js WebGL output should be responsive. In that case, whether the container is canvas, a section or a div, the best way to handle the resizing is to target the container in DOM, and use the renderer.setsize()method by passing it the container's clientWidthand clientHeight, and then calling onResize()function in the render loop function, which its complete implementation is aforementioned by @gman. In this particular case, there is no need to use addEventListener.

第二种可能性,是最初被问到的一种可能性:Three.js WebGL 输出的容器何时应该响应。在这种情况下,无论容器是画布、节还是 div,处理大小调整的最佳方法是将容器定位在 DOM 中,并renderer.setsize()通过将容器的clientWidthand传递给它来使用该方法clientHeight,然后onResize()在渲染循环中调用函数函数,@gman 已经提到了它的完整实现。在这种特殊情况下,不需要使用addEventListener.