offscreencanvas.html 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. <!DOCTYPE html><html lang="fr"><head>
  2. <meta charset="utf-8">
  3. <title>OffscreenCanvas</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – OffscreenCanvas">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>OffscreenCanvas</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p><a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas"><code class="notranslate" translate="no">OffscreenCanvas</code></a>
  29. est une fonctionnalité de navigateur relativement nouvelle, actuellement disponible uniquement dans Chrome mais apparemment
  30. à venir sur d'autres navigateurs. <code class="notranslate" translate="no">OffscreenCanvas</code> permet à un web worker de rendre
  31. sur un canevas. C'est une façon de décharger le travail lourd, comme le rendu d'une scène 3D complexe,
  32. sur un web worker afin de ne pas ralentir la réactivité du navigateur. Cela
  33. signifie également que les données sont chargées et analysées dans le worker, ce qui réduit potentiellement les saccades pendant
  34. le chargement de la page.</p>
  35. <p>Commencer à l'utiliser est assez simple. Portons l'exemple des 3 cubes en rotation depuis <a href="responsive.html">l'article sur la réactivité</a>.</p>
  36. <p>En général, les workers ont leur code séparé
  37. dans un autre fichier script, tandis que la plupart des exemples sur ce site ont leurs
  38. scripts intégrés dans le fichier HTML de la page sur laquelle ils se trouvent.</p>
  39. <p>Dans notre cas, nous allons créer un fichier appelé <code class="notranslate" translate="no">offscreencanvas-cubes.js</code> et
  40. y copier tout le JavaScript depuis <a href="responsive.html">l'exemple réactif</a>. Nous apporterons ensuite
  41. les modifications nécessaires pour qu'il s'exécute dans un worker.</p>
  42. <p>Nous avons encore besoin de JavaScript dans notre fichier HTML. La première chose
  43. à faire est de trouver le canevas, puis de transférer son contrôle
  44. pour qu'il soit offscreen en appelant <code class="notranslate" translate="no">canvas.transferControlToOffscreen</code>.</p>
  45. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  46. const canvas = document.querySelector('#c');
  47. const offscreen = canvas.transferControlToOffscreen();
  48. ...
  49. </pre>
  50. <p>Nous pouvons ensuite démarrer notre worker avec <code class="notranslate" translate="no">new Worker(pathToScript, {type: 'module'})</code>.
  51. et lui passer l'objet <code class="notranslate" translate="no">offscreen</code>.</p>
  52. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  53. const canvas = document.querySelector('#c');
  54. const offscreen = canvas.transferControlToOffscreen();
  55. const worker = new Worker('offscreencanvas-cubes.js', {type: 'module'});
  56. worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  57. }
  58. main();
  59. </pre>
  60. <p>Il est important de noter que les workers ne peuvent pas accéder au <code class="notranslate" translate="no">DOM</code>. Ils
  61. ne peuvent pas regarder les éléments HTML ni recevoir les événements de souris ou
  62. de clavier. La seule chose qu'ils peuvent généralement faire est de répondre
  63. aux messages qui leur sont envoyés et de renvoyer des messages à la page.</p>
  64. <p>Pour envoyer un message à un worker, nous appelons <a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage"><code class="notranslate" translate="no">worker.postMessage</code></a> et
  65. lui passons 1 ou 2 arguments. Le premier argument est un objet JavaScript
  66. qui sera <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm">cloné</a>
  67. et envoyé au worker. Le second argument est un tableau optionnel
  68. d'objets qui font partie du premier objet et que nous voulons <em>transférer</em>
  69. au worker. Ces objets ne seront pas clonés. Au lieu de cela, ils seront <em>transférés</em>
  70. et cesseront d'exister dans la page principale. Cesser d'exister est probablement
  71. la mauvaise description, ils sont plutôt neutralisés. Seuls certains types d'objets
  72. peuvent être transférés au lieu d'être clonés. Ils incluent <code class="notranslate" translate="no">OffscreenCanvas</code>,
  73. donc une fois transféré, l'objet <code class="notranslate" translate="no">offscreen</code> dans la page principale devient inutile.</p>
  74. <p>Les workers reçoivent les messages via leur gestionnaire <code class="notranslate" translate="no">onmessage</code>. L'objet
  75. que nous avons passé à <code class="notranslate" translate="no">postMessage</code> arrive sur <code class="notranslate" translate="no">event.data</code> passé au gestionnaire <code class="notranslate" translate="no">onmessage</code>
  76. sur le worker. Le code ci-dessus déclare un <code class="notranslate" translate="no">type: 'main'</code> dans l'objet qu'il passe
  77. au worker. Cet objet n'a aucune signification pour le navigateur. Il est entièrement destiné
  78. à notre propre usage. Nous allons créer un gestionnaire qui, basé sur le <code class="notranslate" translate="no">type</code>, appelle
  79. une fonction différente dans le worker. Ensuite, nous pourrons ajouter des fonctions au besoin et
  80. les appeler facilement depuis la page principale.</p>
  81. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const handlers = {
  82. main,
  83. };
  84. self.onmessage = function(e) {
  85. const fn = handlers[e.data.type];
  86. if (typeof fn !== 'function') {
  87. throw new Error('no handler for type: ' + e.data.type);
  88. }
  89. fn(e.data);
  90. };
  91. </pre>
  92. <p>Vous pouvez voir ci-dessus que nous recherchons simplement le gestionnaire basé sur le <code class="notranslate" translate="no">type</code> et que nous lui passons les <code class="notranslate" translate="no">data</code>
  93. qui ont été envoyées depuis la page principale.</p>
  94. <p>Il ne nous reste plus qu'à commencer à modifier la fonction <code class="notranslate" translate="no">main</code> que nous avons collée dans
  95. <code class="notranslate" translate="no">offscreencanvas-cubes.js</code> depuis <a href="responsive.html">l'article sur la réactivité</a>.</p>
  96. <p>Au lieu de rechercher le canevas depuis le DOM, nous le recevrons des données d'événement.</p>
  97. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function main() {
  98. - const canvas = document.querySelector('#c');
  99. +function main(data) {
  100. + const {canvas} = data;
  101. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  102. ...
  103. </pre>
  104. <p>En gardant à l'esprit que les workers ne peuvent pas voir le DOM du tout, le premier problème
  105. que nous rencontrons est que <code class="notranslate" translate="no">resizeRendererToDisplaySize</code> ne peut pas lire <code class="notranslate" translate="no">canvas.clientWidth</code>
  106. et <code class="notranslate" translate="no">canvas.clientHeight</code> car ce sont des valeurs DOM. Voici le code original</p>
  107. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function resizeRendererToDisplaySize(renderer) {
  108. const canvas = renderer.domElement;
  109. const width = canvas.clientWidth;
  110. const height = canvas.clientHeight;
  111. const needResize = canvas.width !== width || canvas.height !== height;
  112. if (needResize) {
  113. renderer.setSize(width, height, false);
  114. }
  115. return needResize;
  116. }
  117. </pre>
  118. <p>Au lieu de cela, nous devrons envoyer les tailles au worker dès qu'elles changent.
  119. Ajoutons donc un état global et conservons la largeur et la hauteur à cet endroit.</p>
  120. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const state = {
  121. width: 300, // par défaut du canevas
  122. height: 150, // par défaut du canevas
  123. };
  124. </pre>
  125. <p>Ensuite, ajoutons un gestionnaire <code class="notranslate" translate="no">'size'</code> pour mettre à jour ces valeurs. </p>
  126. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function size(data) {
  127. + state.width = data.width;
  128. + state.height = data.height;
  129. +}
  130. const handlers = {
  131. main,
  132. + size,
  133. };
  134. </pre>
  135. <p>Maintenant, nous pouvons modifier <code class="notranslate" translate="no">resizeRendererToDisplaySize</code> pour utiliser <code class="notranslate" translate="no">state.width</code> et <code class="notranslate" translate="no">state.height</code></p>
  136. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function resizeRendererToDisplaySize(renderer) {
  137. const canvas = renderer.domElement;
  138. - const width = canvas.clientWidth;
  139. - const height = canvas.clientHeight;
  140. + const width = state.width;
  141. + const height = state.height;
  142. const needResize = canvas.width !== width || canvas.height !== height;
  143. if (needResize) {
  144. renderer.setSize(width, height, false);
  145. }
  146. return needResize;
  147. }
  148. </pre>
  149. <p>et là où nous calculons l'aspect, nous avons besoin de changements similaires</p>
  150. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
  151. time *= 0.001;
  152. if (resizeRendererToDisplaySize(renderer)) {
  153. - camera.aspect = canvas.clientWidth / canvas.clientHeight;
  154. + camera.aspect = state.width / state.height;
  155. camera.updateProjectionMatrix();
  156. }
  157. ...
  158. </pre>
  159. <p>De retour dans la page principale, nous enverrons un événement <code class="notranslate" translate="no">size</code> chaque fois que la page change de taille.</p>
  160. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const worker = new Worker('offscreencanvas-picking.js', {type: 'module'});
  161. worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  162. +function sendSize() {
  163. + worker.postMessage({
  164. + type: 'size',
  165. + width: canvas.clientWidth,
  166. + height: canvas.clientHeight,
  167. + });
  168. +}
  169. +
  170. +window.addEventListener('resize', sendSize);
  171. +sendSize();
  172. </pre>
  173. <p>Nous l'appelons également une fois pour envoyer la taille initiale.</p>
  174. <p>Et avec ces quelques modifications seulement, en supposant que votre navigateur prenne entièrement en charge <code class="notranslate" translate="no">OffscreenCanvas</code>,
  175. cela devrait fonctionner. Avant de l'exécuter, vérifions si le navigateur prend réellement en charge
  176. <code class="notranslate" translate="no">OffscreenCanvas</code> et, si ce n'est pas le cas, affichons une erreur. Ajoutons d'abord du HTML pour afficher l'erreur.</p>
  177. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  178. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  179. + &lt;div id="noOffscreenCanvas" style="display:none;"&gt;
  180. + &lt;div&gt;no OffscreenCanvas support&lt;/div&gt;
  181. + &lt;/div&gt;
  182. &lt;/body&gt;
  183. </pre>
  184. <p>et un peu de CSS pour cela</p>
  185. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#noOffscreenCanvas {
  186. display: flex;
  187. width: 100%;
  188. height: 100%;
  189. align-items: center;
  190. justify-content: center;
  191. background: red;
  192. color: white;
  193. }
  194. </pre>
  195. <p>et ensuite nous pouvons vérifier l'existence de <code class="notranslate" translate="no">transferControlToOffscreen</code> pour voir
  196. si le navigateur prend en charge <code class="notranslate" translate="no">OffscreenCanvas</code></p>
  197. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  198. const canvas = document.querySelector('#c');
  199. + if (!canvas.transferControlToOffscreen) {
  200. + canvas.style.display = 'none';
  201. + document.querySelector('#noOffscreenCanvas').style.display = '';
  202. + return;
  203. + }
  204. const offscreen = canvas.transferControlToOffscreen();
  205. const worker = new Worker('offscreencanvas-picking.js', {type: 'module});
  206. worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  207. ...
  208. </pre>
  209. <p>et avec cela, si votre navigateur prend en charge <code class="notranslate" translate="no">OffscreenCanvas</code>, cet exemple devrait fonctionner</p>
  210. <p></p><div translate="no" class="threejs_example_container notranslate">
  211. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/offscreencanvas.html"></iframe></div>
  212. <a class="threejs_center" href="/manual/examples/offscreencanvas.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
  213. </div>
  214. <p></p>
  215. <p>C'est formidable, mais comme tous les navigateurs ne prennent pas en charge <code class="notranslate" translate="no">OffscreenCanvas</code> pour le moment,
  216. modifions le code pour qu'il fonctionne à la fois avec <code class="notranslate" translate="no">OffscreenCanvas</code> et, si ce n'est pas le cas, pour qu'il revienne à l'utilisation
  217. du canevas dans la page principale comme d'habitude.</p>
  218. <blockquote>
  219. <p>En aparté, si vous avez besoin de OffscreenCanvas pour rendre votre page réactive, alors
  220. l'intérêt d'avoir un fallback n'est pas évident. Peut-être que selon si
  221. vous exécutez sur la page principale ou dans un worker, vous pourriez ajuster la quantité
  222. de travail effectué afin que lorsque vous exécutez dans un worker, vous puissiez faire plus que lorsque
  223. vous exécutez dans la page principale. Ce que vous faites dépend entièrement de vous.</p>
  224. </blockquote>
  225. <p>La première chose que nous devrions probablement faire est de séparer le code three.js
  226. du code spécifique au worker. De cette façon, nous pouvons
  227. utiliser le même code sur la page principale et sur le worker. En d'autres termes,
  228. nous aurons maintenant 3 fichiers</p>
  229. <ol>
  230. <li><p>notre fichier html.</p>
  231. <p><code class="notranslate" translate="no">threejs-offscreencanvas-w-fallback.html</code></p>
  232. </li>
  233. <li><p>un fichier JavaScript qui contient notre code three.js.</p>
  234. <p><code class="notranslate" translate="no">shared-cubes.js</code></p>
  235. </li>
  236. <li><p>notre code de support pour le worker</p>
  237. <p><code class="notranslate" translate="no">offscreencanvas-worker-cubes.js</code></p>
  238. </li>
  239. </ol>
  240. <p><code class="notranslate" translate="no">shared-cubes.js</code> et <code class="notranslate" translate="no">offscreencanvas-worker-cubes.js</code> sont essentiellement
  241. la séparation de notre fichier <code class="notranslate" translate="no">offscreencanvas-cubes.js</code> précédent. Nous
  242. copions d'abord tout le contenu de <code class="notranslate" translate="no">offscreencanvas-cubes.js</code> dans <code class="notranslate" translate="no">shared-cube.js</code>. Ensuite,
  243. nous renommons <code class="notranslate" translate="no">main</code> en <code class="notranslate" translate="no">init</code> car nous avons déjà une fonction <code class="notranslate" translate="no">main</code> dans notre
  244. fichier HTML, et nous devons exporter <code class="notranslate" translate="no">init</code> et <code class="notranslate" translate="no">state</code></p>
  245. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
  246. -const state = {
  247. +export const state = {
  248. width: 300, // par défaut du canevas
  249. height: 150, // par défaut du canevas
  250. };
  251. -function main(data) {
  252. +export function init(data) {
  253. const {canvas} = data;
  254. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  255. </pre>
  256. <p>et découpons juste les parties non liées à three.js</p>
  257. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function size(data) {
  258. - state.width = data.width;
  259. - state.height = data.height;
  260. -}
  261. -
  262. -const handlers = {
  263. - main,
  264. - size,
  265. -};
  266. -
  267. -self.onmessage = function(e) {
  268. - const fn = handlers[e.data.type];
  269. - if (typeof fn !== 'function') {
  270. - throw new Error('no handler for type: ' + e.data.type);
  271. - }
  272. - fn(e.data);
  273. -};
  274. </pre>
  275. <p>Ensuite, nous copions les parties que nous venons de supprimer dans <code class="notranslate" translate="no">offscreencanvas-worker-cubes.js</code>
  276. et importons <code class="notranslate" translate="no">shared-cubes.js</code> ainsi qu'appelons <code class="notranslate" translate="no">init</code> au lieu de <code class="notranslate" translate="no">main</code>.</p>
  277. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {init, state} from './shared-cubes.js';
  278. function size(data) {
  279. state.width = data.width;
  280. state.height = data.height;
  281. }
  282. const handlers = {
  283. - main,
  284. + init,
  285. size,
  286. };
  287. self.onmessage = function(e) {
  288. const fn = handlers[e.data.type];
  289. if (typeof fn !== 'function') {
  290. throw new Error('no handler for type: ' + e.data.type);
  291. }
  292. fn(e.data);
  293. };
  294. </pre>
  295. <p>De même, nous devons inclure <code class="notranslate" translate="no">shared-cubes.js</code> dans la page principale</p>
  296. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;script type="module"&gt;
  297. +import {init, state} from './shared-cubes.js';
  298. </pre>
  299. <p>Nous pouvons supprimer le HTML et le CSS que nous avons ajoutés précédemment</p>
  300. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  301. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  302. - &lt;div id="noOffscreenCanvas" style="display:none;"&gt;
  303. - &lt;div&gt;no OffscreenCanvas support&lt;/div&gt;
  304. - &lt;/div&gt;
  305. &lt;/body&gt;
  306. </pre>
  307. <p>et un peu de CSS pour cela</p>
  308. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">-#noOffscreenCanvas {
  309. - display: flex;
  310. - width: 100%;
  311. - height: 100%;
  312. - align-items: center;
  313. - justify-content: center;
  314. - background: red;
  315. - color: white;
  316. -}
  317. </pre>
  318. <p>Ensuite, modifions le code dans la page principale pour appeler une fonction de démarrage ou une autre
  319. selon que le navigateur prend en charge <code class="notranslate" translate="no">OffscreenCanvas</code>.</p>
  320. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  321. const canvas = document.querySelector('#c');
  322. - if (!canvas.transferControlToOffscreen) {
  323. - canvas.style.display = 'none';
  324. - document.querySelector('#noOffscreenCanvas').style.display = '';
  325. - return;
  326. - }
  327. - const offscreen = canvas.transferControlToOffscreen();
  328. - const worker = new Worker('offscreencanvas-picking.js', {type: 'module'});
  329. - worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  330. + if (canvas.transferControlToOffscreen) {
  331. + startWorker(canvas);
  332. + } else {
  333. + startMainPage(canvas);
  334. + }
  335. ...
  336. </pre>
  337. <p>Nous allons déplacer tout le code que nous avions pour configurer le worker à l'intérieur de <code class="notranslate" translate="no">startWorker</code></p>
  338. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function startWorker(canvas) {
  339. const offscreen = canvas.transferControlToOffscreen();
  340. const worker = new Worker('offscreencanvas-worker-cubes.js', {type: 'module'});
  341. worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  342. function sendSize() {
  343. worker.postMessage({
  344. type: 'size',
  345. width: canvas.clientWidth,
  346. height: canvas.clientHeight,
  347. });
  348. }
  349. window.addEventListener('resize', sendSize);
  350. sendSize();
  351. console.log('using OffscreenCanvas');
  352. }
  353. </pre>
  354. <p>et envoyer <code class="notranslate" translate="no">init</code> au lieu de <code class="notranslate" translate="no">main</code></p>
  355. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">- worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
  356. + worker.postMessage({type: 'init', canvas: offscreen}, [offscreen]);
  357. </pre>
  358. <p>pour démarrer dans la page principale, nous pouvons faire ceci</p>
  359. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function startMainPage(canvas) {
  360. init({canvas});
  361. function sendSize() {
  362. state.width = canvas.clientWidth;
  363. state.height = canvas.clientHeight;
  364. }
  365. window.addEventListener('resize', sendSize);
  366. sendSize();
  367. console.log('using regular canvas');
  368. }
  369. </pre>
  370. <p>et avec cela, notre exemple s'exécutera soit dans un OffscreenCanvas, soit il
  371. reviendra à s'exécuter dans la page principale.</p>
  372. <p></p><div translate="no" class="threejs_example_container notranslate">
  373. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/offscreencanvas-w-fallback.html"></iframe></div>
  374. <a class="threejs_center" href="/manual/examples/offscreencanvas-w-fallback.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
  375. </div>
  376. <p></p>
  377. <p>C'était donc relativement facile. Essayons le picking. Nous allons prendre du code de
  378. l'exemple <code class="notranslate" translate="no">RayCaster</code> depuis <a href="picking.html">l'article sur le picking</a>
  379. et le faire fonctionner offscreen.</p>
  380. <p>Copions le fichier <code class="notranslate" translate="no">shared-cube.js</code> vers <code class="notranslate" translate="no">shared-picking.js</code> et ajoutons les parties de picking. Nous copions le <code class="notranslate" translate="no">PickHelper</code> </p>
  381. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class PickHelper {
  382. constructor() {
  383. this.raycaster = new THREE.Raycaster();
  384. this.pickedObject = null;
  385. this.pickedObjectSavedColor = 0;
  386. }
  387. pick(normalizedPosition, scene, camera, time) {
  388. // restore the color if there is a picked object
  389. if (this.pickedObject) {
  390. this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
  391. this.pickedObject = undefined;
  392. }
  393. // cast a ray through the frustum
  394. this.raycaster.setFromCamera(normalizedPosition, camera);
  395. // get the list of objects the ray intersected
  396. const intersectedObjects = this.raycaster.intersectObjects(scene.children);
  397. if (intersectedObjects.length) {
  398. // pick the first object. It's the closest one
  399. this.pickedObject = intersectedObjects[0].object;
  400. // save its color
  401. this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
  402. // set its emissive color to flashing red/yellow
  403. this.pickedObject.material.emissive.setHex((time * 8) % 2 &gt; 1 ? 0xFFFF00 : 0xFF0000);
  404. }
  405. }
  406. }
  407. const pickPosition = {x: 0, y: 0};
  408. const pickHelper = new PickHelper();
  409. </pre>
  410. <p>Nous avons mis à jour <code class="notranslate" translate="no">pickPosition</code> à partir de la souris comme ceci</p>
  411. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function getCanvasRelativePosition(event) {
  412. const rect = canvas.getBoundingClientRect();
  413. return {
  414. x: (event.clientX - rect.left) * canvas.width / rect.width,
  415. y: (event.clientY - rect.top ) * canvas.height / rect.height,
  416. };
  417. }
  418. function setPickPosition(event) {
  419. const pos = getCanvasRelativePosition(event);
  420. pickPosition.x = (pos.x / canvas.width ) * 2 - 1;
  421. pickPosition.y = (pos.y / canvas.height) * -2 + 1; // notez que nous inversons Y
  422. }
  423. window.addEventListener('mousemove', setPickPosition);
  424. </pre>
  425. <p>Un worker ne peut pas lire la position de la souris directement, donc tout comme le code de taille,
  426. envoyons un message avec la position de la souris. Comme pour le code de taille, nous enverrons
  427. la position de la souris et mettrons à jour <code class="notranslate" translate="no">pickPosition</code></p>
  428. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function size(data) {
  429. state.width = data.width;
  430. state.height = data.height;
  431. }
  432. +function mouse(data) {
  433. + pickPosition.x = data.x;
  434. + pickPosition.y = data.y;
  435. +}
  436. const handlers = {
  437. init,
  438. + mouse,
  439. size,
  440. };
  441. self.onmessage = function(e) {
  442. const fn = handlers[e.data.type];
  443. if (typeof fn !== 'function') {
  444. throw new Error('no handler for type: ' + e.data.type);
  445. }
  446. fn(e.data);
  447. };
  448. </pre>
  449. <p>De retour dans notre page principale, nous devons ajouter du code pour passer la souris
  450. au worker ou à la page principale.</p>
  451. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let sendMouse;
  452. function startWorker(canvas) {
  453. const offscreen = canvas.transferControlToOffscreen();
  454. const worker = new Worker('offscreencanvas-worker-picking.js', {type: 'module'});
  455. worker.postMessage({type: 'init', canvas: offscreen}, [offscreen]);
  456. + sendMouse = (x, y) =&gt; {
  457. + worker.postMessage({
  458. + type: 'mouse',
  459. + x,
  460. + y,
  461. + });
  462. + };
  463. function sendSize() {
  464. worker.postMessage({
  465. type: 'size',
  466. width: canvas.clientWidth,
  467. height: canvas.clientHeight,
  468. });
  469. }
  470. window.addEventListener('resize', sendSize);
  471. sendSize();
  472. console.log('using OffscreenCanvas'); /* eslint-disable-line no-console */
  473. }
  474. function startMainPage(canvas) {
  475. init({canvas});
  476. + sendMouse = (x, y) =&gt; {
  477. + pickPosition.x = x;
  478. + pickPosition.y = y;
  479. + };
  480. function sendSize() {
  481. state.width = canvas.clientWidth;
  482. state.height = canvas.clientHeight;
  483. }
  484. window.addEventListener('resize', sendSize);
  485. sendSize();
  486. console.log('using regular canvas'); /* eslint-disable-line no-console */
  487. }
  488. </pre>
  489. <p>Ensuite, nous pouvons copier tout le code de gestion de la souris dans la page principale et
  490. apporter juste des modifications mineures pour utiliser <code class="notranslate" translate="no">sendMouse</code></p>
  491. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function setPickPosition(event) {
  492. const pos = getCanvasRelativePosition(event);
  493. - pickPosition.x = (pos.x / canvas.clientWidth ) * 2 - 1;
  494. - pickPosition.y = (pos.y / canvas.clientHeight) * -2 + 1; // note we flip Y
  495. + sendMouse(
  496. + (pos.x / canvas.clientWidth ) * 2 - 1,
  497. + (pos.y / canvas.clientHeight) * -2 + 1); // notez que nous inversons Y
  498. }
  499. function clearPickPosition() {
  500. // Contrairement à la souris qui a toujours une position
  501. // si l'utilisateur arrête de toucher l'écran, nous voulons
  502. // arrêter le picking. Pour l'instant, nous choisissons juste une valeur
  503. // peu susceptible de sélectionner quelque chose
  504. - pickPosition.x = -100000;
  505. - pickPosition.y = -100000;
  506. + sendMouse(-100000, -100000);
  507. }
  508. window.addEventListener('mousemove', setPickPosition);
  509. window.addEventListener('mouseout', clearPickPosition);
  510. window.addEventListener('mouseleave', clearPickPosition);
  511. window.addEventListener('touchstart', (event) =&gt; {
  512. // prevent the window from scrolling
  513. event.preventDefault();
  514. setPickPosition(event.touches[0]);
  515. }, {passive: false});
  516. window.addEventListener('touchmove', (event) =&gt; {
  517. setPickPosition(event.touches[0]);
  518. });
  519. window.addEventListener('touchend', clearPickPosition);
  520. </pre>
  521. <p>et avec cela, le picking devrait fonctionner avec <code class="notranslate" translate="no">OffscreenCanvas</code>.</p>
  522. <p></p><div translate="no" class="threejs_example_container notranslate">
  523. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/offscreencanvas-w-picking.html"></iframe></div>
  524. <a class="threejs_center" href="/manual/examples/offscreencanvas-w-picking.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
  525. </div>
  526. <p></p>
  527. <p>Allons un peu plus loin et ajoutons les <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.
  528. Cela sera un peu plus complexe. Les <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> utilisent
  529. le DOM de manière assez extensive pour vérifier la souris, les événements tactiles,
  530. et le clavier.</p>
  531. <p>Contrairement à notre code jusqu'à présent, nous ne pouvons pas vraiment utiliser un objet <code class="notranslate" translate="no">state</code> global
  532. sans réécrire tout le code des OrbitControls pour qu'il fonctionne avec.
  533. Les OrbitControls prennent un <code class="notranslate" translate="no">HTMLElement</code> auquel ils attachent la plupart
  534. des événements DOM qu'ils utilisent. Peut-être pourrions-nous passer notre propre
  535. objet qui a la même surface d'API qu'un élément DOM.
  536. Nous n'avons besoin de prendre en charge que les fonctionnalités dont les OrbitControls ont besoin.</p>
  537. <p>En fouillant dans le <a href="https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js">code source des OrbitControls</a>,
  538. il semble que nous devions gérer les événements suivants.</p>
  539. <ul>
  540. <li>contextmenu</li>
  541. <li>pointerdown</li>
  542. <li>pointermove</li>
  543. <li>pointerup</li>
  544. <li>touchstart</li>
  545. <li>touchmove</li>
  546. <li>touchend</li>
  547. <li>wheel</li>
  548. <li>keydown</li>
  549. </ul>
  550. <p>Pour les événements de pointeur, nous avons besoin des propriétés <code class="notranslate" translate="no">ctrlKey</code>, <code class="notranslate" translate="no">metaKey</code>, <code class="notranslate" translate="no">shiftKey</code>,
  551. <code class="notranslate" translate="no">button</code>, <code class="notranslate" translate="no">pointerType</code>, <code class="notranslate" translate="no">clientX</code>, <code class="notranslate" translate="no">clientY</code>, <code class="notranslate" translate="no">pageX</code> et <code class="notranslate" translate="no">pageY</code>.</p>
  552. <p>Pour les événements keydown, nous avons besoin des propriétés <code class="notranslate" translate="no">ctrlKey</code>, <code class="notranslate" translate="no">metaKey</code>, <code class="notranslate" translate="no">shiftKey</code>
  553. et <code class="notranslate" translate="no">keyCode</code>.</p>
  554. <p>Pour l'événement wheel, nous n'avons besoin que de la propriété <code class="notranslate" translate="no">deltaY</code>.</p>
  555. <p>Et pour les événements tactiles, nous n'avons besoin que de <code class="notranslate" translate="no">pageX</code> et <code class="notranslate" translate="no">pageY</code> de
  556. la propriété <code class="notranslate" translate="no">touches</code>.</p>
  557. <p>Alors, créons une paire d'objets proxy. Une partie s'exécutera dans la page principale,
  558. capturera tous ces événements et transmettra les valeurs de propriété pertinentes
  559. au worker. L'autre partie s'exécutera dans le worker, recevra ces
  560. événements et les transmettra en utilisant des événements qui ont la même structure
  561. que les événements DOM originaux, de sorte que les OrbitControls ne pourront pas
  562. faire la différence.</p>
  563. <p>Voici le code pour la partie worker.</p>
  564. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {EventDispatcher} from 'three';
  565. class ElementProxyReceiver extends EventDispatcher {
  566. constructor() {
  567. super();
  568. }
  569. handleEvent(data) {
  570. this.dispatchEvent(data);
  571. }
  572. }
  573. </pre>
  574. <p>Tout ce qu'il fait est, s'il reçoit un message, de le dispatcher.
  575. Il hérite de <a href="/docs/#api/en/core/EventDispatcher"><code class="notranslate" translate="no">EventDispatcher</code></a> qui fournit des méthodes comme
  576. <code class="notranslate" translate="no">addEventListener</code> et <code class="notranslate" translate="no">removeEventListener</code>, tout comme un élément DOM,
  577. donc si nous le passons aux OrbitControls, cela devrait fonctionner.</p>
  578. <p><code class="notranslate" translate="no">ElementProxyReceiver</code> gère 1 élément. Dans notre cas, nous n'en avons besoin que d'un,
  579. mais il est préférable d'anticiper, alors créons un gestionnaire pour gérer
  580. plus d'un.</p>
  581. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ProxyManager {
  582. constructor() {
  583. this.targets = {};
  584. this.handleEvent = this.handleEvent.bind(this);
  585. }
  586. makeProxy(data) {
  587. const {id} = data;
  588. const proxy = new ElementProxyReceiver();
  589. this.targets[id] = proxy;
  590. }
  591. getProxy(id) {
  592. return this.targets[id];
  593. }
  594. handleEvent(data) {
  595. this.targets[data.id].handleEvent(data.data);
  596. }
  597. }
  598. </pre>
  599. <p>Nous pouvons créer une instance de <code class="notranslate" translate="no">ProxyManager</code> et appeler sa méthode <code class="notranslate" translate="no">makeProxy</code>
  600. avec un identifiant, ce qui créera un <code class="notranslate" translate="no">ElementProxyReceiver</code> qui
  601. répondra aux messages avec cet identifiant.</p>
  602. <p>Connectons-le au gestionnaire de messages de notre worker.</p>
  603. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const proxyManager = new ProxyManager();
  604. function start(data) {
  605. const proxy = proxyManager.getProxy(data.canvasId);
  606. init({
  607. canvas: data.canvas,
  608. inputElement: proxy,
  609. });
  610. }
  611. function makeProxy(data) {
  612. proxyManager.makeProxy(data);
  613. }
  614. ...
  615. const handlers = {
  616. - init,
  617. - mouse,
  618. + start,
  619. + makeProxy,
  620. + event: proxyManager.handleEvent,
  621. size,
  622. };
  623. self.onmessage = function(e) {
  624. const fn = handlers[e.data.type];
  625. if (typeof fn !== 'function') {
  626. throw new Error('no handler for type: ' + e.data.type);
  627. }
  628. fn(e.data);
  629. };
  630. </pre>
  631. <p>Dans notre code three.js partagé, nous devons importer les <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> et les configurer.</p>
  632. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
  633. +import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
  634. export function init(data) {
  635. - const {canvas} = data;
  636. + const {canvas, inputElement} = data;
  637. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  638. + const controls = new OrbitControls(camera, inputElement);
  639. + controls.target.set(0, 0, 0);
  640. + controls.update();
  641. </pre>
  642. <p>Notez que nous passons notre proxy aux OrbitControls via <code class="notranslate" translate="no">inputElement</code>
  643. au lieu de passer le canevas comme nous le faisons dans d'autres exemples sans OffscreenCanvas.</p>
  644. <p>Ensuite, nous pouvons déplacer tout le code des événements de picking du fichier HTML
  645. vers le code three.js partagé également, tout en changeant
  646. <code class="notranslate" translate="no">canvas</code> en <code class="notranslate" translate="no">inputElement</code>.</p>
  647. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function getCanvasRelativePosition(event) {
  648. - const rect = canvas.getBoundingClientRect();
  649. + const rect = inputElement.getBoundingClientRect();
  650. return {
  651. x: event.clientX - rect.left,
  652. y: event.clientY - rect.top,
  653. };
  654. }
  655. function setPickPosition(event) {
  656. const pos = getCanvasRelativePosition(event);
  657. - sendMouse(
  658. - (pos.x / canvas.clientWidth ) * 2 - 1,
  659. - (pos.y / canvas.clientHeight) * -2 + 1); // note we flip Y
  660. + pickPosition.x = (pos.x / inputElement.clientWidth ) * 2 - 1;
  661. + pickPosition.y = (pos.y / inputElement.clientHeight) * -2 + 1; // notez que nous inversons Y
  662. }
  663. function clearPickPosition() {
  664. // Contrairement à la souris qui a toujours une position
  665. // si l'utilisateur arrête de toucher l'écran, nous voulons
  666. // arrêter le picking. Pour l'instant, nous choisissons juste une valeur
  667. // peu susceptible de sélectionner quelque chose
  668. - sendMouse(-100000, -100000);
  669. + pickPosition.x = -100000;
  670. + pickPosition.y = -100000;
  671. }
  672. *inputElement.addEventListener('mousemove', setPickPosition);
  673. *inputElement.addEventListener('mouseout', clearPickPosition);
  674. *inputElement.addEventListener('mouseleave', clearPickPosition);
  675. *inputElement.addEventListener('touchstart', (event) =&gt; {
  676. // prevent the window from scrolling
  677. event.preventDefault();
  678. setPickPosition(event.touches[0]);
  679. }, {passive: false});
  680. *inputElement.addEventListener('touchmove', (event) =&gt; {
  681. setPickPosition(event.touches[0]);
  682. });
  683. *inputElement.addEventListener('touchend', clearPickPosition);
  684. </pre>
  685. <p>De retour dans la page principale, nous avons besoin de code pour envoyer des messages pour
  686. tous les événements que nous avons énumérés ci-dessus.</p>
  687. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">let nextProxyId = 0;
  688. class ElementProxy {
  689. constructor(element, worker, eventHandlers) {
  690. this.id = nextProxyId++;
  691. this.worker = worker;
  692. const sendEvent = (data) =&gt; {
  693. this.worker.postMessage({
  694. type: 'event',
  695. id: this.id,
  696. data,
  697. });
  698. };
  699. // register an id
  700. worker.postMessage({
  701. type: 'makeProxy',
  702. id: this.id,
  703. });
  704. for (const [eventName, handler] of Object.entries(eventHandlers)) {
  705. element.addEventListener(eventName, function(event) {
  706. handler(event, sendEvent);
  707. });
  708. }
  709. }
  710. }
  711. </pre>
  712. <p><code class="notranslate" translate="no">ElementProxy</code> prend l'élément dont nous voulons proxifier les événements. Il
  713. enregistre ensuite un identifiant auprès du worker en en choisissant un et en l'envoyant
  714. via le message <code class="notranslate" translate="no">makeProxy</code> que nous avons configuré précédemment. Le worker créera
  715. un <code class="notranslate" translate="no">ElementProxyReceiver</code> et l'enregistrera avec cet identifiant.</p>
  716. <p>Nous avons ensuite un objet de gestionnaires d'événements à enregistrer. De cette façon,
  717. nous pouvons passer des gestionnaires uniquement pour les événements que nous voulons transmettre au
  718. worker.</p>
  719. <p>Lorsque nous démarrons le worker, nous créons d'abord un proxy et passons nos gestionnaires d'événements.</p>
  720. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function startWorker(canvas) {
  721. const offscreen = canvas.transferControlToOffscreen();
  722. const worker = new Worker('offscreencanvas-worker-orbitcontrols.js', {type: 'module'});
  723. + const eventHandlers = {
  724. + contextmenu: preventDefaultHandler,
  725. + mousedown: mouseEventHandler,
  726. + mousemove: mouseEventHandler,
  727. + mouseup: mouseEventHandler,
  728. + pointerdown: mouseEventHandler,
  729. + pointermove: mouseEventHandler,
  730. + pointerup: mouseEventHandler,
  731. + touchstart: touchEventHandler,
  732. + touchmove: touchEventHandler,
  733. + touchend: touchEventHandler,
  734. + wheel: wheelEventHandler,
  735. + keydown: filteredKeydownEventHandler,
  736. + };
  737. + const proxy = new ElementProxy(canvas, worker, eventHandlers);
  738. worker.postMessage({
  739. type: 'start',
  740. canvas: offscreen,
  741. + canvasId: proxy.id,
  742. }, [offscreen]);
  743. console.log('using OffscreenCanvas'); /* eslint-disable-line no-console */
  744. }
  745. </pre>
  746. <p>Et voici les gestionnaires d'événements. Tout ce qu'ils font est de copier une liste de propriétés
  747. à partir de l'événement qu'ils reçoivent. On leur passe une fonction <code class="notranslate" translate="no">sendEvent</code> à laquelle ils passent les données
  748. qu'ils créent. Cette fonction ajoutera l'identifiant correct et l'enverra au worker.</p>
  749. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mouseEventHandler = makeSendPropertiesHandler([
  750. 'ctrlKey',
  751. 'metaKey',
  752. 'shiftKey',
  753. 'button',
  754. 'pointerType',
  755. 'clientX',
  756. 'clientY',
  757. 'pointerId',
  758. 'pageX',
  759. 'pageY',
  760. ]);
  761. const wheelEventHandlerImpl = makeSendPropertiesHandler([
  762. 'deltaX',
  763. 'deltaY',
  764. ]);
  765. const keydownEventHandler = makeSendPropertiesHandler([
  766. 'ctrlKey',
  767. 'metaKey',
  768. 'shiftKey',
  769. 'keyCode',
  770. ]);
  771. function wheelEventHandler(event, sendFn) {
  772. event.preventDefault();
  773. wheelEventHandlerImpl(event, sendFn);
  774. }
  775. function preventDefaultHandler(event) {
  776. event.preventDefault();
  777. }
  778. function copyProperties(src, properties, dst) {
  779. for (const name of properties) {
  780. dst[name] = src[name];
  781. }
  782. }
  783. function makeSendPropertiesHandler(properties) {
  784. return function sendProperties(event, sendFn) {
  785. const data = {type: event.type};
  786. copyProperties(event, properties, data);
  787. sendFn(data);
  788. };
  789. }
  790. function touchEventHandler(event, sendFn) {
  791. // preventDefault() corrige les événements mousemove, mouseup et mousedown
  792. // qui se déclenchent lors d'un simple toucher/relâcher
  793. // Cela n'arrive qu'avec OffscreenCanvas
  794. event.preventDefault();
  795. const touches = [];
  796. const data = {type: event.type, touches};
  797. for (let i = 0; i &lt; event.touches.length; ++i) {
  798. const touch = event.touches[i];
  799. touches.push({
  800. pageX: touch.pageX,
  801. pageY: touch.pageY,
  802. clientX: touch.clientX,
  803. clientY: touch.clientY,
  804. });
  805. }
  806. sendFn(data);
  807. }
  808. // Les quatre touches fléchées
  809. const orbitKeys = {
  810. '37': true, // left
  811. '38': true, // up
  812. '39': true, // right
  813. '40': true, // down
  814. };
  815. function filteredKeydownEventHandler(event, sendFn) {
  816. const {keyCode} = event;
  817. if (orbitKeys[keyCode]) {
  818. event.preventDefault();
  819. keydownEventHandler(event, sendFn);
  820. }
  821. }
  822. </pre>
  823. <p>Cela semble proche de fonctionner, mais si nous l'essayons réellement, nous verrons
  824. que les <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> ont besoin de quelques éléments supplémentaires.</p>
  825. <p>L'une d'elles est qu'ils appellent <code class="notranslate" translate="no">element.focus</code>. Nous n'avons pas besoin que cela se produise
  826. dans le worker, alors ajoutons simplement un stub.</p>
  827. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ElementProxyReceiver extends THREE.EventDispatcher {
  828. constructor() {
  829. super();
  830. }
  831. handleEvent(data) {
  832. this.dispatchEvent(data);
  833. }
  834. + focus() {
  835. + // sans opération
  836. + }
  837. }
  838. </pre>
  839. <p>Une autre chose est qu'ils appellent <code class="notranslate" translate="no">event.preventDefault</code> et <code class="notranslate" translate="no">event.stopPropagation</code>.
  840. Nous gérons déjà cela dans la page principale, donc ceux-ci peuvent également être un noop (sans opération).</p>
  841. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function noop() {
  842. +}
  843. class ElementProxyReceiver extends THREE.EventDispatcher {
  844. constructor() {
  845. super();
  846. }
  847. handleEvent(data) {
  848. + data.preventDefault = noop;
  849. + data.stopPropagation = noop;
  850. this.dispatchEvent(data);
  851. }
  852. focus() {
  853. // sans opération
  854. }
  855. }
  856. </pre>
  857. <p>Une autre chose est qu'ils regardent <code class="notranslate" translate="no">clientWidth</code> et <code class="notranslate" translate="no">clientHeight</code>. Nous
  858. passions la taille auparavant, mais nous pouvons mettre à jour la paire de proxies
  859. pour passer cela également.</p>
  860. <p>Dans le worker...</p>
  861. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ElementProxyReceiver extends THREE.EventDispatcher {
  862. constructor() {
  863. super();
  864. }
  865. + get clientWidth() {
  866. + return this.width;
  867. + }
  868. + get clientHeight() {
  869. + return this.height;
  870. + }
  871. + getBoundingClientRect() {
  872. + return {
  873. + left: this.left,
  874. + top: this.top,
  875. + width: this.width,
  876. + height: this.height,
  877. + right: this.left + this.width,
  878. + bottom: this.top + this.height,
  879. + };
  880. + }
  881. handleEvent(data) {
  882. + if (data.type === 'size') {
  883. + this.left = data.left;
  884. + this.top = data.top;
  885. + this.width = data.width;
  886. + this.height = data.height;
  887. + return;
  888. + }
  889. data.preventDefault = noop;
  890. data.stopPropagation = noop;
  891. this.dispatchEvent(data);
  892. }
  893. focus() {
  894. // sans opération
  895. }
  896. }
  897. </pre>
  898. <p>de retour dans la page principale, nous devons envoyer la taille ainsi que les positions gauche et haut également.
  899. Notez qu'en l'état, nous ne gérons pas si le canevas se déplace, seulement s'il redimensionne. Si vous vouliez
  900. gérer le déplacement, vous devriez appeler <code class="notranslate" translate="no">sendSize</code> chaque fois que quelque chose déplace le canevas.</p>
  901. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ElementProxy {
  902. constructor(element, worker, eventHandlers) {
  903. this.id = nextProxyId++;
  904. this.worker = worker;
  905. const sendEvent = (data) =&gt; {
  906. this.worker.postMessage({
  907. type: 'event',
  908. id: this.id,
  909. data,
  910. });
  911. };
  912. // register an id
  913. worker.postMessage({
  914. type: 'makeProxy',
  915. id: this.id,
  916. });
  917. + sendSize();
  918. for (const [eventName, handler] of Object.entries(eventHandlers)) {
  919. element.addEventListener(eventName, function(event) {
  920. handler(event, sendEvent);
  921. });
  922. }
  923. + function sendSize() {
  924. + const rect = element.getBoundingClientRect();
  925. + sendEvent({
  926. + type: 'size',
  927. + left: rect.left,
  928. + top: rect.top,
  929. + width: element.clientWidth,
  930. + height: element.clientHeight,
  931. + });
  932. + }
  933. +
  934. + window.addEventListener('resize', sendSize);
  935. }
  936. }
  937. </pre>
  938. <p>et dans notre code three.js partagé, nous n'avons plus besoin de <code class="notranslate" translate="no">state</code></p>
  939. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-export const state = {
  940. - width: 300, // par défaut du canevas
  941. - height: 150, // par défaut du canevas
  942. -};
  943. ...
  944. function resizeRendererToDisplaySize(renderer) {
  945. const canvas = renderer.domElement;
  946. - const width = state.width;
  947. - const height = state.height;
  948. + const width = inputElement.clientWidth;
  949. + const height = inputElement.clientHeight;
  950. const needResize = canvas.width !== width || canvas.height !== height;
  951. if (needResize) {
  952. renderer.setSize(width, height, false);
  953. }
  954. return needResize;
  955. }
  956. function render(time) {
  957. time *= 0.001;
  958. if (resizeRendererToDisplaySize(renderer)) {
  959. - camera.aspect = state.width / state.height;
  960. + camera.aspect = inputElement.clientWidth / inputElement.clientHeight;
  961. camera.updateProjectionMatrix();
  962. }
  963. ...
  964. </pre>
  965. <p>Quelques hacks supplémentaires. Les OrbitControls ajoutent des événements <code class="notranslate" translate="no">pointermove</code> et <code class="notranslate" translate="no">pointerup</code> à l'<code class="notranslate" translate="no">ownerDocument</code>
  966. de l'élément pour gérer la capture de la souris (lorsque la souris sort de la fenêtre).</p>
  967. <p>De plus, le code référence le <code class="notranslate" translate="no">document</code> global, mais il n'y a pas de document global
  968. dans un worker. </p>
  969. <p>Nous pouvons résoudre tout cela avec 2 hacks rapides. Dans notre code worker,
  970. nous allons réutiliser notre proxy pour les deux problèmes.</p>
  971. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function start(data) {
  972. const proxy = proxyManager.getProxy(data.canvasId);
  973. + proxy.ownerDocument = proxy; // HACK!
  974. + self.document = {} // HACK!
  975. init({
  976. canvas: data.canvas,
  977. inputElement: proxy,
  978. });
  979. }
  980. </pre>
  981. <p>Cela donnera aux <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> quelque chose à inspecter qui
  982. correspond à leurs attentes.</p>
  983. <p>Je sais que c'était un peu difficile à suivre. La version courte est la suivante :
  984. <code class="notranslate" translate="no">ElementProxy</code> s'exécute sur la page principale et transmet les événements DOM
  985. à <code class="notranslate" translate="no">ElementProxyReceiver</code> dans le worker, qui se fait passer pour un <code class="notranslate" translate="no">HTMLElement</code>
  986. que nous pouvons utiliser à la fois avec les <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> et avec notre propre code.</p>
  987. <p>La dernière chose est notre fallback lorsque nous n'utilisons pas OffscreenCanvas.
  988. Tout ce que nous avons à faire est de passer le canevas lui-même comme notre <code class="notranslate" translate="no">inputElement</code>.</p>
  989. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function startMainPage(canvas) {
  990. - init({canvas});
  991. + init({canvas, inputElement: canvas});
  992. console.log('using regular canvas');
  993. }
  994. </pre>
  995. <p>et maintenant nous devrions avoir les OrbitControls fonctionnant avec OffscreenCanvas</p>
  996. <p></p><div translate="no" class="threejs_example_container notranslate">
  997. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/offscreencanvas-w-orbitcontrols.html"></iframe></div>
  998. <a class="threejs_center" href="/manual/examples/offscreencanvas-w-orbitcontrols.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
  999. </div>
  1000. <p></p>
  1001. <p>C'est probablement l'exemple le plus compliqué sur ce site. Il est un
  1002. peu difficile à suivre car il y a 3 fichiers impliqués pour chaque
  1003. exemple. Le fichier HTML, le fichier worker, le code three.js partagé.</p>
  1004. <p>J'espère que ce n'était pas trop difficile à comprendre et que cela a fourni
  1005. des exemples utiles pour travailler avec three.js, OffscreenCanvas et les web workers.</p>
  1006. </div>
  1007. </div>
  1008. </div>
  1009. <script src="../resources/prettify.js"></script>
  1010. <script src="../resources/lesson.js"></script>
  1011. </body></html>
粤ICP备19079148号