<?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>Forem: Emre Tekinalp</title>
    <description>The latest articles on Forem by Emre Tekinalp (@etekinalp).</description>
    <link>https://forem.com/etekinalp</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2539438%2F0c7dc82c-1630-439b-9dd4-dbcaef5130d5.jpeg</url>
      <title>Forem: Emre Tekinalp</title>
      <link>https://forem.com/etekinalp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/etekinalp"/>
    <language>en</language>
    <item>
      <title>"THIS Is Not My Child" - Integrating PIXI.js in Tauri Vite React</title>
      <dc:creator>Emre Tekinalp</dc:creator>
      <pubDate>Sun, 30 Nov 2025 19:29:39 +0000</pubDate>
      <link>https://forem.com/etekinalp/this-is-not-my-child-integrating-pixijs-in-tauri-vite-react-4j0b</link>
      <guid>https://forem.com/etekinalp/this-is-not-my-child-integrating-pixijs-in-tauri-vite-react-4j0b</guid>
      <description>&lt;p&gt;Ever start a “simple feature” and end up with a full-blown engineering case study? That was me last week.&lt;/p&gt;

&lt;p&gt;I set out to build a high-performance &lt;strong&gt;2D canvas&lt;/strong&gt; inside a Tauri app using &lt;strong&gt;React&lt;/strong&gt;, &lt;strong&gt;PIXI.js&lt;/strong&gt;, and &lt;strong&gt;pixi-viewport&lt;/strong&gt; — just a grid, zoom, pan, and GPU-accelerated drawing. Estimated time: 2–3 hours. &lt;br&gt;
Actual time: a little longer… 🙃&lt;/p&gt;

&lt;p&gt;Here’s what I learned, step by step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want the longer, meme-filled version?&lt;br&gt;
The original LinkedIn article dives deeper with stories, humor, and some chaotic developer moments:&lt;br&gt;
&lt;a href="https://www.linkedin.com/pulse/my-child-integrating-pixijs-tauri-vite-react-emre-tekinalp-05t9c" rel="noopener noreferrer"&gt;“THIS Is Not My Child” – LinkedIn version&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Choosing the Right Tools&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Over the years, I’ve explored a ton of 2D frameworks: Canvas2D, ZimJS, Fabric.js, Konva.js, even Three.js. For this project, &lt;strong&gt;PIXI.js&lt;/strong&gt; won:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU-accelerated&lt;/li&gt;
&lt;li&gt;Focused on 2D&lt;/li&gt;
&lt;li&gt;Mature ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I paired it with React 19, &lt;strong&gt;pixi-viewport&lt;/strong&gt; for zoom/pan, and wrapped it in &lt;strong&gt;Tauri + Vite&lt;/strong&gt; for a desktop app.&lt;/p&gt;

&lt;p&gt;Easy setup, right? …Spoiler: not quite.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The “Not My Child” Problem&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Viewport&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Graphics&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Viewport&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;React immediately rejected it: &lt;em&gt;“This is not my child.”&lt;/em&gt; 😐&lt;/p&gt;

&lt;p&gt;Why? PIXI is imperative (how to draw), React is declarative (what to show). They clash by default.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Bridging React and PIXI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Solution: &lt;strong&gt;ViewportAdapter&lt;/strong&gt; — a custom React wrapper around pixi-viewport.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initializes PIXI correctly inside React&lt;/li&gt;
&lt;li&gt;Waits for the renderer and event system to be ready&lt;/li&gt;
&lt;li&gt;Binds viewport interactions (drag, zoom, resize)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this adapter, React and PIXI can finally coexist peacefully.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Race Conditions and App Readiness&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Even with the adapter, the canvas sometimes rendered blank. React sometimes “won the race” before PIXI initialized.&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;AppReadySensor&lt;/strong&gt; — a small component that waits until PIXI is fully ready before rendering the canvas.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;No more blank screens&lt;/strong&gt;, guaranteed initialization order.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Resizing Across DPI Scales&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Tauri’s DPI-scaled dimensions caused subtle drifting and flickering.&lt;/p&gt;

&lt;p&gt;Solution: a &lt;strong&gt;custom window metrics hook&lt;/strong&gt; that calculates real pixel sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCurrentWindow&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;physical&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;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outerSize&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;scale&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;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleFactor&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;logical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;physical&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="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;physical&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="nx"&gt;scale&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;✅ Pixel-perfect scaling across Retina and 4K&lt;br&gt;
✅ No floating or stretched elements&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Optimizing the Infinite Grid&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Rendering thousands of grid lines continuously = GPU nightmare.&lt;/p&gt;

&lt;p&gt;Fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw only visible lines in viewport&lt;/li&gt;
&lt;li&gt;Scale stroke thickness dynamically by zoom factor
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStrokeStyle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;   
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;camera&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;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xcccccc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    
  &lt;span class="na"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.4&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;Outcome: smooth zoom, consistent sharpness, and stable GPU usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Ensuring Stability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Some async initialization issues persisted occasionally.&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;Multi RAF Retry Logic&lt;/strong&gt; — use multiple &lt;code&gt;requestAnimationFrame&lt;/code&gt; cycles and timed retries to guarantee the PIXI renderer and event system are ready.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;Every launch renders perfectly&lt;/strong&gt;, first try.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Over-Engineering for Fun&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once stable, I couldn’t resist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vitest unit tests&lt;/strong&gt; (mocking Tauri and PIXI)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typedoc documentation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESLint + TypeScript strict mode&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD with GitHub Actions&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Was it overkill for a red rectangle and a grid? Probably. Was it fun and educational? Absolutely.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Lessons Learned&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Imperative + declarative systems can coexist — but need clear boundaries.&lt;/li&gt;
&lt;li&gt;Initialization order matters — async frameworks can bite.&lt;/li&gt;
&lt;li&gt;Don’t trust window dimensions blindly in multi-DPI environments.&lt;/li&gt;
&lt;li&gt;Small abstractions like AppReadySensor prevent subtle bugs.&lt;/li&gt;
&lt;li&gt;Testing and documentation habits are worth building early, even in small projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Check Out the Repo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Full TypeScript source, adapter pattern, resizing handling, infinite grid, tests, and CI/CD:&lt;br&gt;
👉 &lt;a href="https://github.com/etekinalp/tauri-pixi-viewport" rel="noopener noreferrer"&gt;https://github.com/etekinalp/tauri-pixi-viewport&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone it, explore, or adapt it for your own projects — all under MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;What started as a tiny canvas experiment turned into a deep dive in &lt;strong&gt;synchronization, rendering pipelines, and desktop performance optimization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;React + PIXI + Tauri + Vite makes a solid foundation for &lt;strong&gt;modern creative tools or real-time visual interfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even small projects can teach &lt;strong&gt;big lessons&lt;/strong&gt; — about design, architecture, and bridging frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Your Turn&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Have you ever integrated React with an imperative library like PIXI or Three.js?&lt;br&gt;
How did you handle synchronization, lifecycle, and rendering challenges?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Extra fun version: For memes, anecdotes, and more chaotic storytelling, check the original LinkedIn article &lt;a href="https://www.linkedin.com/pulse/my-child-integrating-pixijs-tauri-vite-react-emre-tekinalp-05t9c" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>tauri</category>
      <category>pixijs</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
