If you want to draw a small pixel style icon on the web, what are your ideas? There are many ways to use Canvas or make SVG alone. What if you limit your methods to CSS only? In case you didn’t think of it, box-shadow can do this.

For those unfamiliar with CSS, box-shadow’s job is to shadow the DOM. According to MDN, it is:

The CSS box-shadow property is used to add a shadow effect to the frame of an element. You can set multiple shadow effects on the same element and separate them with commas. The values you can set for this property include the X-axis offset, Y-axis offset, blur radius, spread radius, and color of the shadow. – the MDN document

Normally, a box-shadow is the shadow that highlights the view ahead, but this time we’re going to bring it to the front as the main character. The transformation only takes 3 steps:

  • Fix the front DOM as a square so that each shadow becomes a square (example1 below);
  • Set the offset of the shaded area to an integer multiple of the length of the foreground side, and the shaded area is fully displayed (example2 below);
  • You can create complex shapes like paving tiles by drawing different colors of shadows at different offsets as needed (example3 below);

In this way, we can use box-shadow to draw pixels.

In fact, the famous NES.css on Github does just that. Here are some pixel ICONS provided in Nes.css:

To see how nes.css uses box-shadow for these lovely PIXEL paintings, I’ve simplified the code and extracted it in zhuzilin/ Pixel.css. Nes.css uses the SASS preprocessor to introduce variables, control flows, functions, and so on. For drawing a Mario:

@use 'pixelize.scss';

.pixel-mario {
  // Specify which colors Mario needs
  $mario-colors: (#f81c2f.#65352b.#ffbb8e.# 000.#1530ad.#aeaeac.#fef102);
  // What color does Mario need on each grid
  $mario: ((0.0.0.0.0.1.1.1.1.1.0.0.0.0.0.0),
    (0.0.0.0.1.1.1.1.1.1.1.1.1.0.0.0),
    (0.0.0.0.2.2.2.3.3.4.3.0.0.0.0.0),
    (0.0.0.2.3.2.3.3.3.4.3.3.3.0.0.0),
    (0.0.0.2.3.2.2.3.3.3.4.3.3.3.0.0),
    (0.0.0.2.2.3.3.3.3.4.4.4.4.0.0.0),
    (0.0.0.0.0.3.3.3.3.3.3.3.0.0.0.0),
    (0.0.0.0.0.1.5.1.1.5.1.0.0.0.0.0),
    (0.0.0.1.1.1.5.1.1.5.1.1.1.0.0.0),
    (0.0.1.1.1.1.5.5.5.5.1.1.1.1.0.0),
    (0.0.3.3.1.5.7.5.5.7.5.1.3.3.0.0),
    (0.0.3.3.3.5.5.5.5.5.5.3.3.3.0.0),
    (0.0.3.3.5.5.5.5.5.5.5.5.3.3.0.0),
    (0.0.0.0.5.5.5.0.0.5.5.5.0.0.0.0),
    (0.0.0.2.2.2.0.0.0.0.2.2.2.0.0.0),
    (0.0.2.2.2.2.0.0.0.0.2.2.2.2.0.0));// The size of a single cell
  $size: 20px;

  position: relative;
  display: inline-block;
  // The overall size
  width: $size * length(nth($mario.1));
  height: $size * length($mario);

  &::before {
    position: absolute;
    top: -$size;
    left: -$size;
    content: "";
    background: transparent;

    @include pixelize.pixelize($size.$mario.$mario-colors); }}Copy the code

For those unfamiliar with SASS, @use is similar to javascript import, used to import variables and functions defined in other.scss files. When used, @include is used to indicate that you are calling a function defined elsewhere. The key thing in this SASS code is this function call:

@include pixelize.pixelize($size.$mario.$mario-colors);
Copy the code

Before taking a look at what this function does, let’s take a look at what its arguments and other auxiliary CSS are for.

To draw a pixel art, you need three things: size, pattern, and color. $size, $Mario, and $Mario-colors are the corresponding variables. One syntax point here is that in SASS, everything that starts with $is a variable. $size indicates the size of each grid; $Mario is a 2-dimensional array that indicates which color should be chosen for each position of Mario’s pixel art, with 0 indicating transparency; $mario-color specifies the color of the number in $Mario, for example, 1 corresponds to # f81C2f.

With these three things defined, NES.css draws a pixel pattern by constructing a box-shadow on ::before. The parent DOM of the pseudo-element only needs to set the overall size of the whole pattern. Here we use some array functions inside SASS, meaning that the width of the row is $Mario times the length of the cell, and the height is $Mario times the length of the cell:

width: $size * length(nth($mario.1));
height: $size * length($mario);
Copy the code

We’re going to come back to some of the functions that we’re going to use here, so we don’t need to go over them here.

Inside the pseudo-element, basically is to set some positioning information, and then is to set the content of the pseudo-element as an empty string, the color is transparent, so as to ensure that there is no block in front of the color shadow. & is also SASS syntax, used to support CSS nesting, so &::before is equivalent to. Pixel – Mario ::before.

  &::before {
    position: absolute;
    top: -$size;
    left: -$size;
    content: "";
    background: transparent;

    @include pixelize.pixelize($size.$mario.$mario-colors);
  }
Copy the code

With that done, we need to pass three parameters to the pixelize function to construct a box-shadow. Here’s what pixelize does:

@mixin pixelize($size.$matrix.$colors.$default-color: null) {
  $ret: "";
  @if ($default-color == null) {
    // use the color with the most occurrences as default-color
    $matrix-colors: ();
    $counts: ();
    @each $row in $matrix {
      @each $item in $row {
        @if $item! =0 {
          $index: index($matrix-colors.$item);
          @if not $index {
            $matrix-colors: append($matrix-colors.$item);
            $counts: append($counts.1);
          } @else {
            $count: nth($counts.$index) + 1;
            $counts: set-nth($counts.$index.$count); }}}}$default-color: nth($colors, nth($matrix-colors, index($counts, max($counts...). ))); }// Splice the displacement and color in box-shadow according to the position
  @for $i from 1 through length($matrix) {
    $row: nth($matrix.$i);

    @for $j from 1 through length($row) {
      $dot: nth($row.$j);
      @if $dot! =0 {
        @if $ret! ="" {
          $ret: $ret + ",";
        }
        $color: nth($colors.$dot);
        @if $color= =$default-color {
          $ret: $ret + ($j * $size) + "" + ($i * $size);
        } @else {
          $ret: $ret + ($j * $size) + "" + ($i * $size) + "" + $color; }}}}width: $size;
  height: $size;
  color: $default-color;
  box-shadow: unquote($ret);
}
Copy the code

@mixin can be understood as a function in SASS, but instead of returning arguments, it generates internal CSS directly from the input. For those unfamiliar with SASS, this function may look a bit confusing, but it actually involves some simple control flow and array operations. If translated into javascript, it looks like:

function pixelize(size, matrix, colors, default_color = null) {
  ret = "";
  if (default_color === null) {
    // use default_color as the color with the most occurrences
    matrix_colors = [];
    counts = [];
    for (row in $matrix) {
      for (item in row) {
        ifitem ! =0 {
          index = matrix_colors.indexOf($item);
          if index == -1 {
            matrix_colors.push(item);
            counts.push(1)}else {
            count = counts[index] + 1;
            counts[index] = count;
          }
        }
      }
    }
    default_color = colors[matrix_colors[counts.indexOf(Math.max(... counts))]]; }// Splice the displacement and color in box-shadow according to the position
  for (i = 1; i <= matrix.length; i++) {
    row = matrix[i];
    for (j = 1; j <= row.length; j++) {
      dot = row[j];
      ifdot ! =0 {
        ifret ! ="" {
          ret = ret + ",";
        }
        color = colors[dot];
        if color == default_color {
          ret = ret + (j * size) + "" + (i * size);
        } else {
          ret = ret + (j * $size) + "" + (i * size) + ""+ color; }}}}return `width: ${size};
          height: ${size};
          color: ${default_color};
          box-shadow: ${ret}; `
}
Copy the code

When the two pieces of code correspond, it is easy to see what the functions in SASS mean. For example, NTH (a, k) is a[k]. Pixelize is essentially a loop that calculates the CSS for each shadow based on the array of patterns and colors that are passed in, and then concatenates the final box-shadow property.

With these CSS in place, ICONS can be added to a web page in this way:

<i class="pixel-mario"></i>
Copy the code

With this in mind, we can also add CSS for hover, using different pixels for hover, and even animation:

(If you are interested in this little animation, you can see here ~)

That’s the end of this article. If you find this usage interesting, give it a thumbs up