The sheer number of options can seem overwhelming at first, but you can draw and animate nearly anything on the web these days if you pick and master the right tool. In this article, we’ll go over some of the popular approaches to graphics-intensive projects on the web, compare their advantages, and look at some sample code. By the end, you’ll be able to confidently make your choice, and get drawing!
The DOM: Quick and Efficient
- easy to get started, since you’re working directly with the HTML elements you already have
- excellent for text and element transitions on text-based pages
- no need to add any external libraries or elements
- not suitable for graphics-intensive applications or games
- performance issues when animating multiple elements or increasing refresh rate
CSS has a property called
transition, which allows you to move between its current state and the state defined by the CSS rule. For example, if your website offers a dark mode, you might define a .dark-mode class that sets a dark background and a light text color. When a user clicks the dark mode toggle, you can append this class to all the relevant elements (or a container) to trigger the switch to this color scheme. Rather than instantly switching the colors, you can transition to them by defining the new rules and adding a
transition property on the CSS rule:
transition: color 0.5s, background-color 0.5s
Although the phrase “web animation” might bring back memories of scrolling banners on 1990s websites, they can be useful for elements like loading spinners. In an animation, you define keyframes, where each frame represents the state of an element at a given time. For example, if you wanted a
<div> to change colors from green to red to blue, your three states would set
background-color to each of these. The animation property then allows you to set how to transition from one keyframe to another—how long it should take and how smooth the transition should be.
In the dark mode example above, we can create an event listener on the dark mode toggle, add a
In addition to animations and transitions, there are two other DOM-based ways to draw shapes:
CSS clip paths allow you to “clip” HTML elements, hiding parts of the element. This allows you to create interesting and inventive shapes, from playful curved divs to complex background elements.
SVG, which stands for Scalable Vector Graphics, is a markup language for drawing shapes. (MDN docs say, “SVG is essentially to graphics what HTML is to text.”) Just like HTML defines paragraphs, images, divs, and other elements that help you lay out a web page, SVG defines graphics elements in an
<svg> tag, and uses
<g> tags to group elements and basic shape tags (like
<line>) to draw shapes. You can then select and style these with CSS or dedicated inline attributes.
Canvas: 2D Paradise
- excellent way to write performant 2D graphics and games
- your graphics need to live within a Canvas DOM element
- primarily suitable for 2D
- no built-in support for charts (although there are data visualization libraries built on top of Canvas)
<div> across the screen may not cause lag, but moving dozens of
<div>s at the same time certainly will. Conversely, Canvas has no problem manipulating thousands of particles each frame. Generally, you’ll iterate through each element (or, using the power of JS logic, only the relevant elements) and update their state before drawing anything on screen.
So, what does using Canvas look like? To start, you’ll need to create a single
const canvas = document.querySelect('canvas'); const ctx = canvas.getContext('2d'); // ctx is a common convention for accessing for the Canvas context
The way Canvas draws onto the screen may not feel intuitive—that is, it may be different from animation software you’ve seen before.
You’ll need a refresh loop
As we mentioned earlier, Canvas is stateless and doesn’t retain any information about what’s drawn on it. Once processed, shapes are just pixels on the screen. So, generally, on every frame, you will clear your Canvas by drawing a full-size rectangle (effectively your background) and then drawing all your other objects on top of it. In traditional animation software, you might place an object on the screen, then move it, and then “capture” its position. In Canvas, instructions are declarative rather than procedural. So, rather than saying:
- place a circle
- move it 10px right
- scale it to twice its size
on every frame, you’ll say “clear the screen, then draw circle from our data.” This recalculate/erase/draw refresh loop can be powered by the JS
setInterval method, or the better-performing
requestAnimationFrame method. This browser function re-renders the screen at a rate that generally matches the monitor refresh rate (commonly 60 FPS), and pauses the animation when you switch tabs or hide the window.
Store your element state in objects
A common practice when working with Canvas is to store data about everything on screen in objects. In our example above, you might save a
Circle object (or whatever the circle represents—maybe your growing admiration of Canvas?) with an X, Y, and radius. On every frame draw, you’ll ask Canvas to draw you a circle using its native
arc function, which can supply your object properties as arguments for where the circle should be placed and how big it should be. Then, you can update those properties.
This object-driven workflow makes it a breeze to integrate external data, user events, or data you’re processing as the app is running. By separating the rendering/drawing functions from your data processing, you can focus on updating your objects, and trust that they’ll be updated on the screen on the next refresh.
All in all, your workflow will look roughly like this:
- Each frame, clear the screen by drawing a background rectangle over the Canvas.
- Update any objects you need based on game state, new data, and other incoming events (for example, if a bullet object has collided with a player object, you might decrease that player’s HP).
- Iterate through your JS objects, and for each object, draw a shape, text, or pictures based on the data you’re storing and updating.
As it lacks external dependencies, Canvas is a great, lightweight choice for many 2D projects such as games, art, and interactive experiences. However, if you want to move into 3D or are primarily interested in working with data visualization, there may be other tools (with more relevant standard libraries) that are easier to work with. If you’re ready to level up to 3D, a good next step might be…
Three.js: Welcome to a 3D World
- a popular and richly-featured library for creating and animating 3D scenes
- intuitive animation concepts (data stored in objects)
- reasonable learning curve
- you’ll need to think about light and camera placement
- performance can lag on older computers
- not geared towards creating charts or data visualizations
So, what does it look like to build a basic 3D scene in three.js? Unlike Canvas, three.js isn’t stateless, so it might feel more intuitive to manipulate objects. However, since you’re now in 3D, you’ll need to start thinking about light and camera placement, as well as 3D physics.
Just like the 2D Canvas we worked with before, WebGL uses the
<canvas> element—and since three.js renders your scene with WebGL under the hood, that’s where we’ll be creating our three.js scene. Here is a small demo with comments to help you make sense of the code
In this example:
- We import three.js, since it’s an external JS library.
- We then set up a scene, an aspect ratio, a camera, and a renderer. Since our cube will be centered at (0,0,0) by default, we’ll set our camera Z position to 50.
- We append the renderer to the body, which automatically creates a
<canvas>element. You could also pass in your own Canvas element.
- Next, we create a cube. To do this, we use a primitive box geometry, and then apply a material to it. Next, we add some wireframe geometry to better see the edges of the cube.
- We can’t see the cube (yet), so we add a light source. We’ll use AmbientLight for the sake of simplicity—it’ll illuminate every part of our scene equally. In the future, you’ll generally position your light sources and point them at your scene, resulting in more realistic lighting.
- We’ll use
requestAnimationFrameto render our scene. You could, alternatively, use the same method as our Canvas example and create a
requestAnimationFramewill result in better performance.
- Finally, let’s add a little motion. We’ll rotate the cube a little each refresh, and we’ll also move the camera back and forth.
- Unlike Canvas, we don’t clear the screen every frame. With three.js, our data is stored in objects—for example, we can change the camera, light, and cube position by setting their
Excited to keep going? ThreeJSFundamentals is a fantastic resource for learning this exciting library. Finally, while three.js is a very popular 3D library, it’s certainly not the only one. Babylon.js is a popular alternative, and aframe.js is a library built on top of three.js with a focus on VR. Or, if you like to do things from first principles, you can try learning WebGL (beware—it takes a while to get your scene off the ground!).
D3.js: Up And To The Right
- large and active user community
- excellent out-of-the-box library of charts and transformations
- moderate learning curve
- suitable primarily for data visualization
If your primary goal is to create data visualizations, D3.js is a popular, well-supported library used exactly for this purpose. Under the hood, D3 uses the HTML SVG feature we discussed earlier. D3 helps you marry data to DOM elements, which allow you to quickly and efficiently apply out-of-the-box transformations to out-of-the-box primitive shapes and charts.
In this example, we’re working with a small array of data—representing, if you’re curious, the average monthly temperature (in Fahrenheit), in Boulder, Colorado. Don’t be put off by the amount of code! The first half of it is just an array of objects representing our data.
Notice that there’s no HTML at all—we’re dynamically appending SVG elements, and then styling them with CSS.
- Starting line 50, we append a
.bar-chartSVG and then use the D3
.enter()function to append an SVG rectangle for every piece of data.
- Lines 65-75 position the rectangles and add a
.rectclass, which we can then use to style the rectangles.
In the remaining code, we proceed similarly to add labels to our chart:
- We select our weather data,
- We use
.enter()on our data to iterate through it (this method gets fairly technical, but here is a great explanation!)
- For each data point, we append a label based on a data attribute—first the month, then the temperature.
- We give each label a class will allow us to later style it with CSS.
D3 can be tricky to get started with, but its ability to manipulate and visualize data is top notch. Excited to keep going? This compendium of tutorials should help get you up to speed!
Unity: Game On
- the build process makes it easy to develop in 2D and 3D and export to different platforms
- offers a powerful editor with a physics engine and AI tools
- steep learning curve
- closed platform
- charges for larger projects
- focused primarily on game development
Unity is a popular game engine for serious 2D and 3D game development. Unlike the libraries and technologies we’ve discussed so far, it’s neither native (like Canvas, WebGL, or DOM manipulation) nor open-source (like D3 and three.js)—in fact, Unity charges projects making over $100K per year. Unity is scripted in C#, a general-purpose C-style language. On the web, Unity runs within the now-deprecated Unity player or is compiled to a WebGL project during the build.
In return, Unity offers some amazing advantages when it comes to game development:
- Cross-platform development: once you’re done developing, you can build and export your game to WebGL, mobile, VR, console, and many other platforms.
- The Unity Editor: an integrated UI that offers both design and developer tools, as well as additional AI tools like pathfinding, a physics engine, and animation tools.
- Physics Engine: Unity ships with an advanced physics engine that can help you create realistic, immersive experiences without dipping into complex mathematics or reaching for another third-party library.
Creating a Unity demo is beyond the scope of this article, but if you’re curious about what a finished product looks like, here is a WebGL-based demo. Incredible! If your goal is professional game development, tools like Unity—and its C++ based competitor Unreal Engine—are the way to go.
Physics Libraries: Just Like The Real World!
Before we wrap up our whirlwind tour of graphics on the web, let’s touch briefly on a closely-related topic: physics. No matter which of the above tools you choose to use, if your graphics app involves elements interacting with one another, you’ll soon find yourself writing code to simulate physics. Some popular examples of this are collision detection, travel paths through materials with different density, gravity, magnetic forces, and other behaviors meant to mirror how objects behave in the real world.
For smaller projects, it’s possible (and maybe even fun!) to try and write the code for these by hand. However, as your project scales, you might want to consider leveraging a physics library—code written specifically to help simulate realistic physics. Some examples are PhysicsJS, matter.js, planck.js, and ammo.js.
As always, the downside to using a library is overhead and added complexity—code abstracted away and potentially unused. The tradeoff is time saved not reinventing the wheel, the reliability of using a tried-and-true solution, and the fun of focusing on the parts of the app that are more interesting—unless, of course, you find reading physics textbooks interesting!
Wait, you forgot…
I hope that this article introduced you to the multitude of ways to draw, graph, and animate on the modern web. Of course, since I focused on some combination of the most popular tools and those I’ve personally used, I’ve undoubtedly left some great libraries out. As usual, I hope that this overview has given you a sense of the moving pieces when it comes to web graphics. Most of the libraries you’ll find are built on top of Canvas, SVG, and WebGL—native technologies built into and evolving in our browsers.
Have you found the right tool for your next project? Have you used another great library or engine I haven’t mentioned? Be sure to share in the comments, and I can’t wait to see the projects you make!
Our related podcast with Dries Buytaert
Tags: bulletin, graphics, stackoverflow