Optimizing Scalable UI Elements with Pixi.js NineSlicePlane
Transitioning from Unity to Pixi.js, one noticeable absence was the sliced sprites, especially when working on resizable UI elements like progress bars. Thankfully, Pixi.js offers a comparable component known as NineSlicePlane
, which resembles Unity's sliced sprites in function. Although there are few resources and examples available for NineSlicePlane
, this article aims to delve deeper into its behavior and outline how it aligns with my expectations.
TL;DR: The code used in this article is available on CodePen.
Usage
Let's consider the example of a progress bar. First, using Pixi's Sprite
:
<Sprite
image="progress_bar.png"
width={103}
height={44}
/>
In this instance, I'm employing ReactPixi for code readability, setting the sprite's dimensions to the exact texture size (103px × 44px).
Using NineSlicePlane
is quite straightforward. We merely specify the width and height of both sides in pixels on the texture, effectively dividing the texture into nine segments. In this example, we use 20px width for both sides and 0px height (extending the progress bar horizontally):
<NineSlicePlane
image="progress_bar.png"
leftWidth={20}
topHeight={0}
rightWidth={20}
bottomHeight={0}
width={240}
height={44}
/>
This approach works well, allowing us to easily adjust the width to achieve the desired length for the progress bar.
Issue - Scaling
On occasion, we might need to scale up the width and height of the progress bar, making it fit within its parent container. For example, if we attempt to double the width and height:
<NineSlicePlane
image="progress_bar.png"
leftWidth={20}
topHeight={0}
rightWidth={20}
bottomHeight={0}
width={480}
height={88}
/>
Drawing from Unity's experience, I anticipated that this should work, and Pixi should simply scale the NineSlicePlane
's size by a factor of 2. However, the edges do not extend as expected. In fact, NineSlicePlane
maintains the length of the edges, which aligns with its document behavior:
areas 1 3 7 and 9 will remain unscaled
For further experimentation, what if we also scale up the edge options?
<NineSlicePlane
image="progress_bar.png"
leftWidth={40}
topHeight={0}
rightWidth={40}
bottomHeight={0}
width={480}
height={88}
/>
This adjustment doesn't alter anything or even results in more distortion depending on the texture. This is because the edge sizes should be relative to the texture sizes, not the actual canvas sizes.
Solution 1 - Adjust Texture Size
One workaround is to make the texture pixel-perfect, where texture sizes always match the sprite canvas size. In the example above, if we want to double the sprite canvas size, we must double the progress bar's texture size as well. This makes the previous code work as expected.
From an artistic standpoint, this is a viable solution as scaling up the sprite in the canvas without scaling up the texture can lead to pixelation.
However, this solution is not scalable when we need various progress bar sizes, depending on the parent's container or different sizes for desktop and mobile. It's suitable only for fixed-size progress bars.
Solution 2 - Utilize scale
Similar to other Pixi components, NineSlicePlane features a scale option that allows us to achieve the desired scaling:
<NineSlicePlane
image="progress_bar.png"
leftWidth={20}
topHeight={0}
rightWidth={20}
bottomHeight={0}
width={240}
height={44}
scale={[2, 2]}
/>
However, this approach requires calculating the scale every time, if we only know the desired length:
scale = widthInCanvas / WIDTH_IN_TEXTURE
A tradeoff here is that we cannot use the scale
option in other scenarios. For instance, if we want a button within a NineSlicePlane
to scale up or down when clicked, we'll need to wrap the button in another Container
.
Summary
NineSlicePlane
operates somewhat differently from Unity's sprite, necessitating some special considerations. Nevertheless, it remains a functional solution that we can easily work with. Once again, the example code is available on CodePen. Peace!