FYI.

This story is over 5 years old.

Tech

Hack This: A Short Guide to Drawing All Over the Internet

Making cool visuals in JavaScript is easier than you think.
Image: Andru+en+I/Flickr

Over the past few months I've been screwing around a lot with Javascript drawing libraries for a software/research project that involves displaying hierarchical trees (family trees, archetypically) that don't look like total shit. Which turns out to be a hard problem that computer scientists have been wrestling with for about as long as they've been able to draw graphics on screens (50-ish years) and have still only solved imperfectly.

Advertisement

While working on this has involved a great deal of hair pulling, it also involves a lot of P5.js, which is way fun. P5 is a relatively recent addition to the Javascript drawing scene and counts as the JS implementation of the Processing programming language—itself a highly popular venue for creating dynamic digital art and cool visualizations. Like Processing, P5 is a really good time just out of the box and easy to get into. Unlike Processing, P5 is built for the internet and is implemented in an internet language whose kind of mushy peculiarities and syntactical leniency tend to work well for what people like to call "creative coding."

This, friends, is animation

In other words, in about 30 minutes you could be posting your creations to the internet for all to see. So, let's do that. Note that this will be easier overall than the previous Python-based Hack This tutorials, but will still involve some basic, basic coding.

Prerequisites: Like Processing, P5 has its very own code editor "sketchpad" thing, which is new and still fairly limited. Its basic utility is to quickly and automatically render P5 sketch files on a locally-hosted web page. By default, these pages are then viewed via the P5 editor's own little built-in browser. Let's start just by having the P5 editor downloaded and installed on your system. Note also that you can access tons of examples under the file menu.

0) Draw a shape, any shape

Before actually drawing anything, let's make a stop over at the P5 API. Here you'll find everything that P5 actually does. These are all functions that enable all of the cool stuff that P5 can do. This includes drawing, but also lots of stuff with sounds, images, video, and user interaction. Some things are more obvious than others, and some of those that seem obvious are or can be very complex. Sometimes it just depends on the context.

So, pick a shape. I'd start with a circle (ellipse) or rectangle (rect). Click the link in the P5 API and you'll get some examples and a nice page of documentation—a quick guide for using that particular shape (or other P5 function). Here, let's assume you picked an ellipse to draw and now we can head back over to the P5 editor.

Advertisement

If you look at the documentation page for an ellipse—which you should do—you'll see that it requires four "parameters." This is information that we have to pass to our ellispse about itself or about how it should be drawn. These are intuitive enough. The first two are x and y coordinates giving the location of where we want the ellipse to be drawn, and the last two correspond to the height and width of the ellipse we want to draw (if the height equals the width, we have a circle, obviously).

In just a second I'll get into the layout of a P5 sketch in some more detail, but for now we're going to include our ellipse in between the brackets following the "setup" function:

function setup() { ellipse(200,200,50,50); } function draw() { }

Cool, now click the play button and see what happens.

Nothing! Nothing happened. That's because we tried to draw something but don't have anything yet to draw onto. Let's change that by calling the createCanvas() function, which we need to give two parameters corresponding to the desired size of our drawing area. Almost every time we make a P5 sketch this is something we need to do and createCanvas() should only be called once per sketch or else things can get pretty messed up.

Do this and press play:

function setup() { createCanvas(720,720); ellipse(200,200,50,50); } function draw() { }

Cool circle, dog.

0.5)

A note about coordinates

The coordinate system in P5 (and HTML in general) is funky. Rather than having the (0,0) origin of a two-dimensional system be in the bottom left as we usually see it out in the world, the origin is at the top left. Consequently, as we go from the top of a page to the bottom, the y coordinate counts upward. This can get confusing.

Advertisement

1) The canvas

There are two fundamental elements of a P5 sketch, which can just be considered defaults. As we've already seen, these are draw() and setup(). What they do is simple. When a sketch runs (when we hit the play button in the editor or when it's called via some other method) everything within the setup() block of code runs one time and only one time. This is where we do all of our initialization, such as creating the canvas but really any number of other things too.

Let's pause just a moment to look at something cool. You can look at the P5 code under "JS," though it probably won't make too much sense yet. That's fine. Note here that you can also make and run P5 sketches online at, yep, Codepen.

"Lerping Triangle" by Varun Vachhar

In a P5 sketch, the draw loop is where the action happens. In the example above, we put our ellipse() within setup() because it only needs to run once. It's drawn to the page and that's it. It doesn't change and, within setup(), it can't change because it's just drawn the one time.

The draw loop is different. Once our sketch is launched, draw() executes continuously. We can expect the code within to be executed about 60 times per second, but this is far from a guarantee and will depend greatly on what the system on which it's running can provide.

Running code continuously in a loop means that we can program things that change over time. Each execution of a draw loop represents a frame and there are any number of ways that we might write code that changes its output from frame to frame. This, friends, is animation.

Advertisement

1.5) A note about functions

This is a tricky concept, but we need to get it out of the way.

Generally, in computer programming a function (or method or procedure, alternatively) can be understood as a self-contained block of code that can be written and then stashed in one location somewhere and then referenced elsewhere by name. It represents some unit of functionality that will likely be used more than one time. To use a pre-written function, we have to follow its rules. It may require us to give it some parameters (or input data) of a certain type, for example. And, in exchange, the function may return a specific piece of data or it might change the state of something in a program. It might just be used to output some bit of information to the screen or to gather some information from a file or keyboard or trackpad.

Some functions are written and then used only within the context of a single computer program, but others may be part of huge APIs used by millions of programmers. This latter, centralized way of doing things saves work, obviously, but also guarantees some amount of consistency across programmers and programs. (It's also the soul of open-source software.)

When we made our ellipse above, we used a function. That's what ellipse() is. We give it four items of data and it takes that data off somewhere and returns a nice two-dimensional shape. I don't know how exactly it does this, but it's useful to me to not have to care.

Advertisement

The ellipse-making function is a funny thing though. To say that it "makes" an ellipse is not quite right. I mean, it does, but the ellipse is also an entity itself, in a sense. When we make an ellipse in P5, the ellipse() function spits out an ellipse object. This object counts as a specific instance of the function we called to make it. So, it's as if the function produces the specific ellipse on our page but is also the blueprint for all ellipses.

You'll see this a lot in JavaScript: Blueprint-like "objects" that are also functions. In other words, functions may be performing some task in the usual sense of a function, but they might also be representing an abstracted entity or "thing." It's weird, but winds up making sense.

2) Animation, finally

So, we have a circle and it's pretty boring. We should probably make it do something. Let's start by moving our circle from where it is right now in the setup() section of the sketch to the draw() section. Now, setup() has the createCanvas() function and draw() has the ellipse().

Go ahead and run it. Great … the same circle.

The sketch is actually redrawing the circle something like 60 times per second, but you can't see it because it's just redrawing the same stupid circle in the same place. To make the circle move we need to change stuff about it over time. For this, we need variables.

A variable is simply a name we can declare as some unit of data, like a number or string of characters. We can take that variable name and reassign its values, do computations with it, and, crucially, we can use it in many places at the same time, though it may take on different values at different points in the execution of our sketch-program-thing.

Advertisement

Let's illustrate this by making our circle, but instead of just plugging a number in for the x coordinate parameter, we give it a variable name. To do this, we have to first declare the variable, which in Javascript just means saying "var some_variable_name." Without the "var," JavaScript won't see a variable and will instead just see some garbage and give you a bunch of errors. Other languages will want a data type, but JavaScript is cool with just var.

Note that I'm declaring xCoordinate outside of both the setup() and draw() functions. This is for a couple of reasons. One is that declaring it "globally" means that both functions have access to it because variables declared within one function are generally not available outside of that function (outside of the scope of that function). It also means that the data I store in that variable persists through successive draw loops. If I had declared it within draw(), it would just keep redeclaring it as if it were a new variable and our data would be lost.

Try this:

var xCoordinate = 200; function setup() { createCanvas(720,720); } function draw() { ellipse(xCoordinate,200,50,50); }

Can you even believe that I made you draw the same fucking circle a third time? Here's a reward:

Jerome Herr

Now, let's get on with it. We need to change the value of xCoordinate across different frames, which means changing it through time. One really cheap way to do this is just to add some small amount to xCoordinate every time the draw loop executes. So, add this line to draw():

Advertisement

xCoordinate = xCoordinate + 3;

Click play and look at it go!

Let's give it a color. Let's give everything a color.

This is a bit weird because we don't give things in P5 colors directly. Instead, we call a function called fill() right before we call the actual shape-making function. You'll want to read about fill() here but for now just understand that I'm using it to assign a color to the ellipse. The color here is formatted in hexadecimal because that's what I have laying around, but fill() takes all sorts of different parameter types. Note that I'm also giving a color to the sketch background, which is happening in the setup() section because I don't need to keep declaring it 60 times every second.

var xCoordinate = 200; function setup() { createCanvas(720,720); background('#59747D'); } function draw() { xCoordinate = xCoordinate + 3; fill('#36B8E3'); ellipse(xCoordinate,200,50,50); }

I'm not very happy with that, though it's better than what we started with. For one thing, I'd like to not just lose the ellipse to the off-canvas void. This happens because xCoordinate just keeps counting up and eventually gets greater than the 720 pixel width of the canvas.

How do you suppose I did that with the color?

Let's try making our ellipse bounce. We'll do this by saying that if the ellipse's x-coordinate is greater than the width of the canvas then it should start going in the other direction. This means we have to subtract from xCoordinate instead of adding more to it. We'll use an "if" statement to say that if xCoordinate is greater than 720 pixels, we'll subtract three on every draw loop until xCoordinate is 0 and then we'll start adding three again.

Advertisement

We'll do this by adding another variable, which will represent the ball's speed and which we can change from positive to negative and back again as needed.

var xCoordinate = 200; var xSpeed = 3; function setup() { createCanvas(720,720); background('#59747D'); } function draw() { xCoordinate = xCoordinate + xSpeed; if (xCoordinate > 720){ xSpeed = xSpeed * -1; } else if (xCoordinate < 0){ xSpeed = xSpeed * -1; } fill('#36B8E3'); ellipse(xCoordinate,200,50,50); }

That's sort of trippy, but not quite right. We really want just one ball and, as things are now, we're drawing more and more of them on every loop. So, to draw one ball, we need to erase the previous ball. This is easy: We just need to move the background() function from setup() to draw(), so that every time draw() loops, the background is redrawn over the old balls. Try it.

That's still pretty damn basic. Let's upgrade again. Start by making variables for the y-coordinate and the y-coordinate's speed. We'll add the ySpeed to the y-coordinate every frame so it accelerates downward and then when it hits the "ground," it bounces back but decelerates as it does so.

var xCoordinate = 200; var xSpeed = 1; var yCoordinate = 10; var ySpeed = 3; console.log(ySpeed); function setup() { createCanvas(720,720); } function draw() { background('#59747D'); fill('#36B8E3'); ellipse(xCoordinate,yCoordinate,50,50); xCoordinate = xCoordinate + xSpeed; yCoordinate = yCoordinate + ySpeed; if (yCoordinate >= 720){ ySpeed = ySpeed * -.5; } else if (yCoordinate < 0){ ySpeed = ySpeed * -1; } if (xCoordinate > 720){ xSpeed = xSpeed * -1; } else if (xCoordinate < 0){ xSpeed = xSpeed * -1; } }

Now, that's actually something. Gravity!

This is the part of the tutorial where things could either go on for 5,000 words or I could cut you loose with a good taste of things and point you, again, to the P5 docs and examples for guidance. I'll also (highly) recommend Daniel Shiffman's YouTube series on P5. You've made a ball do some stuff, but there's a whole universe of particle systems, random walkers, and well beyond left to explore.