JavaScript Graphics

Let’s do some graphics, but not much. Just the basics. Baby steps. Baby steps. All we’re doing here is one or two examples for each type of graphics technique.

Simple DOM Manipulation

A browser window contains elements. Elements can have colors (even gradients), borders, background colors, background images, fancy borders, opacity settings, and all kinds of paddings and margins. Elements can even be images! With rounded borders we can get circles. Importantly, CSS also allows elements to be positioned. With JavaScript, we can vary all of these settings over time to make animations.

Directly manipulating the DOM is tedious and error-prone and requires a lot of code. You rarely would ever do such a thing in real life; however, it is useful to know how it works. So study the code here, but don’t fret too much if the code seems unwieldy.

Here’s a bouncing ball program using low-level, DOM-manipulating JavaScript:

Here’s the code. Note the use of requestAnimationFrame, which you supply with a function to execute before the next time the browser repaints.

bouncingballs.js
/*
 * A script illustrating bouncing balls. The HTML should provide a container div
 * (id = 'bounceContainer') and a button (id = 'startOrStopBounce') that toggles
 * the animation.
 */

window.addEventListener('load', () => {
  const container = document.querySelector('#bounceContainer')
  const button = document.querySelector('#startOrStopBounce')

  class Ball {
    constructor(x, y, dx, dy, diameter, color) {
      // Initial model
      Object.assign(this, { x, y, dx, dy, diameter })

      // Initial view
      this.div = document.createElement('div')
      Object.assign(this.div.style, {
        left: `${x}px`,
        top: `${y}px`,
        width: `${diameter}px`,
        height: `${diameter}px`,
        borderRadius: `${diameter / 2}px`,
        backgroundColor: color,
        position: 'absolute',
      })
      container.appendChild(this.div)
    }

    move() {
      // Update the model
      [this.x, this.y] = [this.x + this.dx, this.y + this.dy]
      if (this.x < 0 || this.x > container.clientWidth - this.diameter) {
        this.x = Math.max(0, Math.min(this.x, container.clientWidth - this.diameter))
        this.dx = -this.dx
      }
      if (this.y < 0 || this.y > container.clientHeight - this.diameter) {
        this.y = Math.max(0, Math.min(this.y, container.clientHeight - this.diameter))
        this.dy = -this.dy
      }

      // Update the view
      [this.div.style.left, this.div.style.top] = [`${this.x}px`, `${this.y}px`]
    }
  }

  const advance = () => {
    balls.forEach(ball => ball.move())
    if (button.value === 'STOP') {
      requestAnimationFrame(advance)
    }
  }

  button.addEventListener('click', () => {
    if (button.value === 'STOP') {
      button.value = 'START'
    } else {
      requestAnimationFrame(advance)
      button.value = 'STOP'
    }
  })

  const balls = [
    new Ball(20, 70, 3, 2, 30, 'rgba(90, 255, 95, 0.5)'),
    new Ball(500, 300, -3, -3, 35, 'rgba(200, 41, 199, 0.5)'),
    new Ball(140, 10, 5, 5, 40, 'rgba(250, 50, 10, 0.4)'),
  ]
})

CSS-Only Graphics

There are thousands of examples on the web of using only CSS for interesting graphics. Here’s one:

See the Pen Pure CSS Rainbow Sinus Road by Keith Wyland (@keithwyland) on CodePen.

Have fun browsing the examples and demos and tutorials you can find from a web search on “CSS-only graphics”.

SVG

SVG is a markup language for describing 2-D vector graphics. Mozilla hosts a home page for SVG on the web with JavaScript, with a ton of resources including this tutorial.

D3

D3 is a JavaScript library for visulaizing data in some spectactular ways. Start at the D3 Gallery then make your way to the D3 home page and find some tutorials to make your own creations.

Canvas

HTML features a <canvas> element, designed for drawing. Here are the docs and here is a tutorial

Canvas drawing is done with JavaScript. You obtain a context object for the canvas, either a 2d context or a 3d context and use methods on the context object to draw. If you need interactivity and animation, basic JavaScript methods for timing and events apply as usual. Here’s an example we’ll go over in class:

shapes.js
/*
 * A script that draws random shapes in a canvas whose id is named 'shapes'.
 */

window.addEventListener('load', () => {
  const canvas = document.getElementById('shapes')
  const width = canvas.width
  const height = canvas.height
  const ctx = canvas.getContext('2d')
  const random255 = () => Math.floor(Math.random() * 255)
  const randomColor = () => `rgba(${random255()},${random255()},${random255()},0.5`
  const randomX = () => (Math.random() * width) - 50
  const randomY = () => (Math.random() * height) - 50
  const randomSide = () => (Math.random() * 100) + 20
  const randomRadius = () => (Math.random() * 50) + 20

  const drawShapes = () => {
    ctx.clearRect(0, 0, width, height)
    for (let i = 0; i < 50; i += 1) {
      ctx.fillStyle = randomColor()
      ctx.fillRect(randomX(), randomY(), randomSide(), randomSide())
      ctx.fillStyle = randomColor()
      ctx.beginPath()
      ctx.arc(randomX(), randomY(), randomRadius(), 0, Math.PI * 2, true)
      ctx.closePath()
      ctx.fill()
    }
  }

  canvas.addEventListener('click', drawShapes)

  drawShapes()
})

2-D Graphics using p5.js

The super cool p5.js library greatly simplifies drawing in a canvas. If you landed on this page as part of my Introduction to Computer Programming class, we started the class with p5.js 2-D graphics, so hopefully you remember some things! Whether you are new or just want a refresher, start here.

WebGL

WebGL, (Web Graphics Library) is a JavaScript library for 3D graphics. Your 3D graphics use your device’s GPU for hardware acceleration. Rendering is done on a canvas element.

WebGL is a very low-level library; in practice you will want to use a library built on top of WebGL, such as three.js, babylon.js, or p5.js. Let’s do some p5.js code alongs.

The p5.js folks have their own mini Getting Started with WebGL here.

And of course, there is Coding Train video series that’ll also teach you p5.js 3-D graphics.

Coordinate Systems and Geometry

Here are the basics:

Here’s a demo of the basic shapes and translation (Source code):

Code-Along Time:

We will end up with (Source code):

Exercise: Describe the effect of rotating-before-translating vs. translating-before-rotating.
Models and Transforms

You are not limited to the seven basic shapes. p5.js allows you to create and load your own models with the model and loadModel functions.

Also, you are not limited to translation, rotation, and scaling! There’re a couple functions for shearing, and a function to apply an arbitary 3-D affine transformation! We’re not covering those here, but they’re available.

Material and Lighting

You’ve noticed that shapes are drawn as a mesh of triangles. These triangle are subject to the same stroke, strokeWeight, fill, noStroke, and noFill functions you know and love from 2-D graphics. But for better graphics, we want material and lighting.

Here are the material functions (call them before drawing something):

normalMaterial()
Does not respond to light; use only for debugging
ambientMaterial(color)
The color of light reflected (no matter what kind of light)
specularMaterial(color)
The color of light reflected in all directions, similar to ambientMaterial, but for point and directional light, reflects that back to the view to make the material look shiny
emissiveMaterial(color)
A material colored to appear to “give off” light
shininess(s)
How shiny (glossy) the specular material looks, minimum (and default) value is 1

And here are the lighting functions:

ambientLight(color)
directionalLight(color, direction)
pointLight(color, position)
spotLight(color, position, direction, angle, concentration)
lightFalloff(constant, linear, quadratic)

Here’s a demo with basic material and lighting (Source code):

Here’s a demo with specular lighting (Source code):

Code-Along Time! We’re going to enhance the UFO landing application and get it to this:

Texture

Good enough to know for now: Inovke loadImage in preload, and call texture before drawing. Demo (Source code):

There’s more to it, but this is a start.

Exercise: Find a moon texture and wrap it onto a sphere that moves around the earth. Make the moon go around Earth in the right number of Earth “days.” Also find a good image of space and texture it on a plane which you place far behind the earth and the moon.

Cameras

When starting up an app in p5.js under WebGL, you are given an initial perspective camera with the following specifications:

Some applications are better implemented by moving the camera around a scene, rather than, or in addition to, moving the objects within the scene! Here’s an example of a satellite with a highly ellpitical orbit rotating around Earth, and always pointing its view toward the surface. In the program the Earth is fixed at the origin, but the camera moves:

TODO

The moving camera is great for flight simulators such as the following app. And just for fun, this app throws in some randomly generated terrain:

TODO

A perspective camera is great for adding a degree of realism to your scenes, since objects farther away from the camera appear smaller. But for some engineering drawings and 2.5-D games, an orthgraphic projection is more useful—with this projection, objects of the same size appear to be the same size regardless of how far from the camer they are. Check out the p5.js docs for details.