Advertisement
  1. Web Design
  2. HTML/CSS
  3. SVG

A Beginner's Guide to Drawing 2D Graphics With Two.js

Scroll to top

Two.js is an API that makes it easy to create 2D shapes with code. Follow along and you'll learn how to create and animate shapes from JavaScript.

Two.js is renderer agnostic, so you can rely on the same API to draw 2D with Canvas, SVG, or WebGL. The library has a lot of methods which can be used to control how different shapes appear on the screen or how they are animated.

Installation

The uncompressed version of the library has a size of around 128 KB, while the compressed version is 50 KB. If you are using the latest version, you can further reduce the size of the library using a custom build.

You can either download the minified version of the library from GitHub or you can link directly to the CDN hosted version. Once you have added the library to your webpage, you can start drawing and animating different shapes or objects.

Creating Basic Shapes

First, you need to tell Two.js about the element on which you want to draw 2D and animate your shapes. You can pass some parameters to the Two constructor to set things up.

Set the type of renderer using the type property. You can specify a value like svg, webgl, canvas, etc. The type is set to svg by default. The width and height of the drawing space can be specified using the width and height parameters. You can also set the drawing space to the full available screen using the fullscreen parameter. When fullscreen is set to true, the values of width and height will be disregarded.

Finally, you can tell Two.js to automatically start an animation with the help of the Boolean autostart parameter.

After passing all the desired parameters to the constructor, you can start drawing lines, rectangles, circles, and ellipses.

You can draw a line using two.makeLine(x1, y1, x2, y2). Here, (x1, y1) are the coordinates of the first end point, and (x2, y2) are the coordinates of the second end point. This function will return a Two.Line object, which can be stored in a variable for further manipulation at a later point.

In a similar manner, you can draw normal and rounded rectangles using two.makeRectangle(x, y, width, height) and two.makeRoundedRectangle(x, y, width, height, radius) respectively. Remember that x and y determine the center of the rectangle, instead of its top left coordinates like many other libraries. The width and height parameters will determine the size of the rectangle. The radius parameter is used to specify the value of the radius for the rounded corner.

You can also render circles and ellipses on a webpage using two.makeCircle(x, y, radius) and two.makeEllipse(x, y, width, height) respectively. Just like the rectangles, the x and y parameters specify the center of the circle or ellipse. Setting the width and height to the same value in the case of an ellipse will render it like a circle.

Arrows are also easy to create with the help of the two.makeArrow(x1, y1, x2, y2, size) method. The x1 and y1 values determine the position of the tail of the arrow. The x2 and y2 values determine the position of the arrowhead. The fifth parameter determines the size of the arrowhead.

There is a method called two.makePolygon(x, y, radius, sides) that you can use to create a regular polygon. The x and y values determine the center of the polygon. The radius determines the distance of the polygon's vertices from the center, while sides specify the number of sides in the polygon.

Manipulating Objects in a Group

One useful method in Two.js that you will use frequently is two.makeGroup(objects). You can either pass a list of different objects or pass an array of objects, paths or groups as the parameter to this method. It will also return a Two.Group object. After you have created a group, you can manipulate all its children at once using properties that the group makes available to you.

The stroke and fill properties can be used to set the stroke and fill color for all children in a group. They will accept all valid forms in which you can represent a color in CSS. This means that you are free to use RGB, HSL, or hex notation. You can also simply use the name of the color, like orange, red, or blue. Similarly, you can set values for all other properties like linewidth, opacity, miter, and cap. It is possible to remove the fill and stroke from all children in a group using the noFill() and noStroke() methods.

You can also apply other physical transformations like scale, rotation, and translation. These transformations will be applied to individual objects. Adding new objects to a group and removing them is easy with methods like add() and remove().

Here's an example which creates around 40 different rectangles at random locations. The rectangles are then grouped so that we can change their fill, stroke, and linewidth values at once.

1
var rects = [];
2
3
var elemWidth = document.querySelector("#draw-shapes").offsetWidth;
4
5
for (i = 0; i < 100; i++) {
6
  rects[i] = two.makeRectangle(
7
    Math.floor(Math.random() * elemWidth * 2),
8
    Math.floor(Math.random() * 420 * 2),
9
    10 + Math.floor(Math.random() * 100),
10
    10 + Math.floor(Math.random() * 100)
11
  );
12
}
13
14
var group = two.makeGroup(...rects);
15
16
group.noFill();
17
group.stroke = "black";
18
group.linewidth = 6;
19
20
two.update();

You can click anywhere inside the div to change the position of the rectangles. We will actually be setting the position of the group. Since the rectangles are part of the group, they will move automatically.

For practice, you should try to modify the code and split the rectangles into two equal groups. Apply a different set of linewidth and stroke color values to each of them to create your own unique geometric art piece.

Defining Gradients and Writing Text

You can define both linear and radial gradients in Two.js. Defining a gradient does not mean that it will be rendered automatically on the screen, but it will be available for you to use when setting the fill or stroke values of various objects.

You can define a linear gradient using two.makeLinearGradient(x1, y1, x2, y2, stops). The values x1 and y1 determine the coordinates of the start of the gradient. Similarly, the values x2 and y2 determine the coordinates of the end of the gradient. The stops parameter is an array of Two.Stop instances. These define the colors of each part of the array and where each color transitions into the next. They can be defined using new Two.Stop(offset, color, opacity), where offset determines the point on the gradient where that particular color has to be fully rendered. The color parameter determines the color of the gradient at the particular point. You can use any valid CSS color representation as its value. Finally, the opacity parameter determines the opacity of the color. The opacity is optional, and it can have any value between 0 and 1.

You can define radial gradients in a similar manner using two.makeRadialGradient(x, y, radius, stops, fx, fy). In this case, the values x and y determine the center of the gradient. The radius parameter specifies how far the gradient should extend. You can also pass an array of stops to this method in order to set the color composition of the gradients. The parameters fx and fy are optional, and they can be used to specify the focal position for the gradient.

Check out some of the types of gradient and their code in the CodePen below.

Remember that the x and y position of the gradients are with respect to the origin of the shape they are trying to fill. For instance, a radial gradient which is supposed to fill a shape from the center will always have x and y set to zero.

Two.js also allows you to write text on the drawing area and update it later according to your needs. This requires the use of the method two.makeText(message, x, y, styles). It might be evident from the name of the parameters that message is the actual text that you want to write. The parameters x and y are the coordinates of the point which will act as the center for writing the text. The styles parameter is an object which can be used to set the values of a large set of properties.

You can use styles to set the values of properties like font family, size, and alignment. You can also specify the value of properties like fill, stroke, opacity, rotation, scale, and translation.

Creating a Two.js Project

After learning about all these methods and properties, it is time to apply them to a project. In this tutorial, I will show you how we can use Two.js to render the first ten elements of the periodic table with electrons rotating around the nucleus. The nucleus will also have some slight movement to improve the visual appeal of our representation.

We begin by defining some variables and functions which will be used later.

1
var centerX = window.innerWidth / 2;
2
var centerY = window.innerHeight / 2;
3
4
var elem = document.getElementById("atoms");
5
6
var elementNames = [
7
  "",
8
  "Hydrogen",
9
  "Helium",
10
  "Lithium",
11
  "Beryllium",
12
  "Boron",
13
  "Carbon",
14
  "Nitrogen",
15
  "Oxygen",
16
  "Fluorine",
17
  "Neon"
18
];
19
20
var styles = {
21
  alignment: "center",
22
  size: 36,
23
  family: "Lato"
24
};
25
26
var nucleusCount = 10;
27
var nucleusArray = Array();
28
29
var electronCount = 10;
30
var electronArray = Array();
31
32
function intRange(min, max) {
33
  return Math.random() * (max - min) + min;
34
}

The above code stores the coordinates of the center of our window in the variables centerX and centerY. These will be used later to place our atom in the center. The elementNames array contains the names of the first ten elements of the periodic table. The index of each name corresponds to the number of electrons and protons of that element, and it begins with an empty string. The styles object contains properties for styling the text object.

We have also defined a function intRange() to get a random integer value within given extremes.

1
var two = new Two({ fullscreen: true }).appendTo(elem);
2
3
var protonColor = two.makeRadialGradient(
4
  0,
5
  0,
6
  15,
7
  new Two.Stop(0, "red", 1),
8
  new Two.Stop(1, "black", 1)
9
);
10
11
var neutronColor = two.makeRadialGradient(
12
  0,
13
  0,
14
  15,
15
  new Two.Stop(0, "blue", 1),
16
  new Two.Stop(1, "black", 1)
17
);
18
19
for (i = 0; i < nucleusCount; i++) {
20
  nucleusArray.push(two.makeCircle(intRange(-10, 10), intRange(-10, 10), 8));
21
}
22
23
nucleusArray.forEach(function(nucleus, index) {
24
  if (index % 2 == 0) {
25
    nucleus.fill = protonColor;
26
  }
27
  if (index % 2 == 1) {
28
    nucleus.fill = neutronColor;
29
  }
30
  nucleus.noStroke();
31
});

This creates an instance of Two and defines two radial gradients. The red/black radial gradients will represent protons, and blue/black gradients will represent neutrons.

We have used the intRange() function to place all these neutrons and protons within 20 pixels of each other. The makeCircle() method also sets the radius of these protons and neutrons to 10 pixels. After that, we iterate over nucleusArray and fill each circle with a different gradient alternately.

1
for (var i = 0; i < 10; i++) {
2
  if (i < 2) {
3
    var shellRadius = 50;
4
    var angle = i * Math.PI;
5
    electronArray.push(
6
      two.makeCircle(
7
        Math.cos(angle) * shellRadius,
8
        Math.sin(angle) * shellRadius,
9
        5
10
      )
11
    );
12
  }
13
  if (i >= 2 && i < 10) {
14
    var shellRadius = 80;
15
    var angle = (i - 2) * Math.PI / 4;
16
    electronArray.push(
17
      two.makeCircle(
18
        Math.cos(angle) * shellRadius,
19
        Math.sin(angle) * shellRadius,
20
        5
21
      )
22
    );
23
  }
24
}

Placing neutrons and protons inside the nucleus was easy. However, properly placing the electrons at a uniform distance will require a little maths. We use the shellRadius variable to specify the distance of different electron shells from the nucleus. A whole circle covers an angle equal to 2 PI radians. We can place different electrons uniformly by distributing the 2 PI radians between them equally.

The Math.cos() and Math.sin() functions are used to separate the vertical and horizontal components of the position vector of different electrons based on their angle.

1
var orbitA = two.makeCircle(centerX, centerY, 50);
2
orbitA.fill = "transparent";
3
orbitA.linewidth = 2;
4
orbitA.stroke = "rgba(0, 0, 0, 0.1)";
5
6
var orbitB = two.makeCircle(centerX, centerY, 80);
7
orbitB.fill = "transparent";
8
orbitB.linewidth = 2;
9
orbitB.stroke = "rgba(0, 0, 0, 0.1)";
10
11
var groupElectronA = two.makeGroup(electronArray.slice(0, 2));
12
groupElectronA.translation.set(centerX, centerY);
13
groupElectronA.fill = "orange";
14
groupElectronA.linewidth = 1;
15
16
var groupElectronB = two.makeGroup(electronArray.slice(2, 10));
17
groupElectronB.translation.set(centerX, centerY);
18
groupElectronB.fill = "yellow";
19
groupElectronB.linewidth = 1;
20
21
var groupNucleus = two.makeGroup(nucleusArray);
22
groupNucleus.translation.set(centerX, centerY);

This part of the code puts electrons from different shells as well as neutrons and protons in their own separate groups. It also sets the fill colors for all electrons in a specific orbit at once.

1
two
2
  .bind("update", function(frameCount) {
3
    groupElectronA.rotation += 0.025 * Math.PI;
4
    groupElectronB.rotation += 0.005 * Math.PI;
5
    groupNucleus.rotation -= 0.05;
6
  })
7
  .play();
8
9
var text = two.makeText("", centerX, 100, styles);
10
11
nucleusArray.forEach(function(nucleus, index) {
12
  nucleus.opacity = 0;
13
});
14
15
electronArray.forEach(function(electron, index) {
16
  electron.opacity = 0;
17
});

This part of the code sets the opacity of individual electrons and protons to zero. It also tells Two.js to rotate the electrons and protons at specific speeds.

1
visible = 0;
2
3
document.addEventListener("click", function(event) {
4
  if (visible < nucleusArray.length) {
5
    nucleusArray[visible].opacity = 1;
6
    electronArray[visible].opacity = 1;
7
    visible++;
8
    text.value = elementNames[visible];
9
  }
10
  else {
11
    nucleusArray.forEach(el => el.opacity=0);
12
    electronArray.forEach(el => el.opacity=0);
13
    visible = 0;
14
    text.value = elementNames[0];
15
  }
16
});         
17

The final part of the code allows us to iterate through the elements by clicking the mouse or tapping. To load the next element, we make one more electron and one more proton or neutron visible and update the element name. After clicking on the last element, all the particles are hidden again so we can start over. The visible variable keeps track of the number of atomic particles that are currently visible so that we can show or hide them accordingly.

Try clicking or tapping in the following CodePen demo to see the first ten elements of the periodic table.

Final Thoughts

We began this tutorial with a brief introduction to the Two.js library and how it can be used to draw shapes like rectangles, circles, and ellipses. After that, we discussed how we can group different objects to manipulate them all at once. We used this ability to group elements to translate and rotate them in synchronization. These tools all came together in our animation of the atoms of the first ten elements in the periodic table.

As you can see, creating animated 2D graphics is very easy using Two.js. The focus of this post was to help you get started quickly, so we only covered the basics. However, you should read the official documentation to learn more about the library!

More JavaScript Resources

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.