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 ofsetTimeout
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