# HTML5 & Canvas Beginner Lab

24 lessons from page setup to an interactive chart. HTML5 is commonly used to describe modern HTML; this guide focuses on the 2D Canvas API.

## 1. Your first HTML5 drawing surface

HTML describes page structure, CSS controls presentation, and JavaScript supplies behavior. Start a document with <!doctype html>, add a language, charset and viewport, and give it a meaningful title. A canvas is a bitmap drawing surface inside that page, not a collection of HTML shapes. This lab supplies a 600 × 360 canvas named canvas and its 2D context named ctx. Open the setup panel to see the complete HTML foundation.

Try: Change the rectangle color and run the example.

```javascript
ctx.fillStyle = "#246a73";
ctx.fillRect(30, 30, 180, 100);
```

Check: Which part draws pixels into a canvas?

Answer: JavaScript using a rendering context — HTML creates the element. Drawing commands on a rendering context paint its bitmap.

## 2. Get the 2D context

Find the canvas with document.querySelector, then call getContext("2d"). The returned object provides drawing methods and styles. In production, check for null before drawing. This playground already provides canvas and ctx, but the example also shows how to obtain them yourself.

Try: Rename pen to brush everywhere and verify the drawing still works.

```javascript
const surface = document.querySelector("canvas");
const pen = surface.getContext("2d");
if (!pen) throw new Error("2D drawing is unavailable");
pen.fillStyle = "#df7148";
pen.fillRect(50, 40, 140, 90);
```

Check: What does getContext("2d") return?

Answer: A drawing context or null — The context holds drawing methods and state. A null check handles unavailable contexts.

## 3. Understand coordinates

The default origin is the top-left corner. x increases to the right; y increases downward. Units refer to the drawing coordinate system, which can later be transformed. A point at (300,180) is in the center of this 600 × 360 surface.

Try: Move the center square 50 units down.

```javascript
ctx.fillStyle = "#246a73";
for (const [x,y] of [[0,0],[300,180],[580,340]]) {
  ctx.fillRect(x,y,20,20);
}
ctx.font = "18px sans-serif";
ctx.fillText("Center: (300, 180)", 320, 185);
```

Check: Increasing y moves a shape…

Answer: Down — In the default canvas coordinate system, positive y points downward.

## 4. Bitmap size versus CSS size

The width and height attributes set bitmap dimensions; CSS determines displayed size. Stretching a small bitmap with CSS can blur or distort it. Assigning canvas.width or canvas.height resets pixels and drawing state, even if you assign the same value. This lab fits the display responsively while preserving its aspect ratio.

Try: Change the bitmap width to 300 and observe the result before restoring 600.

```javascript
canvas.width = 600;
canvas.height = 360;
ctx.fillStyle = "#246a73";
ctx.fillRect(30,30,240,120);
```

Check: What happens when canvas.width is assigned?

Answer: Pixels and context state reset — Resizing the bitmap clears it and resets context state; redraw after resizing.

## 5. Filled and outlined rectangles

fillRect paints a rectangle; strokeRect outlines it. Both accept x, y, width and height. These methods draw immediately and do not add a rectangle to the current path. Set fillStyle, strokeStyle and lineWidth before drawing.

Try: Make both rectangles the same size but give the outline a thinner stroke.

```javascript
ctx.fillStyle = "#f1bd64";
ctx.fillRect(40,40,200,120);
ctx.strokeStyle = "#246a73";
ctx.lineWidth = 8;
ctx.strokeRect(280,40,200,120);
```

Check: Which method outlines a rectangle?

Answer: strokeRect — strokeRect draws an outline using strokeStyle and lineWidth.

## 6. Clear and redraw

Canvas does not remember independent shape objects. To move a shape, keep its position in your own data, clear the old image and redraw. clearRect makes pixels transparent; it does not paint them white. The white you see here is the element’s CSS background.

Try: Move the transparent hole and compare it with drawing a white rectangle.

```javascript
ctx.fillStyle = "#246a73";
ctx.fillRect(30,30,250,150);
ctx.clearRect(80,70,90,70);
```

Check: clearRect makes pixels…

Answer: Transparent — Clearing removes pixel opacity. The background beneath the canvas becomes visible.

## 7. Build a path

beginPath starts a fresh current path. moveTo positions the drawing point without drawing a line. lineTo adds a segment; stroke paints the path. Starting a new path avoids accidentally repainting older segments.

Try: Add a fourth point to make a zigzag.

```javascript
ctx.beginPath();
ctx.moveTo(60,240);
ctx.lineTo(200,70);
ctx.lineTo(360,240);
ctx.strokeStyle = "#246a73";
ctx.lineWidth = 8;
ctx.stroke();
```

Check: What starts a fresh current path?

Answer: beginPath() — beginPath resets the current path. save stores drawing state, not a fresh path.

## 8. Close and fill shapes

closePath connects the current point back to the start of its subpath. fill paints the interior. A fill may implicitly close an open subpath for filling, but explicit closure makes the intended outline clear. Draw fill before stroke when you want a visible border.

Try: Change the triangle into a four-point diamond.

```javascript
ctx.beginPath();
ctx.moveTo(100,270);
ctx.lineTo(250,60);
ctx.lineTo(400,270);
ctx.closePath();
ctx.fillStyle = "#f1bd64";
ctx.fill();
ctx.strokeStyle = "#246a73";
ctx.lineWidth = 5;
ctx.stroke();
```

Check: Why call closePath before stroking this triangle?

Answer: To connect back to the start — closePath adds the final connection needed for the outlined closed shape.

## 9. Circles and radians

arc takes a center, radius, start angle and end angle, with an optional counterclockwise flag. Angles are in radians. A full turn is 2 × Math.PI. Use beginPath for each independent circle so paths do not join unexpectedly.

Try: Change the end angle to Math.PI to draw a half-circle shape.

```javascript
ctx.beginPath();
ctx.arc(280,180,100,0,Math.PI*2);
ctx.fillStyle = "#df7148";
ctx.fill();
```

Check: A full turn in radians is…

Answer: Math.PI * 2 — 2π radians is a complete turn; 360 is a degree measure.

## 10. Curves and control points

quadraticCurveTo uses one control point and an endpoint. The control point pulls the curve toward it; it is generally not a point the curve passes through. bezierCurveTo uses two control points for more flexible curves.

Try: Lower the control point’s y coordinate toward 180 and observe the flatter curve.

```javascript
ctx.beginPath();
ctx.moveTo(50,280);
ctx.quadraticCurveTo(300,-80,550,280);
ctx.lineWidth=7;
ctx.strokeStyle="#246a73";
ctx.stroke();
ctx.fillStyle="#df7148";
ctx.fillRect(296,16,8,8);
```

Check: A quadratic curve uses how many control points?

Answer: One — quadraticCurveTo uses one control point and one endpoint.

## 11. Color and transparency

fillStyle and strokeStyle accept CSS colors. globalAlpha multiplies the opacity of subsequent drawing, affecting fills, strokes and images. Restore it afterward if later shapes should be opaque. Overlapping transparent shapes blend with existing pixels.

Try: Try opacity values 0.2 and 0.8.

```javascript
ctx.fillStyle="#246a73";
ctx.fillRect(80,70,200,180);
ctx.globalAlpha=0.5;
ctx.fillStyle="#df7148";
ctx.fillRect(200,110,200,180);
ctx.globalAlpha=1;
```

Check: Does globalAlpha affect only the next command?

Answer: No, it remains until changed or restored — globalAlpha is drawing state, so it persists across later commands.

## 12. Gradients

Create a gradient, add color stops between 0 and 1, and assign it as a fillStyle or strokeStyle. A linear gradient’s endpoints describe its direction in the coordinate space when created. Radial gradients blend between two circles.

Try: Add a middle stop at 0.5.

```javascript
const gradient=ctx.createLinearGradient(60,0,500,0);
gradient.addColorStop(0,"#246a73");
gradient.addColorStop(1,"#f1bd64");
ctx.fillStyle=gradient;
ctx.fillRect(60,80,440,200);
```

Check: Where may a color-stop offset lie?

Answer: 0 to 1 — Gradient stops use normalized offsets from zero to one.

## 13. Text and alignment

Set font using CSS font syntax. fillText paints glyphs at an anchor; textAlign and textBaseline determine alignment around it. Canvas text is pixels, not selectable page text. Keep important information in HTML as well. measureText returns metrics useful for layout.

Try: Change textAlign to left and observe the anchor.

```javascript
ctx.font="bold 38px sans-serif";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillStyle="#246a73";
ctx.fillText("Hello, Canvas!",300,180);
```

Check: Is canvas text automatically selectable HTML text?

Answer: No — Canvas renders glyphs into pixels. Provide HTML equivalents for essential text.

## 14. Save and restore state

save pushes drawing state onto a stack; restore retrieves it. Styles, transforms and clipping are included. Pixels are not undone by restore, and the current path is not saved. Pair saves and restores to keep local changes from leaking into later drawings.

Try: Remove restore, compare the colors, then put it back.

```javascript
ctx.fillStyle="#246a73";
ctx.save();
ctx.fillStyle="#df7148";
ctx.fillRect(60,80,160,160);
ctx.restore();
ctx.fillRect(300,80,160,160);
```

Check: Does restore erase pixels already drawn?

Answer: No — restore restores context state, not the bitmap.

## 15. Translate and rotate

translate moves the origin; rotate turns the coordinate system around its current origin. To rotate a shape about its center, translate to that center and draw around (0,0). Transform order matters. Use save and restore around local transformations.

Try: Try a quarter turn with Math.PI/2.

```javascript
ctx.save();
ctx.translate(300,180);
ctx.rotate(Math.PI/6);
ctx.fillStyle="#246a73";
ctx.fillRect(-100,-50,200,100);
ctx.restore();
```

Check: rotate turns around…

Answer: The current origin — Translate the origin first when you want to rotate around a chosen point.

## 16. Scale and high-density screens

A high-density display can show more device pixels than CSS pixels. Use devicePixelRatio to size the backing bitmap and set a transform so drawing still uses logical units. Resizing clears state. This example uses a fixed 600 × 360 logical area and caps the ratio at 2 to limit memory.

Try: Compare device pixel dimensions with the 600 × 360 logical area.

```javascript
const ratio=Math.min(window.devicePixelRatio || 1,2);
canvas.width=600*ratio; canvas.height=360*ratio;
ctx.setTransform(ratio,0,0,ratio,0,0);
ctx.font="32px sans-serif";
ctx.fillStyle="#246a73";
ctx.fillText("Crisp logical coordinates",30,180);
```

Check: Why scale the context after increasing bitmap resolution?

Answer: To keep logical drawing units consistent — The transform maps logical coordinates to the higher-resolution bitmap.

## 17. Draw an image without a network

drawImage accepts sources such as an image, video or another canvas. Real images must be decoded before drawing. This example creates a small offscreen canvas locally, then scales it onto the main canvas; no remote asset is needed. imageSmoothingEnabled changes how scaled pixels are interpolated.

Try: Set imageSmoothingEnabled to true and compare the edges.

```javascript
const stamp=document.createElement("canvas");
stamp.width=32; stamp.height=32;
const s=stamp.getContext("2d");
s.fillStyle="#246a73"; s.fillRect(0,0,32,32);
s.fillStyle="#f1bd64"; s.fillRect(8,8,16,16);
ctx.imageSmoothingEnabled=false;
ctx.drawImage(stamp,80,60,240,240);
```

Check: What can drawImage use as a source?

Answer: Another canvas — Other canvas elements are valid image sources and useful for cached graphics.

## 18. Crop an image source

The nine-argument drawImage form uses a source rectangle followed by a destination rectangle. The first rectangle chooses pixels from the source; the second positions and scales them. This sample crops the orange half of a locally generated image.

Try: Change the source x from 50 to 0 to select the teal half.

```javascript
const source=document.createElement("canvas");
source.width=100; source.height=50;
const s=source.getContext("2d");
s.fillStyle="#246a73"; s.fillRect(0,0,50,50);
s.fillStyle="#df7148"; s.fillRect(50,0,50,50);
ctx.drawImage(source,50,0,50,50,180,80,200,200);
```

Check: The source rectangle controls…

Answer: Which source pixels are selected — Source coordinates crop the input; destination coordinates control placement and scale.

## 19. Clip to a shape

clip restricts future drawing to the current path. Clipping combines with any existing clipping region. Use save before clipping and restore afterward to return to the previous region. Clipping does not itself paint the path.

Try: Change the circular clipping region into a rectangle using ctx.rect.

```javascript
ctx.save();
ctx.beginPath(); ctx.arc(300,180,110,0,Math.PI*2); ctx.clip();
for(let x=0;x<600;x+=40){ctx.fillStyle=x%80===0?"#246a73":"#f1bd64";ctx.fillRect(x,0,40,360);}
ctx.restore();
```

Check: How do you return to the previous clipping region?

Answer: restore after a matching save — A saved context state includes the clipping region.

## 20. Composite overlapping drawings

globalCompositeOperation changes how new pixels combine with existing ones. source-over is the default. destination-out removes existing pixels where the new shape is drawn. Save and restore around special compositing so later drawings use the expected mode.

Try: Move the cutout and compare it with a white circle.

```javascript
ctx.fillStyle="#246a73"; ctx.fillRect(100,50,360,260);
ctx.save();
ctx.globalCompositeOperation="destination-out";
ctx.beginPath();ctx.arc(280,180,80,0,Math.PI*2);ctx.fill();
ctx.restore();
```

Check: destination-out can be used to…

Answer: Erase existing pixels with a shape — The source removes coverage from the destination, producing transparency.

## 21. Read and modify pixels

getImageData reads raw RGBA bytes. Each pixel occupies four entries: red, green, blue and alpha, each normally from 0 to 255. putImageData writes raw pixels and is not affected by the current transform. Frequent pixel reads can be expensive; use them when needed rather than for every ordinary shape.

Try: Set alpha to 100 for each copied pixel and compare opacity.

```javascript
ctx.fillStyle="#df7148";ctx.fillRect(50,50,200,200);
const image=ctx.getImageData(50,50,200,200);
for(let i=0;i<image.data.length;i+=4){
 const gray=Math.round((image.data[i]+image.data[i+1]+image.data[i+2])/3);
 image.data[i]=image.data[i+1]=image.data[i+2]=gray;
}
ctx.putImageData(image,320,50);
```

Check: How many RGBA entries describe one pixel?

Answer: Four — RGBA stores red, green, blue and alpha.

## 22. Animate with elapsed time

requestAnimationFrame schedules drawing before a browser repaint. Use its timestamp to calculate elapsed time; adding a fixed distance each frame changes speed across displays. Clear and redraw each frame. Stop preview removes the iframe and its animation. This example respects reduced-motion preference by showing a stationary frame.

Try: Change the speed from 100 logical units per second to 50.

```javascript
const reduced=matchMedia("(prefers-reduced-motion: reduce)").matches;
let start;
function frame(time){
 if(start===undefined)start=time;
 const x=reduced?300:30+((time-start)/1000*100)%540;
 ctx.clearRect(0,0,600,360);
 ctx.beginPath();ctx.arc(x,180,24,0,Math.PI*2);
 ctx.fillStyle="#246a73";ctx.fill();
 if(!reduced)requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
```

Check: Why use elapsed time?

Answer: Consistent motion speed across frame rates — Time-based movement avoids tying animation speed to the number of frames rendered.

## 23. Pointer coordinates and hit testing

Pointer events support mouse, pen and touch. Convert client coordinates using getBoundingClientRect and the ratio between logical dimensions and displayed dimensions. Canvas shapes are not DOM elements, so hit testing is your responsibility. For this circle, compare squared distance with squared radius. A real control also needs an accessible HTML alternative.

Try: Click inside and outside the circle, then change its radius in both drawing and hit test.

```javascript
function draw(color){ctx.clearRect(0,0,600,360);ctx.beginPath();ctx.arc(300,180,80,0,Math.PI*2);ctx.fillStyle=color;ctx.fill();}
draw("#246a73");
canvas.addEventListener("pointerdown",e=>{
 const r=canvas.getBoundingClientRect();
 const x=(e.clientX-r.left)*600/r.width;
 const y=(e.clientY-r.top)*360/r.height;
 draw((x-300)**2+(y-180)**2<=80**2?"#df7148":"#246a73");
});
```

Check: Why convert pointer coordinates?

Answer: CSS display size may differ from logical size — A pointer position is in viewport CSS pixels; map it to the drawing coordinate system.

## 24. Final project: a mini chart

Combine rectangles, labels and meaningful structure to make a small chart. Canvas pixels alone do not expose data to assistive technology, so this example also creates a visible HTML data table. Keep the source data separate from drawing commands. Before shipping, review contrast, responsive layout, keyboard access, memory use and whether SVG or ordinary HTML would serve the task better.

Try: Change one value in the data and verify both the chart and table update.

```javascript
const values=[40,75,55,90];
const labels=["Mon","Tue","Wed","Thu"];
ctx.font="16px sans-serif";
values.forEach((v,i)=>{
 const x=65+i*130;
 ctx.fillStyle="#246a73";ctx.fillRect(x,300-v*2.5,65,v*2.5);
 ctx.fillStyle="#263746";ctx.fillText(labels[i],x,325);ctx.fillText(String(v),x,290-v*2.5);
});
const table=document.createElement("table");
const caption=table.createCaption();caption.textContent="Daily activity values";
labels.forEach((label,i)=>{const row=table.insertRow();const th=document.createElement("th");th.scope="row";th.textContent=label;row.append(th);row.insertCell().textContent=values[i];});
document.body.append(table);
```

Check: Why include the HTML table?

Answer: To expose the chart data as structured text — A visible table provides an accessible, text-based representation of the chart data.