I’ve recently redesigned my website to be more pixel art focused. Here are some tricks I’ve used that are quite nice for creating such a distinct design.
Upscaled Sprites
One problem with rendering pixel art on the web is that of bilinear scaling. To prevent this, I export all assets at 8x size (e.g. a block of 8x8 pixels is one “original pixel”). You can do this easily in Aseprite by going to File > Export > Export As…
It might work to set CSS image-rendering to pixelated, but I have not yet experimented with this!
Nine Slice Scaling
Also known as your nine patch rects in Godot, or your 9-slicing in Unity. The basic idea is that we want to stretch/tile the borders and fill of an image, without distorting resolution. This is especially important for pixel art, since we want to preserve our pixel scale.
CSS provides a convenient property known as border-image, which we can leverage to do nine slice scaling.
1
2
3
4
5
6
.window-card {
border: 32px solid transparent;
border-image: url("/assets/images/ninepatch.png") 128 fill;
box-sizing: border-box;
background-color: transparent;
}
The border pixel size is based on the real pixel width of the nine slice’s border. That is, how big it should be in the browser.
Meanwhile, the number parameter for border-image indicates the pixel size from the edge of the rect that should be sliced. In my case, I have 16 pixel wide borders on an image scaled 8x, so I use 16 x 8 = 128 pixels in the browser. The url indicates the asset for the nine slice and the fill indicates that we want to stretch the inside portion to act as the background.
Finally, setting box-sizing to border-box means that any defined width/height includes both padding and border, instead of the padding and border being added on top of the width/height. This simplifies figuring out the final width/height of objects will be. Unfortunately, this still keeps the content at border + padding distance from the edge.
To address this, we can add a negative margin to the content div inside the nine slices to push outwards toward the border. Depending on your nine patch slice, you may want different margins for different directions. Here is what I use for my window sprites:
1
2
3
4
5
6
.window-card-content {
margin-left: -22px;
margin-right: -22px;
margin-top: -16px;
margin-bottom: -22px;
}
Since the left, right, and bottom edges are visually made of 5 “original pixels” out of 16 original pixels, I would have to subtract 11/16 of the border width. The border width in the browser is 32px, so I subtract 22 pixels from these sides. Similar logic holds for the top margin.
Discrete Keyframed Animations
Pixel art animations are often made using discrete frames. However, CSS animations typically allow you to define keyframes that are interpolated by the browser. We want to avoid this interpolation so that we can define discrete steps for a float animation or a hover animation. How can we do this?
The solution is to use the CSS keyframes logic but to hold the keyframes for their entire duration.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Float up with overshoot, */
@keyframes discrete_up {
0% {
transform: translateY(0px);
}
1%,
33% {
transform: translateY(-4px);
}
34%,
66% {
transform: translateY(-7px);
}
67%,
100% {
transform: translateY(-5px);
}
}
Here, we incorporate some ease and anticipation by splitting up the animation into four “frames”, where we hold a position for the duration of each frame.
And so?
Pixel art is a natural fit for the websites of, well, pixel art games. It “extends” their pixel design languages through to their meta elements. Notable games that come to mind include Neverway, and the train animation at the bottom of Loco Motive. Of course, it may not be ideal to make everything pixel art (for legibility and content cohesion reasons), but the aesthetic can certainly help differentiate against being yet another modern website.