Nine slice modal
9/11/2024
Kaloyan Geshev
Creating a 9-sliced image is a powerful technique that allows you to set custom borders for an element, ensuring the borders maintain their correct size when the element is resized. This feature is particularly useful when designing game UI modals that contain dynamic content. With 9-slice scaling, the edges of the border remain intact while the other parts scale accordingly as the modal changes size.
Showcase Overview
In this tutorial, we’ll create an animated modal with dynamic content using an image for the border, the border-image
CSS property to slice the image, and CSS transitions for animation effects.
Source location
You can find the complete sample source within the ${Gameface package}/Samples/uiresources/UITutorials/NineSliceModal
directory.
Preparing the border image
First, we need to design the image that will serve as the modal’s border. Tools like Figma or any other image editor can be used for this purpose.
In this design, we want to preserve the top-left, top-right, bottom-left, and bottom-right corners of the border when the modal resizes, while scaling the sections marked with red arrows.
Once the border image is designed, export it as a PNG file.
Generating preview of the border image
Because the border-image
property can be complex and difficult to adjust by manually writing CSS, we’ll use an editor to preview the border image and generate CSS for us.
You can access the editor here .
Use the Upload image
button to load your border image.
Initially, the default settings may not look right, so we’ll need to adjust the values.
First, set the border-image-slice
property to define the 9-slice areas of the border that should either scale or be preserved.
Be sure to check the fill
option to avoid gaps in the middle of the modal. The preview might still show some issues, like the left and right borders appearing spliced.
To fix this, adjust the border-image-width
settings. Experiment with different values to achieve the best fit. In this example, we’ve set the following values:
Displaying the border image in our HTML page
After finalizing the settings in the editor and achieving the desired appearance, it’s time to display the border image in your HTML.
First, define the element that will display the modal:
Next, copy the border-image-slice
and border-image-width
properties generated by the editor.
Now we can apply these styles to our CSS file:
The result will look like this:
If you adjust the .modal
element’s width
or height
in the CSS, you’ll see that the edges of the border remain preserved while the other parts scale up or down, demonstrating how to do 9-slice in web using border-image
.
Enhancing the modal
We will enhance the modal functionality by allowing it to open when a button is clicked and dynamically update its text content.
Additional elements to the modal
First, we’ll introduce additional elements to the modal and wrap it with a container:
We added an “Open Modal” button, which will have a click
event listener to trigger the modal opening when clicked.
The modal is wrapped inside a modal-wrapper
, which helps center it on the screen and adds a black transparent background with a blur effect to obscure the content behind the modal. This keeps the user’s focus on the modal.
Inside the modal, we added a modal-title
, a modal-text
(which will be dynamically updated with different content), and modal-controls
that manage navigating through different text or closing the modal.
The modal-wrapper
is initially hidden using the visibility-hidden
class:
We set the background-color
and backdrop-filter
to zero when the visibility-hidden
class is applied, so the modal stays invisible. When this class is removed, a transition will be triggered:
We also stop pointer-events
when the modal-wrapper
is hidden, preventing any interactions while it’s not visible.
Adjusting the modal
We will modify some properties of the modal itself. Instead of a fixed width
, we’ll set it to 60% of the modal-wrapper
. We’ll remove the height
property so it can be dynamically adjusted via JavaScript, and update the border-image-width
to use vw
units for better responsiveness across different screen resolutions.
Additionally, we will add a transition
to the modal for both the transform
and height
properties. This ensures a smooth scaleY
transition when the modal opens, and a smooth height transition when the content inside the modal changes.
We adjusted the border-image-slice
and border-image-width
values to maintain a similar visual appearance as before. You may need to tweak these values using for example developer tools to get the desired look.
Lastly, we need to set the transform: scaleY(0)
when the modal is closed, so the opening transition works correctly:
Define modal messages
To modify the messages displayed in the modal, we first need to define them. We’ll create an array of strings to store these messages, and set up variables to track the current message index and the total number of messages for later use:
Open modal on button click
To open the modal, we’ll add an event listener to the “Open Modal” button.
Here we are removing the visibility-hidden
class from the modal-wrapper
, triggering the fade-in transition to make the modal visible.
Within the openModal
function, the first message from the modalTexts
array is displayed, and we disable the “Next” or “Prev” buttons based on the current message index using the changeControlsInteraction
function. We also adjust the modal’s size to fit the new content via the changeTextContentAndResize
function.
The key functions here are changeTextContentAndResize
and onModalHeightTransitionEnds
.
In changeTextContentAndResize
, the text is updated via the textContent
property. After the text changes, we calculate the new modal content height. To ensure the height is correctly calculated, we wait 3 frames before accessing clientHeight
(due to Gameface’s specific behavior). This delay is handled by the waitForFrames
method, which defaults to waiting for 3 frames but can be customized if needed.
Once the correct height is determined, the modal height is updated as follows:
This sets the modal’s height to the clientHeight
of its content plus the total top and bottom paddings (in total - 5vw).
Finally, we listen for the transitionend
event to trigger the fade-in effect by adding the modal-content-visible
class.
After that, the transitionend
event listener is removed inside the onModalHeightTransitionEnds
.
Updating the active text in modal
The Next
and Prev
buttons are responsible for cycling through the active text in the modal. We need to attach click
event listeners to both buttons to handle this functionality.
The text update is managed by the setModalText
function.
First, we verify that the index is valid (within bounds). Then we update currentTextIndex
with the new value and adjust the state of the Next/Prev buttons by calling updateControlState
.
Next, we trigger the fade-out transition for the current text content by removing the modal-content-visible
class. The transitionend
event listener is added to ensure that once the fade-out completes, we proceed with updating the text and resizing the modal to fit the new content using the onModalContentTransitionEnds
function.
The onModalContentTransitionEnds
function removes the event listener and calls the changeTextContentAndResize
function, which was previously defined for handling modal opening.
Closing the modal
The modal can be closed when the Close
button is clicked.
When the Close
button is clicked, the fade-out transition for the modal content begins by removing the modal-content-visible
class. After the content transition ends, we wait to apply the visibility-hidden
class to the modalWrapper
, which triggers the fade-out effect for the entire modal.
Making the modal height responsive
To make the modal height adjust based on window resizing, you can use a ResizeObserver
on the modal content element. This ensures that when the window size changes, the modal’s height is updated accordingly.