You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
16 KiB
374 lines
16 KiB
2 years ago
|
<!DOCTYPE html><html lang="en"><head>
|
||
|
<meta charset="utf-8">
|
||
|
<title>Tips</title>
|
||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||
|
<meta name="twitter:card" content="summary_large_image">
|
||
|
<meta name="twitter:site" content="@threejs">
|
||
|
<meta name="twitter:title" content="Three.js – Tips">
|
||
|
<meta property="og:image" content="https://threejs.org/files/share.png">
|
||
|
<link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
|
||
|
<link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
|
||
|
|
||
|
<link rel="stylesheet" href="/manual/resources/lesson.css">
|
||
|
<link rel="stylesheet" href="/manual/resources/lang.css">
|
||
|
<!-- Import maps polyfill -->
|
||
|
<!-- Remove this when import maps will be widely supported -->
|
||
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
||
|
|
||
|
<script type="importmap">
|
||
|
{
|
||
|
"imports": {
|
||
|
"three": "../../build/three.module.js"
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div class="container">
|
||
|
<div class="lesson-title">
|
||
|
<h1>Tips</h1>
|
||
|
</div>
|
||
|
<div class="lesson">
|
||
|
<div class="lesson-main">
|
||
|
<p>This article is a collection of small issues you might run into
|
||
|
using three.js that seemed too small to have their own article.</p>
|
||
|
<hr>
|
||
|
<p><a id="screenshot" data-toc="Taking a screenshot"></a></p>
|
||
|
<h1 id="taking-a-screenshot-of-the-canvas">Taking A Screenshot of the Canvas</h1>
|
||
|
<p>In the browser there are effectively 2 functions that will take a screenshot.
|
||
|
The old one
|
||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL"><code class="notranslate" translate="no">canvas.toDataURL</code></a>
|
||
|
and the new better one
|
||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob"><code class="notranslate" translate="no">canvas.toBlob</code></a></p>
|
||
|
<p>So you'd think it would be easy to take a screenshot by just adding some code like</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><canvas id="c"></canvas>
|
||
|
+<button id="screenshot" type="button">Save...</button>
|
||
|
</pre>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const elem = document.querySelector('#screenshot');
|
||
|
elem.addEventListener('click', () => {
|
||
|
canvas.toBlob((blob) => {
|
||
|
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
const saveBlob = (function() {
|
||
|
const a = document.createElement('a');
|
||
|
document.body.appendChild(a);
|
||
|
a.style.display = 'none';
|
||
|
return function saveData(blob, fileName) {
|
||
|
const url = window.URL.createObjectURL(blob);
|
||
|
a.href = url;
|
||
|
a.download = fileName;
|
||
|
a.click();
|
||
|
};
|
||
|
}());
|
||
|
</pre>
|
||
|
<p>Here's the example from <a href="responsive.html">the article on responsiveness</a>
|
||
|
with the code above added and some CSS to place the button</p>
|
||
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-screenshot-bad.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-screenshot-bad.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
<p>When I tried it I got this screenshot</p>
|
||
|
<div class="threejs_center"><img src="../resources/images/screencapture-413x313.png"></div>
|
||
|
|
||
|
<p>Yes, it's just a black image.</p>
|
||
|
<p>It's possible it worked for you depending on your browser/OS but in general
|
||
|
it's not likely to work.</p>
|
||
|
<p>The issue is that for performance and compatibility reasons, by default the browser
|
||
|
will clear a WebGL canvas's drawing buffer after you've drawn to it.</p>
|
||
|
<p>The solution is to call your rendering code just before capturing.</p>
|
||
|
<p>In our code we need to adjust a few things. First let's separate
|
||
|
out the rendering code.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const state = {
|
||
|
+ time: 0,
|
||
|
+};
|
||
|
|
||
|
-function render(time) {
|
||
|
- time *= 0.001;
|
||
|
+function render() {
|
||
|
if (resizeRendererToDisplaySize(renderer)) {
|
||
|
const canvas = renderer.domElement;
|
||
|
camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
||
|
camera.updateProjectionMatrix();
|
||
|
}
|
||
|
|
||
|
cubes.forEach((cube, ndx) => {
|
||
|
const speed = 1 + ndx * .1;
|
||
|
- const rot = time * speed;
|
||
|
+ const rot = state.time * speed;
|
||
|
cube.rotation.x = rot;
|
||
|
cube.rotation.y = rot;
|
||
|
});
|
||
|
|
||
|
renderer.render(scene, camera);
|
||
|
|
||
|
- requestAnimationFrame(render);
|
||
|
}
|
||
|
|
||
|
+function animate(time) {
|
||
|
+ state.time = time * 0.001;
|
||
|
+
|
||
|
+ render();
|
||
|
+
|
||
|
+ requestAnimationFrame(animate);
|
||
|
+}
|
||
|
+requestAnimationFrame(animate);
|
||
|
</pre>
|
||
|
<p>Now that <code class="notranslate" translate="no">render</code> is only concerned with actually rendering
|
||
|
we can call it just before capturing the canvas.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const elem = document.querySelector('#screenshot');
|
||
|
elem.addEventListener('click', () => {
|
||
|
+ render();
|
||
|
canvas.toBlob((blob) => {
|
||
|
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
|
||
|
});
|
||
|
});
|
||
|
</pre>
|
||
|
<p>And now it should work.</p>
|
||
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-screenshot-good.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-screenshot-good.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
<p>For a different solution see the next item.</p>
|
||
|
<hr>
|
||
|
<p><a id="preservedrawingbuffer" data-toc="Prevent the Canvas Being Cleared"></a></p>
|
||
|
<h1 id="preventing-the-canvas-being-cleared">Preventing the canvas being cleared</h1>
|
||
|
<p>Let's say you wanted to let the user paint with an animated
|
||
|
object. You need to pass in <code class="notranslate" translate="no">preserveDrawingBuffer: true</code> when
|
||
|
you create the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>. This prevents the browser from
|
||
|
clearing the canvas. You also need to tell three.js not to clear
|
||
|
the canvas as well.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
|
||
|
-const renderer = new THREE.WebGLRenderer({canvas});
|
||
|
+const renderer = new THREE.WebGLRenderer({
|
||
|
+ canvas,
|
||
|
+ preserveDrawingBuffer: true,
|
||
|
+ alpha: true,
|
||
|
+});
|
||
|
+renderer.autoClearColor = false;
|
||
|
</pre>
|
||
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-preservedrawingbuffer.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-preservedrawingbuffer.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
<p>Note that if you were serious about making a drawing program this would not be a
|
||
|
solution as the browser will still clear the canvas anytime we change its
|
||
|
resolution. We're changing is resolution based on its display size. Its display
|
||
|
size changes when the window changes size. That includes when the user downloads
|
||
|
a file, even in another tab, and the browser adds a status bar. It also includes when
|
||
|
the user turns their phone and the browser switches from portrait to landscape.</p>
|
||
|
<p>If you really wanted to make a drawing program you'd
|
||
|
<a href="rendertargets.html">render to a texture using a render target</a>.</p>
|
||
|
<hr>
|
||
|
<p><a id="tabindex" data-toc="Get Keyboard Input From a Canvas"></a></p>
|
||
|
<h1 id="getting-keyboard-input">Getting Keyboard Input</h1>
|
||
|
<p>Throughout these tutorials we've often attached event listeners to the <code class="notranslate" translate="no">canvas</code>.
|
||
|
While many events work, one that does not work by default is keyboard
|
||
|
events.</p>
|
||
|
<p>To get keyboard events, set the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex"><code class="notranslate" translate="no">tabindex</code></a>
|
||
|
of the canvas to 0 or more. Eg.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><canvas tabindex="0"></canvas>
|
||
|
</pre>
|
||
|
<p>This ends up causing a new issue though. Anything that has a <code class="notranslate" translate="no">tabindex</code> set
|
||
|
will get highlighted when it has the focus. To fix that set its focus CSS outline
|
||
|
to none</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">canvas:focus {
|
||
|
outline:none;
|
||
|
}
|
||
|
</pre>
|
||
|
<p>To demonstrate here are 3 canvases </p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><canvas id="c1"></canvas>
|
||
|
<canvas id="c2" tabindex="0"></canvas>
|
||
|
<canvas id="c3" tabindex="1"></canvas>
|
||
|
</pre>
|
||
|
<p>and some css just for the last canvas </p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#c3:focus {
|
||
|
outline: none;
|
||
|
}
|
||
|
</pre>
|
||
|
<p>Let's attach the same event listeners to all of them</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">document.querySelectorAll('canvas').forEach((canvas) => {
|
||
|
const ctx = canvas.getContext('2d');
|
||
|
|
||
|
function draw(str) {
|
||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
ctx.textAlign = 'center';
|
||
|
ctx.textBaseline = 'middle';
|
||
|
ctx.fillText(str, canvas.width / 2, canvas.height / 2);
|
||
|
}
|
||
|
draw(canvas.id);
|
||
|
|
||
|
canvas.addEventListener('focus', () => {
|
||
|
draw('has focus press a key');
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('blur', () => {
|
||
|
draw('lost focus');
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('keydown', (e) => {
|
||
|
draw(`keyCode: ${e.keyCode}`);
|
||
|
});
|
||
|
});
|
||
|
</pre>
|
||
|
<p>Notice you can't get the first canvas to accept keyboard input.
|
||
|
The second canvas you can but it gets highlighted. The 3rd
|
||
|
canvas has both solutions applied.</p>
|
||
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-tabindex.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-tabindex.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
<hr>
|
||
|
<p><a id="transparent-canvas" data-toc="Make the Canvas Transparent"></a></p>
|
||
|
<h1 id="making-the-canvas-transparent">Making the Canvas Transparent</h1>
|
||
|
<p>By default THREE.js makes the canvas opaque. If you want the
|
||
|
canvas to be transparent pass in <a href="/docs/#api/en/renderers/WebGLRenderer#alpha"><code class="notranslate" translate="no">alpha:true</code></a> when you create
|
||
|
the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a></p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
|
||
|
-const renderer = new THREE.WebGLRenderer({canvas});
|
||
|
+const renderer = new THREE.WebGLRenderer({
|
||
|
+ canvas,
|
||
|
+ alpha: true,
|
||
|
+});
|
||
|
</pre>
|
||
|
<p>You probably also want to tell it that your results are <strong>not</strong> using premultiplied alpha</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
|
||
|
const renderer = new THREE.WebGLRenderer({
|
||
|
canvas,
|
||
|
alpha: true,
|
||
|
+ premultipliedAlpha: false,
|
||
|
});
|
||
|
</pre>
|
||
|
<p>Three.js defaults to the canvas using
|
||
|
<a href="/docs/#api/en/renderers/WebGLRenderer#premultipliedAlpha"><code class="notranslate" translate="no">premultipliedAlpha: true</code></a> but defaults
|
||
|
to materials outputting <a href="/docs/#api/en/materials/Material#premultipliedAlpha"><code class="notranslate" translate="no">premultipliedAlpha: false</code></a>.</p>
|
||
|
<p>If you'd like a better understanding of when and when not to use premultiplied alpha
|
||
|
here's <a href="https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre">a good article on it</a>.</p>
|
||
|
<p>In any case let's setup a simple example with a transparent canvas.</p>
|
||
|
<p>We applied the settings above to the example from <a href="responsive.html">the article on responsiveness</a>.
|
||
|
Let's also make the materials more transparent.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
|
||
|
- const material = new THREE.MeshPhongMaterial({color});
|
||
|
+ const material = new THREE.MeshPhongMaterial({
|
||
|
+ color,
|
||
|
+ opacity: 0.5,
|
||
|
+ });
|
||
|
|
||
|
...
|
||
|
</pre>
|
||
|
<p>And let's add some HTML content</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
|
||
|
<canvas id="c"></canvas>
|
||
|
+ <div id="content">
|
||
|
+ <div>
|
||
|
+ <h1>Cubes-R-Us!</h1>
|
||
|
+ <p>We make the best cubes!</p>
|
||
|
+ </div>
|
||
|
+ </div>
|
||
|
</body>
|
||
|
</pre>
|
||
|
<p>as well as some CSS to put the canvas in front</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">body {
|
||
|
margin: 0;
|
||
|
}
|
||
|
#c {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
display: block;
|
||
|
+ position: fixed;
|
||
|
+ left: 0;
|
||
|
+ top: 0;
|
||
|
+ z-index: 2;
|
||
|
+ pointer-events: none;
|
||
|
}
|
||
|
+#content {
|
||
|
+ font-size: 7vw;
|
||
|
+ font-family: sans-serif;
|
||
|
+ text-align: center;
|
||
|
+ width: 100%;
|
||
|
+ height: 100%;
|
||
|
+ display: flex;
|
||
|
+ justify-content: center;
|
||
|
+ align-items: center;
|
||
|
+}
|
||
|
</pre>
|
||
|
<p>note that <code class="notranslate" translate="no">pointer-events: none</code> makes the canvas invisible to the mouse
|
||
|
and touch events so you can select the text beneath.</p>
|
||
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-transparent-canvas.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-transparent-canvas.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
<hr>
|
||
|
<p><a id="html-background" data-toc="Use three.js as Background in HTML"></a></p>
|
||
|
<h1 id="making-your-background-a-three-js-animation">Making your background a three.js animation</h1>
|
||
|
<p>A common question is how to make a three.js animation be the background of
|
||
|
a webpage.</p>
|
||
|
<p>There are 2 obvious ways.</p>
|
||
|
<ul>
|
||
|
<li>Set the canvas CSS <code class="notranslate" translate="no">position</code> to <code class="notranslate" translate="no">fixed</code> as in</li>
|
||
|
</ul>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#c {
|
||
|
position: fixed;
|
||
|
left: 0;
|
||
|
top: 0;
|
||
|
...
|
||
|
}
|
||
|
</pre>
|
||
|
<p>You can basically see this exact solution on the previous example. Just set <code class="notranslate" translate="no">z-index</code> to -1
|
||
|
and the cubes will appear behind the text.</p>
|
||
|
<p>A small disadvantage to this solution is your JavaScript must integrate with the page
|
||
|
and if you have a complex page then you need to make sure none of the JavaScript in your
|
||
|
three.js visualization conflict with the JavaScript doing other things in the page.</p>
|
||
|
<ul>
|
||
|
<li>Use an <code class="notranslate" translate="no">iframe</code></li>
|
||
|
</ul>
|
||
|
<p>This is the solution used on <a href="/">the front page of this site</a>.</p>
|
||
|
<p>In your webpage just insert an iframe, for example</p>
|
||
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><iframe id="background" src="responsive.html">
|
||
|
<div>
|
||
|
Your content goes here.
|
||
|
</div>
|
||
|
</pre>
|
||
|
<p>Then style the iframe to fill the window and be in the background
|
||
|
which is basically the same code as we used above for the canvas
|
||
|
except we also need to set <code class="notranslate" translate="no">border</code> to <code class="notranslate" translate="no">none</code> since iframes have
|
||
|
a border by default.</p>
|
||
|
<pre class="prettyprint showlinemods notranslate notranslate" translate="no">#background {
|
||
|
position: fixed;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
left: 0;
|
||
|
top: 0;
|
||
|
z-index: -1;
|
||
|
border: none;
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
</pre><p></p><div translate="no" class="threejs_example_container notranslate">
|
||
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-html-background.html"></iframe></div>
|
||
|
<a class="threejs_center" href="/manual/examples/tips-html-background.html" target="_blank">click here to open in a separate window</a>
|
||
|
</div>
|
||
|
|
||
|
<p></p>
|
||
|
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<script src="/manual/resources/prettify.js"></script>
|
||
|
<script src="/manual/resources/lesson.js"></script>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</body></html>
|