123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- <!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="../resources/lesson.css">
- <link rel="stylesheet" href="../resources/lang.css">
- <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({antialias: true, 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({antialias: true, 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="../resources/prettify.js"></script>
- <script src="../resources/lesson.js"></script>
- </body></html>
|