Fitting Canvas Snow In a Tweet
Tags: html, javascript, codegolf, twitter
Published: 2021-02-08 17:31:29

Last year before Christmas, I created this snowfall animation in 257 bytes:

![Output of the code](https://dev-to-uploads.s3.amazonaws.com/i/hbbi8c3tfoqenb9ahj88.gif)

It’s not just the JavaScript portion but the entire source, and it fits well in Twitter’s 280-character limit. If you haven’t already checked out my 278-byte [Flappy Bird](https://dev.to/jonkantner/fitting-flappy-bird-into-a-tweet-484h), I’ll demystify this snowfall in a similar fashion.

## The Code

Here’s the whole code with line breaks, tabs, and spaces for better readability.

```
<body onload="
    c = C.getContext('2d');
    R = Math.random;
    H = 164;
    F = [];
    f = 99;
    while (f--)
        F[f] = {
            y: R() * H,
            s: R()
        };
    setInterval(`
        c.fillRect(x = 0,0,300,H);
        for (f of F) {
	    c.font = 28 * f.s + 'q a';
	    f.y = f.y % H + f.s;
	    c.fillText('*',x,f.y);
	    x += 3;
	    c.fillStyle = x > 294 ? '#000' : '#fff'
        }
    `,9)
">
<canvas id=C>
```

Using a single letter for every variable is essential for writing minimal code. In addition, JavaScript minifiers often do this and should be to help reduce script size when talking web performance.

## Tags and Attributes

```
<body onload="">
<canvas id=C>
```

All the HTML we’ll ever need here is an opening `<body>` and `<canvas>` tag. Since no other elements will follow, we can omit the closing tags. As a result, we save 16 bytes (7 for `</body>` and 9 for `</canvas>`).

Since JavaScript lets us to use element IDs as variables, I used only `C` here for the canvas. Although attributes of one value may not require quotes, we still need them for the body’s `onload` because having `'2d'` in `.getContext('2d')` as a value in quotes, the browser will treat all else before and after it as separate attribute-value pairs. Then some `=`s will become invalid HTML syntax.

## Context, Main Variables, and Snowflake Array

```
c = C.getContext('2d');
```

As mentioned earlier, we can use the canvas ID like a variable; using `document.getElementById('C')` or `document.querySelector('#C')` would just rack up the expense.

```
R = Math.random;
```

Since we’re using `Math.random()` more than once for randomizing the starting snowflake positions, we can assign `R` the function. Then we’d save 10 bytes per use of `R()`!

```
H = 164;
```

`H` is the height of the canvas (150) plus enough space (14) past the bottom so that when snowflakes are moved back to the top, it doesn’t look like they magically disappear.

```
F = [];
f = 99;
while (f--)
    F[f] = {
        y: R() * H,
        s: R()
    };
```

The last chunk of code before the `setInterval()` is where we generate 99 snowflakes as well as determine their starting y-positions (`y`) and speeds (`s`). For the random values, we use our byte-saving `R()` function. Rather than using a `for` loop of any kind and `.push()` to supply the `F` array, we can use fewer bytes with a `while` loop with a counter starting at 99. Then we can append each snowflake object using `F[f]=`, which is 3 bytes cheaper than `F.push()`.

## The Animation Loop

```
setInterval(`
    c.fillRect(x = 0,0,300,H);
    for (f of F) {
        c.font = 28 * f.s + 'q a';
        f.y = f.y % H + f.s;
        c.fillText('*',x,f.y);
        x += 3;
        c.fillStyle = x > 296 ? '#000' : '#fff'
    }
`,9)
```

The `setInterval()` here is where all the logic and drawing happens. Although I don’t recommend doing it anyway due to [security concerns](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval#parameters), you can supply the whole function as a string or template literal. 


```
c.fillRect(x = 0,0,300,H);
for (f of F) {
    . . .
}
```

While filling the screen black by default for the first statement, we set `x` equal to 0 for the x-position of the first snowflake. In the `for...of` loop contents we’re about to explore, the `x` will come in handy for horizontally placing the snowflakes without using too many bytes, and we’ll see how later.

```
c.font = 28 * f.s + 'q a';
```

In the first statement in the `for...of` loop, we set a maximum font size of `28q`. I normally don’t use the `q` unit, but it happens to be great for code golfing because every other unit is at least a character longer. Then multiplying the font size by snowflake speed (`f.s`), we’ll get the visual depth we want. The smaller the snowflake, the further away and slower in movement it’ll appear. By the way as you’ve seen earlier in the whole chunk of code, the asterisk (*) will be the snowflake. Then as part of getting the desired shape, we can use `a` as a single-letter fake font family that will be rendered as the browser default. If we were to leave it out in attempt to save 2 more bytes, however, the snowflakes would wind up as dots.

```
f.y = f.y % H + f.s;
```

The next statement moves each snowflake downward. By using `f.y % H`, each snowflake will be kept in its 0-164 range when its `y` finally exceeds `H` through adding `f.s`.

```
c.fillText('*',x,f.y);
x += 3;
```

Once having the needed position, we throw in the snowflake itself. After that we evenly space out all 99 snowflakes as possible across the canvas by dividing the default canvas width of 300 by 99 and rounding the result, which will be 3. That’s what we use to increment `x`. The reason we did not give snowflakes their own `x` property is that obviously more bytes will be wasted if we used `f.x`. So that explains our use of `x` in the `.fillRect()` from earlier.

```
c.fillStyle = x > 294 ? '#000' : '#fff'
```

The last statement in the loop determines when to switch fills. Since the last snowflake will be located at `x` = 294 ((`x` increment of 3) × (99 snowflakes) - (`x` increment of 3)), we can set the fill black for the background when `x` is finally greater than it.

```
setInterval(`
    . . .
`,9)
```

Finally for the sake of cutting bytes, a single-digit interval of 9 milliseconds sets a comfortable animation speed.

## Conclusion

There you have it, a wintery scene in about a quarter of a kilobyte! The key parts of this have been:

* Turning a built-in JavaScript function `Math.random` into a single letter if using more than once
* Changing how we may normally populate arrays
* Not using a certain property if a pattern can be determined without it (`x`)
* Tolerating defaults

{% codepen https://codepen.io/jkantner/pen/OJRxNyN default-tab=html,result %}