<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: NixieFX</title>
    <description>The latest articles on DEV Community by NixieFX (nixiefx).</description>
    <link>https://dev.to/nixiefx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15199%2F99538b0f-1fda-413f-85d4-89182c3c82f0.png</url>
      <title>DEV Community: NixieFX</title>
      <link>https://dev.to/nixiefx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nixiefx"/>
    <language>en</language>
    <item>
      <title>Make campfire wind reproducible before tuning the camera</title>
      <dc:creator>Almast </dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:02:03 +0000</pubDate>
      <link>https://dev.to/nixiefx/make-campfire-wind-reproducible-before-tuning-the-camera-4e1b</link>
      <guid>https://dev.to/nixiefx/make-campfire-wind-reproducible-before-tuning-the-camera-4e1b</guid>
      <description>&lt;p&gt;Disclosure: AI created this tutorial, source and synthetic scene for a NixieFX marketing assignment. The numeric checks and browser captures described here ran locally with nixie-fx 0.1.17 and Three.js 0.185.1. The review record consists of AI source inspection and actual runtime checks; no human code-review signoff is claimed.&lt;/p&gt;

&lt;p&gt;A campfire ember test becomes hard to compare when the camera, seed and elapsed time all change between captures. This example holds those inputs steady and changes the initial X velocity. The result is a modest stream of particles that rises and drifts to one side of a synthetic log marker.&lt;/p&gt;

&lt;p&gt;The demo has Right and Left wind options, a replay button, a fixed-time inspection button and scene disposal. It uses a real exported NixieFX effect with a Three.js adapter. Wind here means an authored initial velocity bias. It does not mean a fluid simulation or a changing force field acting on every live particle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The effect has a small, explicit budget
&lt;/h2&gt;

&lt;p&gt;The emitter uses a 0.35 by 0.05 by 0.35 spawn box, emits at 24 particles per second, and gives particles a two-second lifetime. Its maximum capacity is 56. Each ember is a small unlit procedural billboard. Depth testing is enabled and depth writing is disabled so the transparent particles do not write an opaque depth surface.&lt;/p&gt;

&lt;p&gt;The upward velocity ranges from 0.7 to 1.1 units per second. Z ranges from -0.16 to 0.16. For the Right variant, X ranges from 0.22 to 0.62. For Left, X ranges from -0.62 to -0.22. These are demonstration units. Their physical interpretation depends on the host scene's scale.&lt;/p&gt;

&lt;p&gt;The original export validates before the host creates a derived preview definition. Keep the package versions and the exported source in the handoff. Replacing the effect file without replacing its manifest can invalidate the bundle's identity checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the bundle before constructing the instance
&lt;/h2&gt;

&lt;p&gt;Fetch the manifest and its referenced effect files, then give those parsed values to &lt;code&gt;loadVfxExportBundle&lt;/code&gt;. Pass &lt;code&gt;requiredBackend: "three3d"&lt;/code&gt;. The loader verifies the export structure and support information; fetching arbitrary JSON and hoping the renderer accepts it loses that check.&lt;/p&gt;

&lt;p&gt;The complete source includes the loading path and the authored project. This excerpt focuses on the wind variant after a verified effect has been selected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;normalizeThreeVfxEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nixie-fx/three&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeThreeVfxEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportedEmbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emitters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;initializeParticle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rightWind&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.22&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.62&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rightWind&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.62&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Preview inspection only. Do not seek on every animation frame.&lt;/span&gt;
&lt;span class="nx"&gt;embers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;embers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During normal animation, the host advances &lt;code&gt;vfx.update(deltaSeconds)&lt;/code&gt; once per frame and renders its scene. The inspection action seeks to one second and pauses. It makes the comparison reproducible without capturing the stream at an arbitrary wall-clock moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the output at the same simulation time
&lt;/h2&gt;

&lt;p&gt;The local numeric check created both variants with seed 42 and sampled at one second. Each contained 23 live particles at that sample. The mean X position was approximately +0.207 for Right and -0.209 for Left. Those values describe this particular fixture and update schedule. They are not a performance measurement or a claim about an arbitrary game's wind model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsriuryos9uzqkl3vrghv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsriuryos9uzqkl3vrghv.jpg" alt="Right wind at one simulated second" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0fhww61dlr3aekwouib.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0fhww61dlr3aekwouib.jpg" alt="Left wind at one simulated second" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The camera stays at the same oblique angle for both captures. A fixed camera prevents a view change from making the wind reversal appear larger or smaller. A front view remains available for inspection, but it should be treated as a separate check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop and release the effect with the scene
&lt;/h2&gt;

&lt;p&gt;The replay action removes and destroys the previous instance before creating another. Scene disposal cancels the animation callback, disconnects the resize observer, destroys the VFX renderer and disposes host geometry and materials. The demo also releases its texture store, although this particular ember effect uses no texture file.&lt;/p&gt;

&lt;p&gt;That ownership split matters when a scene contains imported assets. An effect instance does not automatically own every resource supplied by the host. Release the resources your host created when that scene ends, and avoid destroying shared assets still used elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this example establishes
&lt;/h2&gt;

&lt;p&gt;The export has no validation errors and the active Three.js ember instance reports no unsupported features in the local check. The example does not exercise collision, authored lights, trails, complex material graphs or PixiJS visual equivalence. Turning those settings on would require another support and runtime check.&lt;/p&gt;

&lt;p&gt;If you use a three js particle editor for the authored effect, the &lt;a href="https://nixiefx.com/threejs-runtime/" rel="noopener noreferrer"&gt;NixieFX Three.js runtime reference&lt;/a&gt; explains the adapter and instance controls. Keep the wind values, seed, sample time and camera position in the review record. That small amount of context lets another developer reproduce a visual disagreement before changing the effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://almastparuyryan.github.io/day10-source.zip" rel="noopener noreferrer"&gt;Download the reproducible fixture source&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>animation</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Put a Three.js landing ring at a world position</title>
      <dc:creator>Almast </dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:33:43 +0000</pubDate>
      <link>https://dev.to/nixiefx/put-a-threejs-landing-ring-at-a-world-position-3fdc</link>
      <guid>https://dev.to/nixiefx/put-a-threejs-landing-ring-at-a-world-position-3fdc</guid>
      <description>&lt;p&gt;Disclosure: This article and demo were generated by an AI coding agent for a NixieFX marketing task. The scene is a synthetic runtime fixture. Review the technical details in your own scene before production use.&lt;/p&gt;

&lt;p&gt;A teleport landing cue should stay attached to its world location as the camera moves. Its radius should also follow the scene's scale. A screen overlay can look convincing in one screenshot while failing both conditions.&lt;/p&gt;

&lt;p&gt;This example uses an exported particle burst on the XZ plane, positioned at &lt;code&gt;[1, 0.04, -0.5]&lt;/code&gt;. The host offers three cameras and three uniform scales so those assumptions can be checked directly. It implements the cue, not teleport movement or collision detection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkz50xh8bqhduohb39mg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkz50xh8bqhduohb39mg6.png" alt="Landing ring at a marked world position" width="800" height="897"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Caption: Actual local browser capture at scale one. The floor grid and center marker are host geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Author a ring that matches the coordinate convention
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;landing-ring&lt;/code&gt; effect targets &lt;code&gt;three-world-3d&lt;/code&gt;. One burst emits forty particles, with a maximum capacity of forty-eight and a lifetime of 0.85 seconds. Continuous emission is zero. The spawn shape is &lt;code&gt;circle&lt;/code&gt;, radius one, with radius thickness zero.&lt;/p&gt;

&lt;p&gt;In the pinned runtime, circle emission lies in local XZ. The supplied definition leaves spawn rotation at zero; no quarter-turn is needed to put it on the floor. Initial vertical velocity is 0.08 units per second. The particles are small unlit cyan billboards with depth testing enabled and depth writing disabled.&lt;/p&gt;

&lt;p&gt;The project uses NixieFX 0.1.17 and Three.js 0.185.1. Do not assume another version or backend interprets every setting identically. The export's support report is part of the integration input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the compiled effect
&lt;/h2&gt;

&lt;p&gt;The supplied &lt;code&gt;src/bundle.js&lt;/code&gt; imports the compiled manifest and exported effects, builds an &lt;code&gt;effectsByPath&lt;/code&gt; map, and calls the official loader. It requires the &lt;code&gt;three3d&lt;/code&gt; backend and the requested effect ID before returning the definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadVfxExportBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;effectsByPath&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requiredBackend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;three3d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredEffectIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;landing-ring&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effectsById&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;landing-ring&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loader consumes compiled export envelopes. An authoring file and a compiled effect are different inputs. This asset uses the built-in procedural billboard, so it needs no texture provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Place and replay one instance
&lt;/h2&gt;

&lt;p&gt;The host already has a scene, camera, and renderer. It creates one VFX renderer and replaces the previous instance before replay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreeVfxRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;captureDebugTransforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advance &lt;code&gt;vfx.update(deltaSeconds)&lt;/code&gt; once per host frame, then render the scene. The fixture caps individual steps at 0.05 seconds and skips simulation while the document is hidden. These are host choices, not a guarantee about simulation time during backgrounding.&lt;/p&gt;

&lt;p&gt;The 0.04-unit offset is chosen for this floor and marker. A real landing surface may need a different offset, surface orientation, or slope handling. The example does not solve placement on arbitrary terrain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure rendered scale in world space
&lt;/h2&gt;

&lt;p&gt;The local check exposed a useful distinction: &lt;code&gt;getParticleDebugTransforms()&lt;/code&gt; reports sample positions before the instance root's scale. Comparing those positions directly with a desired world radius gives the wrong measurement.&lt;/p&gt;

&lt;p&gt;For the actual rendered centers, the fixture takes each debug matrix's translation and applies the instance root's world matrix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getParticleDebugTransforms&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFromMatrixPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Matrix4&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyMatrix4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matrixWorld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs after rendering has updated the world matrices. Debug capture is useful for this diagnostic and can be disabled in production. The check measures distance from the known center in XZ; it does not infer a radius from the apparent size of a billboard on screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check camera, scale, and completion separately
&lt;/h2&gt;

&lt;p&gt;The browser verification covered oblique, top, and low cameras at uniform scales 0.5, 1, and 2, at viewport widths of 1280 and 390 pixels. Each of the eighteen cases recorded forty active particles during the burst. The largest measured radius error was below 0.000001 world units.&lt;/p&gt;

&lt;p&gt;After waiting for completion, the active count reached zero. The runtime reported no unsupported features, missing meshes, or missing materials. These observations establish the fixture's placement and lifecycle checks; they are not a GPU timing benchmark or a guarantee of legibility in a production scene.&lt;/p&gt;

&lt;p&gt;Inspect the supplied camera screenshots too. The low view compresses the ring's projected footprint, and the top view exposes its distribution. Random positions around a circle do not produce evenly spaced segments, and camera-facing billboards do not form a solid ground decal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the cue's limits visible
&lt;/h2&gt;

&lt;p&gt;There is no teleport controller, navmesh query, collision response, terrain projection, or light emitted onto the floor. The effect does not write depth, and it uses no bloom postprocessing. PixiJS does not provide the same GPU depth-buffer behavior as this Three.js host.&lt;/p&gt;

&lt;p&gt;For a &lt;strong&gt;visual particle editor for three.js&lt;/strong&gt;, the integration decision belongs in the real scene. The &lt;a href="https://nixiefx.com/threejs-runtime/" rel="noopener noreferrer"&gt;NixieFX Three.js runtime reference&lt;/a&gt; covers bundle loading and instance transforms used here.&lt;/p&gt;

&lt;p&gt;The complete Day 9 source includes the authoring definition, compiled output, host code, and verification script. Open &lt;code&gt;?mode=ring&lt;/code&gt;, change cameras and scales, and replay. On teardown the host cancels animation, disconnects resizing, destroys VFX resources, and disposes its own geometry and materials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://almastparuyryan.github.io/day09.html?mode=ring" rel="noopener noreferrer"&gt;Open the working demo&lt;/a&gt; and &lt;a href="https://almastparuyryan.github.io/day09-source.zip" rel="noopener noreferrer"&gt;download the complete source&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>threejs</category>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Keep a PixiJS hover ember alive while the button has keyboard focus</title>
      <dc:creator>Almast </dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:44:26 +0000</pubDate>
      <link>https://dev.to/nixiefx/keep-a-pixijs-hover-ember-alive-while-the-button-has-keyboard-focus-1cn4</link>
      <guid>https://dev.to/nixiefx/keep-a-pixijs-hover-ember-alive-while-the-button-has-keyboard-focus-1cn4</guid>
      <description>&lt;p&gt;Disclosure: This article and demo were generated by a coding agent for a NixieFX marketing task. The code was validated in a synthetic button demo; this is not a production-game capture.&lt;/p&gt;

&lt;p&gt;A button can have a hover effect without making hover its only way to communicate. The important state is whether the user is interacting with it: a pointer may be over the button, or keyboard focus may still be inside it after the pointer leaves.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;NixieFX tutorial&lt;/strong&gt; uses a real HTML button over a PixiJS canvas. The button supplies native keyboard activation and focus. PixiJS renders a decorative ember layer that cannot receive pointer input. The button's layout stays independent of particle bounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a small, exported effect
&lt;/h2&gt;

&lt;p&gt;The accompanying source pins &lt;code&gt;nixie-fx&lt;/code&gt; to 0.1.17 and &lt;code&gt;pixi.js&lt;/code&gt; to 8.19.0. Its &lt;code&gt;hover-ember&lt;/code&gt; authoring effect uses a box emitter, 22 requested particles per second, a 0.65-second lifetime, and a hard capacity of 24. Initial velocity moves particles upward in screen coordinates. These are authored values, not a performance claim.&lt;/p&gt;

&lt;p&gt;The demo uses an exported manifest and compiled effect JSON. It calls &lt;code&gt;loadVfxExportBundle&lt;/code&gt; with &lt;code&gt;requiredBackend: "pixi2d"&lt;/code&gt;, checks the backend support report, then creates a &lt;code&gt;PixiVfxRenderer&lt;/code&gt; with a separate parent container.&lt;/p&gt;

&lt;p&gt;The estimated steady population is about 14 particles before discrete emission and fade behavior. The live counter is the observation to inspect. Capacity is an upper bound, not the number that should be visible every frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combine hover and focus as independent inputs
&lt;/h2&gt;

&lt;p&gt;Do not stop the effect on every &lt;code&gt;pointerleave&lt;/code&gt;. A keyboard user might still have focus on the same button. Conversely, losing focus should not stop the effect if a pointer remains over it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;motion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-reduced-motion: reduce)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wanted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hovered&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;focused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointerenter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointerleave&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This excerpt assumes the verified effect and renderer are already available. The complete source includes loading, position updates, disposal, and statistics. Its button click handler increments a demonstration counter; connect the existing game action in a real UI.&lt;/p&gt;

&lt;p&gt;The focus ring remains visible whether motion is allowed or not. Particles are decoration, not the only indication that the button can be activated. On touch devices, the native button still works without requiring a hover state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolve layout coordinates each frame
&lt;/h2&gt;

&lt;p&gt;The native button is centered in a responsive stage. Its DOM rectangle must be converted into that stage's local CSS-pixel coordinates. The Pixi projection uses one pixel per unit and a downward Y axis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonRect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stageRect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setPosition&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;buttonRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;stageRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;buttonRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;buttonRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;stageRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;buttonRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deltaMS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the renderer update once per frame. The example caps the step at 0.05 seconds and skips hidden documents. It therefore chooses stable visual continuation over catching up every elapsed wall-clock second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop the decoration and remove its owners
&lt;/h2&gt;

&lt;p&gt;In the pinned PixiJS adapter, &lt;code&gt;stop()&lt;/code&gt; clears the effect immediately. This example removes the stopped instance from the renderer and creates a fresh one on re-entry. It does not claim the Three.js adapter's graceful-completion API is available here. During teardown, remove event listeners and the ticker callback, destroy the VFX renderer, and destroy the Pixi application and its canvas.&lt;/p&gt;

&lt;p&gt;The reduced-motion setting prevents fresh emission. If it changes while particles are alive, the example clears them immediately. A static focus ring remains available.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this example does not transfer to PixiJS
&lt;/h2&gt;

&lt;p&gt;The recipe uses a supported 2D billboard effect without file textures, custom materials, trails, or scene lighting. It does not implement GPU depth occlusion or a 3D collision response. A successful export is not evidence that every editor option will render identically across backends.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nixiefx.com/pixijs-particle-effects/" rel="noopener noreferrer"&gt;NixieFX PixiJS particle-effects guide&lt;/a&gt; is the assigned reference for the integration. Use the exported support report for the actual authored effect and keep the installed package version with the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce and inspect
&lt;/h2&gt;

&lt;p&gt;Run the supplied demo in &lt;code&gt;hover&lt;/code&gt; mode. Tab to Continue, activate it with Enter, move the pointer away while focus remains, then move focus away too. Check that particles clear without disabling the button's existing action. Repeat with reduced motion enabled and at a narrow viewport.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakhwc1acwufsfakqesad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakhwc1acwufsfakqesad.png" alt="Continue button with keyboard focus and a restrained ember effect" width="800" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://almastparuyryan.github.io/day-08.html?mode=hover" rel="noopener noreferrer"&gt;Open the working demo&lt;/a&gt; and &lt;a href="https://almastparuyryan.github.io/day-08-source.zip" rel="noopener noreferrer"&gt;download the complete source&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pixijs</category>
      <category>javascript</category>
      <category>a11y</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Keep a PixiJS item reveal out of the layout calculation</title>
      <dc:creator>Almast </dc:creator>
      <pubDate>Fri, 09 Oct 2026 08:52:04 +0000</pubDate>
      <link>https://dev.to/nixiefx/keep-a-pixijs-item-reveal-out-of-the-layout-calculation-482a</link>
      <guid>https://dev.to/nixiefx/keep-a-pixijs-item-reveal-out-of-the-layout-calculation-482a</guid>
      <description>&lt;p&gt;AI disclosure: This tutorial and its sample code were prepared with AI assistance. It is a local implementation recipe, not a device-performance benchmark.&lt;/p&gt;

&lt;p&gt;A rarity reveal should draw attention to an item without moving the item card. The common trap is to let the particle container participate in the bounds used to size or center the interface. The effect expands, the measured bounds change, and the card appears to jump.&lt;/p&gt;

&lt;p&gt;The sample uses a fixed card, a preloaded crystal icon, and a separate effect layer. The item and its rarity label remain in the layout container. Particles are siblings of that container rather than children whose bounds determine its position.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdnijyjpp7lvsrqcm03l6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdnijyjpp7lvsrqcm03l6.png" alt="PixiJS rarity reveal with a fixed item card, crystal icon, particles, and layout diagnostics" width="799" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Actual local fixture capture (1424 × 1343 image). This synthetic scene shows the integration and counters, not a device-performance benchmark.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Load before enabling the reveal
&lt;/h2&gt;

&lt;p&gt;The accompanying source includes an original SVG item icon and the compiled &lt;code&gt;rarity-reveal&lt;/code&gt; effect. The particle effect uses procedural circles, so its export has no file textures. That distinction matters: an empty effect asset list does not mean the UI image can be loaded after the first click.&lt;/p&gt;

&lt;p&gt;Keep the replay button disabled until the export loader accepts the bundle and &lt;code&gt;Assets.load&lt;/code&gt; resolves the item image. Failed fetches should leave a visible error and a disabled trigger. In a game using textured particles, preload every referenced particle texture into the provider as well.&lt;/p&gt;

&lt;p&gt;For readers working with a &lt;strong&gt;pixi js particle editor&lt;/strong&gt;, &lt;a href="https://nixiefx.com/pixijs-particle-effects/" rel="noopener noreferrer"&gt;the NixieFX PixiJS integration guide&lt;/a&gt; supplies the export and renderer context. This example focuses on the host layout boundary rather than reproducing the guide's reward fountain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserve the layout and add an independent layer
&lt;/h2&gt;

&lt;p&gt;Here is the core arrangement from the supplied runnable source. &lt;code&gt;app&lt;/code&gt; is an initialized PixiJS application and &lt;code&gt;effect&lt;/code&gt; is the validated exported effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Graphics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;roundRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x243146&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;texture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./item.svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Sprite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effectLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectLayer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PixiVfxRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;effectLayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createPixiVfx2dProjection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;originX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;originY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pixelsPerUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;yAxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reveal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toGlobal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;vfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full source handles button state, bundle loading, diagnostics, and teardown. Import &lt;code&gt;createPixiVfx2dProjection&lt;/code&gt; from &lt;code&gt;nixie-fx/pixi&lt;/code&gt; alongside the renderer. The demo explicitly selects pixels and a downward Y axis to match Pixi UI coordinates. The adapter's default projection points Y upward, which would place a positive UI Y coordinate above the canvas. If the host transforms the effect layer, also convert the point into its local space before spawning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give repeated triggers one policy
&lt;/h2&gt;

&lt;p&gt;The demo replaces an existing reveal before creating the next one. This avoids stacking bursts when a player taps repeatedly. A different game might deliberately overlap reveals, but then it needs an explicit capacity budget and cleanup policy.&lt;/p&gt;

&lt;p&gt;Use the Pixi instance's documented lifecycle rather than assuming that it has the Three.js adapter's controls. The supplied host advances the renderer once per ticker callback, removes inactive instances, and removes the callback before destroying the renderer. Pause in this example gates the update call; it is a host control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the card and the effect separately
&lt;/h2&gt;

&lt;p&gt;The diagnostics compare the card's width and height and the content position against their baseline. In the local browser check, three trigger actions left the tracked maximum bounds delta at zero. An active reveal showed 18 particles and no missing texture, material, or sub-emitter references. After completion, the effect count returned to zero.&lt;/p&gt;

&lt;p&gt;That is evidence about this fixed-size fixture. It does not establish correctness for a responsive inventory, transformed parents, or a scrolled panel. Test those cases with the host's actual coordinate conversion and layout rules.&lt;/p&gt;

&lt;p&gt;The capture above shows an active frame with the item, rarity text, and diagnostics visible. The source's zero-delta counter proves only the properties it measures. A visual comparison still needs to check the label baseline, card alignment, and whether particles obscure the item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits to keep visible
&lt;/h2&gt;

&lt;p&gt;The export validation has no errors, warnings, or blockers for this recipe. There are no mesh emitters, custom materials, sub-effects, or particle file textures to resolve. Adding any of those features changes the integration obligations.&lt;/p&gt;

&lt;p&gt;This Pixi UI example does not demonstrate GPU depth-buffer behavior, 3D lighting, or automatic responsive positioning. It also does not claim that every authored editor setting has equivalent behavior in every backend. Inspect support reports for the actual export and preserve any reported limitation.&lt;/p&gt;

&lt;p&gt;Try the &lt;a href="https://almastparuyryan.github.io/demos/day-07/?mode=rarity" rel="noopener noreferrer"&gt;runnable item-reveal fixture&lt;/a&gt; and &lt;a href="https://almastparuyryan.github.io/demos/day-07/day-07-source.zip" rel="noopener noreferrer"&gt;download its editable source&lt;/a&gt;. The source includes &lt;code&gt;assets/src/pixi.js&lt;/code&gt;, the authoring effect, compiled bundle, and original item image.&lt;/p&gt;

</description>
      <category>pixijs</category>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
