When I was first learning to code in grade school, I learned how make some very simple graphics using QuickBASIC. One of my first learning exercises was to draw random dots and lines on the screen at a stunning resolution of 320 x 200 in a magnificent 256 colors. Later, I would also make some "art" with overlapping gradients, mathematical patterns, etc..
In the modern(-ish) day, I haven't really spent much time learning how to make graphics in the browser. Even Shooty Ship, for example, is made entirely with hand-drawn graphics and DOM Elements. (Hard to believe. I know.)
So, it's finally time to repeat this experiment with the Canvas API.
Naturally, I'll start by creating the most important component: The monitor, which I dub the "Interweb VGA".
<svg
style='width: 100%; height: auto;'
viewBox="0 0 370 250"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Monitor body -->
<rect x="0" y="0" width="370" height="250" rx="10" ry="10"
fill="#f5f5dc" stroke="#bfbfa3" stroke-width="3" />
<!-- Screen -->
<rect x="25" y="25" width="320" height="200" rx="5" ry="5"
fill="#000" stroke="#000" stroke-width="2" />
<!-- Brand Name -->
<text x="265" y="240"
font-family="monospace" font-size="10" fill="#555"
>Interweb VGA</text>
<!-- Knobs, Buttons, and Light -->
<circle class='powerButton'
cx="55" cy="236" r="4" fill="#888" />
<line class='powerButton'
x1="55" y1="234" x2="55" y2="238"
stroke="#555" stroke-width="1" />
<circle cx="70" cy="236" r="4" fill="#888"/>
<rect x="35" y="232" width="8" height="8" rx="1" fill="#6a6"/>
<!-- FullScreen Button -->
<text class="fullscreenButton"
x="348" y="239" font-size="11"
style="cursor: pointer;" fill="#999"
>⛶</text>
</svg>
Without anything on it, it looks like this:
After laying a canvas over the monitor SVG, I'll add some methods to wrap the Canvas API. This will just make drawing lines and pixels a little simpler. (Strictly speaking, we won't draw individual "pixels" on canvas; we'll draw 1x1 rectangles). The raw methods will come in handy when I play with image manipulation later; but, for my little experiment, these "pixel" (or point) and "line" abstractions are sufficient.
Those extension methods look lke this:
draw: {
get context() {
ctx = ctx ?? self.data.canvas.getContext('2d')!;
return ctx;
},
point(x: number, y: number, color: string) {
// points are colors by drawing a 1x1 rectangle
const ctx = this.context;
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
},
line(x1: number, y1: number, x2: number, y2: number, color: string) {
// lines are made by walking a path and stroking it with color
const ctx = this.context;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
},
}
Nothing here is terribly complicated. This "draw" object will be attached to a component whose "canvas" property is an HTML canvas. And, I'll use the component's onadd()
callback to start drawing as soon as the component is added to the DOM. But, before getting to the demo code, let's also cover a couple utilities for generating random coordinates and colors. They're pretty small, but I they would be verbose and ugly enough in-line to warrant being separate utils.
function randomCoord({ context }: { context: CanvasRenderingContext2D }) {
return [
Math.random() * context.canvas.width,
Math.random() * context.canvas.height
];
}
function randomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
Again, these aren't complicated. But, these adapters make drawing things pretty simple. The "random dots" program becomes very simple.
${DosMonitor().onadd(self => {
setInterval(() => {
const coord = randomCoord(self.draw);
const color = randomColor();
self.draw.point(coord[0], coord[1], color);
}, 5;
}
It looks like this.
Producing random lines is about as simple. I'll make the lines interconnect as they're drawn, which increases the complexity slightly. But, even with this small added complexity, it's still pretty simple.
${DosMonitor().onadd(self => {
let lastCoord = randomCoord(self.draw);
setInterval(() => {
const nextCoord = randomCoord(self.draw);
const color = randomColor();
self.draw.line(lastCoord[0], lastCoord[1], nextCoord[0], nextCoord[1], color);
lastCoord = nextCoord;
}, 100);
})}
It ends up looking like this.
Now, when I did this originally in grade school with QuickBASIC, I was learning the absolute fundamentals of programming at the same time. I am now arguably a well seasoned "senior" engineer. Some of the abstractions, though maybe not 100% necessary, came very naturally to me. Algorithmically, there's nothing at all to speak of. But, for a grade school brain, I would have struggled a bit to connect some of the dots. (See what I did there?)
Today, I also work in a sea of engineers. My Twitter X and LinkedIn feeds are flooded with programming and software engineering threads, opinions, and technical rants. We have LLM's that can code nearly anything in an instant — bugs included! Coloring a few pixels on the screen is pretty unimpressive. (Within the last few months, "vibe coders" on Twitter have even created full multi-player flight simulators in a matter of days.)
So, perhaps it's needless to say, but, small adventures like this are very different endeavors now. They no longer feel quite so novel and exciting. But, taking a break from the day-to-day routine of writing complex TypeScript libraries, CI/CD pipelines, and operational dashboards to draw a few pixels and lines on a nostalgic looking "Interweb VGA" has been ... nice.
If you also like writing code, but find that your day job requires you to argue more about whether to use a for-in
or a .forEach()
or fix broken deployments ... I encourage you to take a break.
Take a break to write some code you don't have to debate in a pull request. Take a break to write some code that won't get stuck in a failing deployment pipeline. Take a break to write some code that you don't actually need monitoring and alarming for.
Take a break.
Take a break to draw some pixels on an "Interweb VGA", for example.
You don't need to draw the same pixels I did. Just make some pixels happen. Unnecessary pixels. Playful pixels.
Take a break.
Play.
If you enjoyed anything about this article, subscribe so you don't miss out!
Published: 4/11/2025.
Topics: html, typescript, javascript, canvas, graphics, retro, play