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:
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>
.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%;
}
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>
);
};
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.
Top comments (0)