tips.html 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. Title: Three.js Tips
  2. Description: Small issues that might trip you up using three.js
  3. TOC: #
  4. This article is a collection of small issues you might run into
  5. using three.js that seemed too small to have their own article.
  6. ---
  7. <a id="screenshot" data-toc="Taking a screenshot"></a>
  8. # Taking A Screenshot of the Canvas
  9. In the browser there are effectively 2 functions that will take a screenshot.
  10. The old one
  11. [`canvas.toDataURL`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
  12. and the new better one
  13. [`canvas.toBlob`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
  14. So you'd think it would be easy to take a screenshot by just adding some code like
  15. ```html
  16. <canvas id="c"></canvas>
  17. +<button id="screenshot" type="button">Save...</button>
  18. ```
  19. ```js
  20. const elem = document.querySelector('#screenshot');
  21. elem.addEventListener('click', () => {
  22. canvas.toBlob((blob) => {
  23. saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
  24. });
  25. });
  26. const saveBlob = (function() {
  27. const a = document.createElement('a');
  28. document.body.appendChild(a);
  29. a.style.display = 'none';
  30. return function saveData(blob, fileName) {
  31. const url = window.URL.createObjectURL(blob);
  32. a.href = url;
  33. a.download = fileName;
  34. a.click();
  35. };
  36. }());
  37. ```
  38. Here's the example from [the article on responsiveness](threejs-responsive.html)
  39. with the code above added and some CSS to place the button
  40. {{{example url="../threejs-tips-screenshot-bad.html"}}}
  41. When I tried it I got this screenshot
  42. <div class="threejs_center"><img src="resources/images/screencapture-413x313.png"></div>
  43. Yes, it's just a black image.
  44. It's possible it worked for you depending on your browser/OS but in general
  45. it's not likely to work.
  46. The issue is that for performance and compatibility reasons, by default the browser
  47. will clear a WebGL canvas's drawing buffer after you've drawn to it.
  48. The solution is to call your rendering code just before capturing.
  49. In our code we need to adjust a few things. First let's separate
  50. out the rendering code.
  51. ```js
  52. +const state = {
  53. + time: 0,
  54. +};
  55. -function render(time) {
  56. - time *= 0.001;
  57. +function render() {
  58. if (resizeRendererToDisplaySize(renderer)) {
  59. const canvas = renderer.domElement;
  60. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  61. camera.updateProjectionMatrix();
  62. }
  63. cubes.forEach((cube, ndx) => {
  64. const speed = 1 + ndx * .1;
  65. - const rot = time * speed;
  66. + const rot = state.time * speed;
  67. cube.rotation.x = rot;
  68. cube.rotation.y = rot;
  69. });
  70. renderer.render(scene, camera);
  71. - requestAnimationFrame(render);
  72. }
  73. +function animate(time) {
  74. + state.time = time * 0.001;
  75. +
  76. + render();
  77. +
  78. + requestAnimationFrame(animate);
  79. +}
  80. +requestAnimationFrame(animate);
  81. ```
  82. Now that `render` is only concerned with actually rendering
  83. we can call it just before capturing the canvas.
  84. ```js
  85. const elem = document.querySelector('#screenshot');
  86. elem.addEventListener('click', () => {
  87. + render();
  88. canvas.toBlob((blob) => {
  89. saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
  90. });
  91. });
  92. ```
  93. And now it should work.
  94. {{{example url="../threejs-tips-screenshot-good.html" }}}
  95. For a different solution see the next item.
  96. ---
  97. <a id="preservedrawingbuffer" data-toc="Prevent the Canvas Being Cleared"></a>
  98. # Preventing the canvas being cleared
  99. Let's say you wanted to let the user paint with an animated
  100. object. You need to pass in `preserveDrawingBuffer: true` when
  101. you create the `WebGLRenderer`. This prevents the browser from
  102. clearing the canvas. You also need to tell three.js not to clear
  103. the canvas as well.
  104. ```js
  105. const canvas = document.querySelector('#c');
  106. -const renderer = new THREE.WebGLRenderer({canvas});
  107. +const renderer = new THREE.WebGLRenderer({
  108. + canvas,
  109. + preserveDrawingBuffer: true,
  110. + alpha: true,
  111. +});
  112. +renderer.autoClearColor = false;
  113. ```
  114. {{{example url="../threejs-tips-preservedrawingbuffer.html" }}}
  115. Note that if you were serious about making a drawing program this would not be a
  116. solution as the browser will still clear the canvas anytime we change its
  117. resolution. We're changing is resolution based on its display size. Its display
  118. size changes when the window changes size. That includes when the user downloads
  119. a file, even in another tab, and the browser adds a status bar. It also includes when
  120. the user turns their phone and the browser switches from portrait to landscape.
  121. If you really wanted to make a drawing program you'd
  122. [render to a texture using a render target](threejs-rendertargets.html).
  123. ---
  124. <a id="tabindex" data-toc="Get Keyboard Input From a Canvas"></a>
  125. # Getting Keyboard Input
  126. Throughout these tutorials we've often attached event listeners to the `canvas`.
  127. While many events work, one that does not work by default is keyboard
  128. events.
  129. To get keyboard events, set the [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex)
  130. of the canvas to 0 or more. Eg.
  131. ```html
  132. <canvas tabindex="0"></canvas>
  133. ```
  134. This ends up causing a new issue though. Anything that has a `tabindex` set
  135. will get highlighted when it has the focus. To fix that set its focus CSS outline
  136. to none
  137. ```css
  138. canvas:focus {
  139. outline:none;
  140. }
  141. ```
  142. To demonstrate here are 3 canvases
  143. ```html
  144. <canvas id="c1"></canvas>
  145. <canvas id="c2" tabindex="0"></canvas>
  146. <canvas id="c3" tabindex="1"></canvas>
  147. ```
  148. and some css just for the last canvas
  149. ```css
  150. #c3:focus {
  151. outline: none;
  152. }
  153. ```
  154. Let's attach the same event listeners to all of them
  155. ```js
  156. document.querySelectorAll('canvas').forEach((canvas) => {
  157. const ctx = canvas.getContext('2d');
  158. function draw(str) {
  159. ctx.clearRect(0, 0, canvas.width, canvas.height);
  160. ctx.textAlign = 'center';
  161. ctx.textBaseline = 'middle';
  162. ctx.fillText(str, canvas.width / 2, canvas.height / 2);
  163. }
  164. draw(canvas.id);
  165. canvas.addEventListener('focus', () => {
  166. draw('has focus press a key');
  167. });
  168. canvas.addEventListener('blur', () => {
  169. draw('lost focus');
  170. });
  171. canvas.addEventListener('keydown', (e) => {
  172. draw(`keyCode: ${e.keyCode}`);
  173. });
  174. });
  175. ```
  176. Notice you can't get the first canvas to accept keyboard input.
  177. The second canvas you can but it gets highlighted. The 3rd
  178. canvas has both solutions applied.
  179. {{{example url="../threejs-tips-tabindex.html"}}}
  180. ---
  181. <a id="transparent-canvas" data-toc="Make the Canvas Transparent"></a>
  182. # Making the Canvas Transparent
  183. By default THREE.js makes the canvas opaque. If you want the
  184. canvas to be transparent pass in [`alpha:true`](WebGLRenderer.alpha) when you create
  185. the `WebGLRenderer`
  186. ```js
  187. const canvas = document.querySelector('#c');
  188. -const renderer = new THREE.WebGLRenderer({canvas});
  189. +const renderer = new THREE.WebGLRenderer({
  190. + canvas,
  191. + alpha: true,
  192. +});
  193. ```
  194. You probably also want to tell it that your results are **not** using premultiplied alpha
  195. ```js
  196. const canvas = document.querySelector('#c');
  197. const renderer = new THREE.WebGLRenderer({
  198. canvas,
  199. alpha: true,
  200. + premultipliedAlpha: false,
  201. });
  202. ```
  203. Three.js defaults to the canvas using
  204. [`premultipliedAlpha: true`](WebGLRenderer.premultipliedAlpha) but defaults
  205. to materials outputting [`premultipliedAlpha: false`](Material.premultipliedAlpha).
  206. If you'd like a better understanding of when and when not to use premultiplied alpha
  207. here's [a good article on it](https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre).
  208. In any case let's setup a simple example with a transparent canvas.
  209. We applied the settings above to the example from [the article on responsiveness](threejs-responsive.html).
  210. Let's also make the materials more transparent.
  211. ```js
  212. function makeInstance(geometry, color, x) {
  213. - const material = new THREE.MeshPhongMaterial({color});
  214. + const material = new THREE.MeshPhongMaterial({
  215. + color,
  216. + opacity: 0.5,
  217. + });
  218. ...
  219. ```
  220. And let's add some HTML content
  221. ```html
  222. <body>
  223. <canvas id="c"></canvas>
  224. + <div id="content">
  225. + <div>
  226. + <h1>Cubes-R-Us!</h1>
  227. + <p>We make the best cubes!</p>
  228. + </div>
  229. + </div>
  230. </body>
  231. ```
  232. as well as some CSS to put the canvas in front
  233. ```css
  234. body {
  235. margin: 0;
  236. }
  237. #c {
  238. width: 100%;
  239. height: 100%;
  240. display: block;
  241. + position: fixed;
  242. + left: 0;
  243. + top: 0;
  244. + z-index: 2;
  245. + pointer-events: none;
  246. }
  247. +#content {
  248. + font-size: 7vw;
  249. + font-family: sans-serif;
  250. + text-align: center;
  251. + width: 100%;
  252. + height: 100%;
  253. + display: flex;
  254. + justify-content: center;
  255. + align-items: center;
  256. +}
  257. ```
  258. note that `pointer-events: none` makes the canvas invisible to the mouse
  259. and touch events so you can select the text beneath.
  260. {{{example url="../threejs-tips-transparent-canvas.html" }}}
  261. ---
  262. <a id="html-background" data-toc="Use three.js as Background in HTML"></a>
  263. # Making your background a three.js animation
  264. A common question is how to make a three.js animation be the background of
  265. a webpage.
  266. There are 2 obvious ways.
  267. * Set the canvas CSS `position` to `fixed` as in
  268. ```css
  269. #c {
  270. position: fixed;
  271. left: 0;
  272. top: 0;
  273. ...
  274. }
  275. ```
  276. You can basically see this exact solution on the previous example. Just set `z-index` to -1
  277. and the cubes will appear behind the text.
  278. A small disadvantage to this solution is your JavaScript must integrate with the page
  279. and if you have a complex page then you need to make sure none of the JavaScript in your
  280. three.js visualization conflict with the JavaScript doing other things in the page.
  281. * Use an `iframe`
  282. This is the solution used on [the front page of this site](/).
  283. In your webpage just insert an iframe, for example
  284. ```html
  285. <iframe id="background" src="threejs-responsive.html">
  286. <div>
  287. Your content goes here.
  288. </div>
  289. ```
  290. Then style the iframe to fill the window and be in the background
  291. which is basically the same code as we used above for the canvas
  292. except we also need to set `border` to `none` since iframes have
  293. a border by default.
  294. ```
  295. #background {
  296. position: fixed;
  297. width: 100%;
  298. height: 100%;
  299. left: 0;
  300. top: 0;
  301. z-index: -1;
  302. border: none;
  303. pointer-events: none;
  304. }
  305. ```
  306. {{{example url="../threejs-tips-html-background.html"}}}