Canvas and WebGL in JavaScript

The HTML5 Canvas and WebGL APIs provide powerful ways to create 2D and 3D graphics directly in the browser. These technologies enable everything from simple drawings and animations to complex games and data visualizations.

Part 1: HTML5 Canvas

The Canvas API is a 2D drawing API that allows you to render graphics, animations, and interactive content directly in the browser without plugins.

Getting Started with Canvas

To use Canvas, you first need to add a <canvas> element to your HTML and then access it with JavaScript:

<!-- HTML -->
<canvas id="myCanvas" width="500" height="300"></canvas>

// JavaScript
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// Now you can use ctx to draw on the canvas

Note: The width and height attributes define the canvas's coordinate system. If you set the size with CSS, the content may appear stretched or blurry.

Basic Drawing Operations

The Canvas API provides methods for drawing shapes, text, images, and more:

// Drawing rectangles
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 100, 50); // x, y, width, height

ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);

ctx.clearRect(20, 20, 80, 30); // Erases a rectangular area

// Drawing paths
ctx.beginPath();
ctx.moveTo(250, 10);
ctx.lineTo(350, 10);
ctx.lineTo(300, 60);
ctx.closePath(); // Closes the path by connecting to the start point
ctx.fillStyle = 'green';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();

// Drawing circles
ctx.beginPath();
ctx.arc(400, 35, 25, 0, Math.PI * 2); // x, y, radius, startAngle, endAngle
ctx.fillStyle = 'orange';
ctx.fill();

// Drawing text
ctx.font = '20px Arial';
ctx.fillStyle = 'black';
ctx.fillText('Hello Canvas!', 10, 100);
ctx.strokeText('Outlined Text', 200, 100);

// Drawing images
const img = new Image();
img.src = 'example.jpg';
img.onload = function() {
  ctx.drawImage(img, 10, 120, 150, 100); // x, y, width, height
};

Canvas Transformations

You can apply transformations to change how shapes are drawn:

// Save the current state
ctx.save();

// Translate (move the origin)
ctx.translate(100, 200);

// Rotate (in radians)
ctx.rotate(Math.PI / 4); // 45 degrees

// Scale
ctx.scale(1.5, 0.5);

// Draw a rectangle at the transformed position
ctx.fillStyle = 'purple';
ctx.fillRect(-25, -25, 50, 50);

// Restore to the previous state
ctx.restore();

// The next drawing operation will use the original coordinates

Animation with Canvas

You can create animations by clearing the canvas and redrawing in a loop:

let x = 0;
const ballRadius = 20;

function animate() {
  // Clear the canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // Draw the ball
  ctx.beginPath();
  ctx.arc(x, canvas.height / 2, ballRadius, 0, Math.PI * 2);
  ctx.fillStyle = 'blue';
  ctx.fill();
  
  // Update position
  x += 2;
  
  // Reset when the ball goes off-screen
  if (x > canvas.width + ballRadius) {
    x = -ballRadius;
  }
  
  // Request the next frame
  requestAnimationFrame(animate);
}

// Start the animation
animate();

Interactive Canvas

You can make your canvas interactive by handling mouse and touch events:

// Simple drawing app
let isDrawing = false;
let lastX = 0;
let lastY = 0;

canvas.addEventListener('mousedown', (e) => {
  isDrawing = true;
  [lastX, lastY] = [e.offsetX, e.offsetY];
});

canvas.addEventListener('mousemove', (e) => {
  if (!isDrawing) return;
  
  ctx.beginPath();
  ctx.moveTo(lastX, lastY);
  ctx.lineTo(e.offsetX, e.offsetY);
  ctx.strokeStyle = 'black';
  ctx.lineWidth = 2;
  ctx.stroke();
  
  [lastX, lastY] = [e.offsetX, e.offsetY];
});

canvas.addEventListener('mouseup', () => {
  isDrawing = false;
});

canvas.addEventListener('mouseout', () => {
  isDrawing = false;
});

Canvas Example: Bouncing Ball

const canvas = document.getElementById('bouncingBallCanvas');
const ctx = canvas.getContext('2d');

let x = 50;
let y = 50;
let dx = 3;
let dy = 2;
const radius = 20;

function drawBouncingBall() {
  // Clear the canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // Draw the ball
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fillStyle = '#3498db';
  ctx.fill();
  ctx.strokeStyle = '#2980b9';
  ctx.lineWidth = 2;
  ctx.stroke();
  
  // Bounce off walls
  if (x + dx > canvas.width - radius || x + dx < radius) {
    dx = -dx;
  }
  if (y + dy > canvas.height - radius || y + dy < radius) {
    dy = -dy;
  }
  
  // Update position
  x += dx;
  y += dy;
  
  requestAnimationFrame(drawBouncingBall);
}

drawBouncingBall();

Part 2: WebGL

WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without plugins.

Introduction to WebGL

WebGL is based on OpenGL ES and provides low-level access to the GPU. It's more complex than Canvas 2D but offers much more power and flexibility.

Note: WebGL is complex and has a steep learning curve. Many developers use libraries like Three.js, Babylon.js, or Pixi.js to simplify WebGL development.

Setting Up WebGL

To use WebGL, you need to get a WebGL rendering context from a canvas element:

<!-- HTML -->
<canvas id="webglCanvas" width="500" height="300"></canvas>

// JavaScript
const canvas = document.getElementById('webglCanvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

if (!gl) {
  alert('WebGL is not supported by your browser');
}

WebGL Basics: Drawing a Triangle

Here's a simplified example of drawing a triangle with WebGL:

// Vertex shader program
const vsSource = `
  attribute vec4 aVertexPosition;
  attribute vec4 aVertexColor;
  
  varying lowp vec4 vColor;
  
  void main() {
    gl_Position = aVertexPosition;
    vColor = aVertexColor;
  }
`;

// Fragment shader program
const fsSource = `
  varying lowp vec4 vColor;
  
  void main() {
    gl_FragColor = vColor;
  }
`;

// Initialize shader program
function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  
  // Create the shader program
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
    return null;
  }
  
  return shaderProgram;
}

// Create a shader
function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  
  return shader;
}

// Initialize buffers
function initBuffers(gl) {
  // Create a buffer for the triangle's positions
  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  
  // Triangle vertices
  const positions = [
     0.0,  0.5,  0.0,
    -0.5, -0.5,  0.0,
     0.5, -0.5,  0.0
  ];
  
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  
  // Create a buffer for the colors
  const colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  
  // Colors for each vertex
  const colors = [
    1.0, 0.0, 0.0, 1.0,  // Red
    0.0, 1.0, 0.0, 1.0,  // Green
    0.0, 0.0, 1.0, 1.0   // Blue
  ];
  
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
  
  return {
    position: positionBuffer,
    color: colorBuffer
  };
}

// Draw the scene
function drawScene(gl, programInfo, buffers) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clearDepth(1.0);
  gl.enable(gl.DEPTH_TEST);
  gl.depthFunc(gl.LEQUAL);
  
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
  // Tell WebGL how to pull out the positions from the position buffer
  {
    const numComponents = 3;  // pull out 3 values per iteration
    const type = gl.FLOAT;    // the data in the buffer is 32bit floats
    const normalize = false;  // don't normalize
    const stride = 0;         // how many bytes to get from one set to the next
    const offset = 0;         // how many bytes inside the buffer to start from
    
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexPosition,
      numComponents,
      type,
      normalize,
      stride,
      offset
    );
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
  }
  
  // Tell WebGL how to pull out the colors from the color buffer
  {
    const numComponents = 4;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexColor,
      numComponents,
      type,
      normalize,
      stride,
      offset
    );
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
  }
  
  // Tell WebGL to use our program when drawing
  gl.useProgram(programInfo.program);
  
  // Draw the triangle
  {
    const offset = 0;
    const vertexCount = 3;
    gl.drawArrays(gl.TRIANGLES, offset, vertexCount);
  }
}

// Main function
function main() {
  // Initialize the shader program
  const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
  
  // Collect all the info needed to use the shader program
  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
      vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor')
    }
  };
  
  // Initialize the buffers
  const buffers = initBuffers(gl);
  
  // Draw the scene
  drawScene(gl, programInfo, buffers);
}

// Run the main function
main();

Using Three.js for 3D Graphics

Three.js is a popular JavaScript library that simplifies WebGL development:

// First, include Three.js in your HTML:
// <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

// Create a scene
const scene = new THREE.Scene();

// Create a camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;

// Create a renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Create a cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Animation loop
function animate() {
  requestAnimationFrame(animate);
  
  // Rotate the cube
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  
  // Render the scene
  renderer.render(scene, camera);
}

// Start the animation
animate();

// Handle window resize
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

WebGL Features and Capabilities

WebGL enables a wide range of graphics capabilities:

  • 3D Models: Loading and rendering 3D models in formats like glTF, OBJ, or FBX
  • Textures: Applying image textures to 3D objects
  • Lighting: Implementing various lighting models (ambient, directional, point, spot)
  • Shaders: Custom GLSL shaders for advanced visual effects
  • Physics: Integration with physics engines for realistic simulations
  • Particle Systems: Creating effects like fire, smoke, or water
  • Post-Processing: Applying effects like bloom, depth of field, or motion blur
  • VR/AR: Creating virtual or augmented reality experiences

Canvas vs. WebGL: When to Use Each

Both Canvas and WebGL have their strengths and ideal use cases:

Use Canvas 2D When:

  • You need simple 2D graphics or visualizations
  • You want a straightforward API with a gentle learning curve
  • Your application doesn't require high performance
  • You're creating UI elements, charts, or simple games

Use WebGL When:

  • You need 3D graphics or complex visual effects
  • Performance is critical (WebGL uses GPU acceleration)
  • You're creating games, simulations, or immersive experiences
  • You need to process large amounts of visual data

Performance Considerations

When working with Canvas and WebGL, keep these performance tips in mind:

Canvas Performance Tips:

  • Minimize the number of draw calls
  • Use requestAnimationFrame instead of setTimeout for animations
  • Only redraw what's changed, not the entire canvas
  • Use off-screen canvases for complex operations
  • Consider using multiple layered canvases for complex scenes

WebGL Performance Tips:

  • Minimize state changes (changing shaders, textures, etc.)
  • Use instancing for repeated objects
  • Implement level of detail (LOD) for complex models
  • Use texture atlases to reduce texture switching
  • Implement frustum culling to avoid rendering off-screen objects
  • Optimize shaders for performance

Browser Support

Canvas and WebGL are supported in all modern browsers:

  • Canvas: Chrome, Firefox, Safari, Edge, Opera, iOS Safari, Android Browser
  • WebGL: Chrome, Firefox, Safari, Edge, Opera, iOS Safari, Android Browser

Note: While WebGL is widely supported, some older devices or browsers may have limited or no WebGL support. Always implement fallbacks for critical applications.

Resources and Libraries

Here are some popular libraries and resources for Canvas and WebGL development:

Canvas Libraries:

  • Fabric.js: Provides an interactive object model on top of Canvas
  • Paper.js: Vector graphics scripting framework
  • p5.js: Creative coding library with an easy-to-use Canvas API
  • Chart.js: Simple yet flexible JavaScript charting library
  • D3.js: Data-driven documents for powerful visualizations

WebGL Libraries:

  • Three.js: The most popular WebGL library for 3D graphics
  • Babylon.js: Powerful, beautiful, simple, and open 3D framework
  • Pixi.js: 2D WebGL renderer with Canvas fallback
  • A-Frame: Web framework for building VR experiences
  • Regl: Functional abstraction for WebGL

Next Steps

Now that you understand Canvas and WebGL, you might want to explore:

  • Creating interactive data visualizations
  • Building browser-based games
  • Implementing creative visual effects
  • Learning GLSL shaders for custom visual effects
  • Exploring WebXR for virtual and augmented reality