DEV Community

Dmitry Vasiliev
Dmitry Vasiliev

Posted on

2

Making adaptive UI layout in Pixi.JS easy with DOM

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.
Let's use DOM, the only piece of HTML5 technology that works on every browser. We will need to add a div with position: absolute above our canvas.

Say you have three areas in your game where you want to place some game content:

Grid Layout

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 react-pixi-fiber and react-pixi will help you with that.

Second, transform layout into HTML.

    <div class="wrapper">
      <div class="box" id="buttons-left">Left Controls</div>
      <div class="box" id="game">Frame</div>
      <div class="box" id="buttons-right">Right Controls</div>
    </div>   
Enter fullscreen mode Exit fullscreen mode
.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%;
}

Enter fullscreen mode Exit fullscreen mode

Example on CodePen with default CSS styles removed https://codepen.io/schmooky/pen/jOpNxqR

Now we have to make a component that looks at bounding client rect of div to determine screen position.

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<ObserverContainerProps> = (
  props: ObserverContainerProps,
) => {
  const { target, visible, defaultWidth } = props;

  const containerRef = React.useRef<PixiContainer>(null);

  const ref = useRef<HTMLElement>();
  const [bbox, setBbox] = useState<Partial<DOMRect>>({});

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

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

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

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

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

  return (
    <Container
      ref={containerRef as any}
      visible={visible}
      x={bbox.left + bbox.width / 2}
      y={bbox.top + bbox.height / 2}
      scale={new Point(scale, scale)}
    >
      {props.children}
    </Container>
  );
};
Enter fullscreen mode Exit fullscreen mode

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

And, since dom elements are positioned in the same area of the screen as their canvas coutnerparts, Cypresscan provide you with smooth e2e testing experience.

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 Math.max or Math.min, or even add MutationObserver to look at more delicate attribute changes based on media query.

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more