<?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: Dmitry Vasiliev</title>
    <description>The latest articles on Forem by Dmitry Vasiliev (@schmooky).</description>
    <link>https://forem.com/schmooky</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%2F554023%2F627d47e5-0217-4471-90d1-1ed289681096.jpeg</url>
      <title>Forem: Dmitry Vasiliev</title>
      <link>https://forem.com/schmooky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/schmooky"/>
    <language>en</language>
    <item>
      <title>Making adaptive UI layout in Pixi.JS easy with DOM</title>
      <dc:creator>Dmitry Vasiliev</dc:creator>
      <pubDate>Sat, 17 Dec 2022 14:34:44 +0000</pubDate>
      <link>https://forem.com/schmooky/making-adaptive-ui-layout-in-pixijs-easy-with-dom-186j</link>
      <guid>https://forem.com/schmooky/making-adaptive-ui-layout-in-pixijs-easy-with-dom-186j</guid>
      <description>&lt;p&gt;Most HTML5 game developers know that having a decent UI layout in canvas can be problematic. Whether because of screen sizes of native browsers or overwhelming creativity of UI/UX designed, buttons never seem to be well-positioned everywhere unless you solve corner cases in your code.&lt;br&gt;
Let's use DOM, the only piece of HTML5 technology that works on every browser. We will need to add a div with &lt;code&gt;position: absolute&lt;/code&gt; above our canvas.&lt;/p&gt;

&lt;p&gt;Say you have three areas in your game where you want to place some game content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82qco53usp39kc8hs8j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82qco53usp39kc8hs8j6.png" alt="Grid Layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, take your class-based Pixi.JS code and throw it away, because if you wanna be mature HTML5 game developer you have to grow some balls and learn React. Libraries like &lt;a href="https://github.com/michalochman/react-pixi-fiber" rel="noopener noreferrer"&gt;react-pixi-fiber&lt;/a&gt; and &lt;a href="https://github.com/pixijs/pixi-react" rel="noopener noreferrer"&gt;react-pixi&lt;/a&gt; will help you with that.&lt;/p&gt;

&lt;p&gt;Second, transform layout into HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;div class="wrapper"&amp;gt;
      &amp;lt;div class="box" id="buttons-left"&amp;gt;Left Controls&amp;lt;/div&amp;gt;
      &amp;lt;div class="box" id="game"&amp;gt;Frame&amp;lt;/div&amp;gt;
      &amp;lt;div class="box" id="buttons-right"&amp;gt;Right Controls&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.wrapper {
  display: grid;
  grid-gap: 10px;
  grid-template-columns: 200px 1fr 200px;
  color: #444;
  position: absolute;
  width: 100%;
  height: 100%;
}

.box {
  font-size: 18px;
  color: #fff;
  border-radius: 10px;
  padding: 50px;
  font-size: 150%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #7f55d452;
  height: 100%;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example on CodePen with default CSS styles removed &lt;a href="https://codepen.io/schmooky/pen/jOpNxqR" rel="noopener noreferrer"&gt;https://codepen.io/schmooky/pen/jOpNxqR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have to make a component that looks at bounding client rect of div to determine screen position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Container as PixiContainer, Point } from 'pixi.js';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { Container, PixiComponent } from 'react-pixi-fiber';

type ObserverContainerProps = {
  target: string;
  visible?: boolean;
  children: React.ReactNode;
  defaultWidth?: number;
};

export const ObserverContainer: PixiComponent&amp;lt;ObserverContainerProps&amp;gt; = (
  props: ObserverContainerProps,
) =&amp;gt; {
  const { target, visible, defaultWidth } = props;

  const containerRef = React.useRef&amp;lt;PixiContainer&amp;gt;(null);

  const ref = useRef&amp;lt;HTMLElement&amp;gt;();
  const [bbox, setBbox] = useState&amp;lt;Partial&amp;lt;DOMRect&amp;gt;&amp;gt;({});

  const set = () =&amp;gt; setBbox(ref &amp;amp;&amp;amp; ref.current ? ref.current.getBoundingClientRect() : {});

  const [scale, setScale] = React.useState(1);

  useEffect(() =&amp;gt; {
    set();
    window.addEventListener('resize', set);
    return () =&amp;gt; window.removeEventListener('resize', set);
  }, []);

  useLayoutEffect(() =&amp;gt; {
    ref.current = document.getElementById(target);
  }, [target]);

  useEffect(() =&amp;gt; {
    if (!containerRef.current) return;
    if (!defaultWidth) return;
    console.log(target, defaultWidth);
    setScale(bbox.width / defaultWidth);
  }, [bbox]);

  return (
    &amp;lt;Container
      ref={containerRef as any}
      visible={visible}
      x={bbox.left + bbox.width / 2}
      y={bbox.top + bbox.height / 2}
      scale={new Point(scale, scale)}
    &amp;gt;
      {props.children}
    &amp;lt;/Container&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If, for any reason, UI/UX designer would wanna "move that button", you can simply add &lt;code&gt;div&lt;/code&gt; with margin that offsets it's position.&lt;/p&gt;

&lt;p&gt;And, since dom elements are positioned in the same area of the screen as their canvas coutnerparts, &lt;a href="https://www.cypress.io" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;can provide you with smooth e2e testing experience.&lt;/p&gt;

&lt;p&gt;This is just an example of such component that takes into account width of target DOM element to resize it's contents. You might wanna add fit/fill cover calculation using &lt;code&gt;Math.max&lt;/code&gt; or &lt;code&gt;Math.min&lt;/code&gt;, or even add MutationObserver to look at more delicate attribute changes based on media query.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>pixi</category>
      <category>react</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
