Introduction
This article is part of the series "Mastering HTML Canvas: From Basics to Beyond", designed to take you from the fundamentals of HTML Canvas to advanced techniques and real-world applications. This series aims to guide you through interactive examples, case studies, and practical projects so that you can master rendering with HTML and JavaScript.
In this first article, we’ll focus on the basics of JavaScript Canvas—how to set up a canvas, draw shapes, manipulate colors, and render text.
What is a <canvas>
and why do I need to know about this?
I could try and explain what a <canvas>
is but I am pretty sure the MDN Docs does it much better. So here is a quote from the site:
The Canvas API provides a means for drawing graphics via JavaScript and the HTML element. Among other things, it can be used for animation, game graphics, data visualization, photo manipulation, and real-time video processing.
The Canvas API largely focuses on 2D graphics. The WebGL API, which also uses the element, draws hardware-accelerated 2D and 3D graphics.
So long story short, if you want to have anything from a simple map integration to highly specific advanced graphics on your website, you will encounter canvas one way or another.
Here are some examples of everyday features that might use it:
-
Maps & Navigation
- Google Maps, Apple Maps, OpenStreetMap → Smooth zooming, panning, and tile rendering.
- Restaurant locators → Many business websites use Canvas-powered maps to display store locations.
-
Image & Video Effects
- Image Cropping & Editing → Simple online image crop tools (like profile picture editors).
- Lazy Loading & Blur Effects → Some websites use Canvas to render blurred placeholders before images fully load.
- Video Overlays & Watermarks → Websites that allow adding watermarks or timestamps to videos.
-
Charts & Graphs
- Website Analytics Dashboards → Platforms like Google Analytics use Canvas to render real-time graphs.
- Stock Market & Crypto Charts → Interactive price graphs on finance websites.
-
Animations & UI Enhancements
- Particle Effects → Background effects like falling snowflakes, floating particles, or cursor trails.
-
Interactive Product Displays
- 360° Product Viewers → Online stores like IKEA use Canvas to create rotatable product previews.
Basics
No matter what your application is you will need two things:
- A
<canvas>
element in your HTML - Some JavasScript to draw things on the canvas
Let's draw a green triangle:
HTML
<canvas id="myCanvas"></canvas>
JS
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 200);
ctx.lineTo(150, 50);
ctx.lineTo(250, 200);
ctx.closePath();
ctx.fillStyle = "green";
ctx.fill();
Result
I remember when I was first introduced to this I was thinking:
- Why do you need 9 lines of JS to draw something as simple as a triangle?
- I think I understand what those lines are doing... or do I?
Depending on where you are in your learning journey you might have similar thoughts. So let's unpack this simple shape and discuss why you need all this, just to draw a green triangle.
1. Getting the canvas element
const canvas = document.getElementById("myCanvas");
With this, we are finding the <canvas>
element based on its ID and saving it to a variable. As you might noticed it will be used only once after, to get its CanvasRenderingContext2D.
What is a canvas
- The
<canvas>
element itself is just a container—it doesn’t inherently render anything. -
CanvasRenderingContext2D
is an interface that provides methods to draw on the canvas. - Think of
<canvas>
as a blank sheet of paper, whileCanvasRenderingContext2D
is like the tools (pens, brushes, erasers) that allow you to create visuals. - The
<canvas>
is essentially an empty bitmap (a rectangular grid of pixels).
How the canvas rasterization works
- The canvas is a grid of pixels stored in memory.
- Every drawing operation modifies pixel color values in this grid.
- Internally, each pixel has RGBA values (Red, Green, Blue, Alpha).
Canvas resolution & Scaling
When you create a <canvas>
element, it has an internal bitmap that determines how many pixels it uses for rendering. The default resolution is a 1:1 pixel ratio, meaning:
- The CSS width and height of the
<canvas>
match the bitmap resolution. - One canvas unit corresponds to one device pixel.
However, the canvas rendering resolution and its CSS size are independent. This means you can scale the internal pixel grid separately from how big the canvas appears on the page.
If you want to know more details visit the Docs | scale() website or look out for upcoming articles in this series.
2. Getting the canvas's 2D rendering context
const ctx = canvas.getContext("2d");
- When you create a
<canvas>
in HTML, it remains an empty bitmap until you retrieve a context from it - Internally, the canvas maintains a pixel buffer (rasterized grid) where drawing operations modify pixel values.
-
getContext("2d")
creates and returns a 2D rendering context for the canvas - and binds the JavaScript drawing API to the bitmap memory
What other contexts exist besides "2d"?
While CanvasRenderingContext2D
is the most commonly used context, <canvas>
can support different drawing modes:
Standard Contexts:
- "2d" → The standard 2D drawing context (basic shapes, images, text, etc.).
- "webgl" → Uses WebGL for low-level GPU rendering, enabling 3D graphics.
Other Contexts:
- "webgl2" → An improved WebGL API with more features.
- "bitmaprenderer" → Renders an ImageBitmap directly to the canvas for efficient offscreen rendering.
- "webgpu" → A new GPU API for advanced rendering (experimental)
3. Starting a new path
ctx.beginPath();
Internally, the context has a path buffer where new points are stored before rendering. Using beginPath()
:
- Resets the current path
- It clears any previous drawing commands but does not erase the canvas bitmap
- No pixels are changed yet, this only starts defining a shape
4. Moving the "pen" to the starting point
ctx.moveTo(50, 200);
The canvas keeps track of the current drawing position (like lifting a pen and placing it somewhere else on paper).
With this command, we are moving this position to the position (50, 200).
Coordinate system explained
As I previously mentioned, the canvas is technically a grid. The origin of this grid is positioned in the top left corner at coordinate (0,0). All elements are placed relative to this origin.
So, when we are moving our "pen" to (50, 200) we are telling the context that the start position of our triangle is 50px from the left and 200px from the top.
Why is (0,0) at the Top Left?
- Historically, screen buffers were designed for top-to-bottom rasterization.
- Most image formats (BMP, PNG, etc.) store pixels row by row from top to bottom.
- The web follows a flow-based layout (text, divs, images are arranged from top-left downward).
It makes sense for to follow the same model.
- Example: When positioning elements with position: absolute, (0,0) is at the top-left corner of the viewport.
What if You Want a Bottom-Left Origin?
If you want a traditional Cartesian coordinate system (with Y increasing upwards), you can flip the Y-axis:
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
5. Drawing the edges of the triangle
ctx.lineTo(150, 50);
ctx.lineTo(250, 200);
"Drawing" the first line (lineTo(150, 50))
- A line is plotted from (50, 200) to (150, 50), but it is not drawn yet.
- This is stored in the path buffer, not the bitmap.
"Drawing" the second line (lineTo(250, 200))
- Another line is plotted from (150, 50) to (250, 200), still not drawn yet.
- The path buffer now contains two line segments, forming an incomplete triangle.
You might be thinking why aren't we defining 3 lines as a triangle has 3 edges, right? And you are right. However, we don't necessarily need the 3rd edge thanks to closePath()
6. Closing the shape
ctx.closePath();
This attempts to add a straight line from the current point to the start of the current sub-path. If the shape has already been closed or has only one point, this function does nothing.
In our case:
- This connects the last point to the first point automatically.
- Now the path buffer contains a closed triangle.
- Still, nothing is drawn to the canvas bitmap yet.
So technically, we can get the same triangle using:
ctx.moveTo(50, 200);
ctx.lineTo(150, 50);
ctx.lineTo(250, 200);
ctx.lineTo(50, 200);
ctx.closePath(); // Does nothing
7. Setting the fill color
ctx.fillStyle = "green";
The fillStyle property is stored in the context state. It tells the canvas: "If you fill something next, use this color."
- No changes to any pixel yet
8. Filling the shape
ctx.fill();
Now, every pixel inside the triangle is updated in the bitmap.
The canvas checks which pixels are inside the closed path and changes their RGBA values to green.
The fill does not affect outlines, only the interior of the shape.
And now, we made ourselves a triangle.
Fill vs Stroke
Now that we explored the basics with a fully filled-in green triangle you might think "Okay, but what if I want just outlines of a shape or both outlines and fills?"
This is where the distinction between fill and stroke comes into play.
What is fill?
-
fill()
is used to paint the interior of a closed path. It completely covers all the pixels inside the path using the currentfillStyle
. - Think of it as pouring paint into the shape.
- Works only for closed paths—open shapes cannot be filled.
What is stroke?
-
stroke()
is used to draw the outline of a path based on the currentstrokeStyle
andlineWidth
. - It works for both open and closed paths.
- Think of it as tracing the shape with a pen.
You can combine both fill and stroke on the same shape to create a filled shape with an outline. To do this, ensure you set the appropriate styles before calling fill()
and stroke()
.
And if I don't like green?
The good news is that the canvas API gives you complete control over colors, allowing you to customize your drawings however you like. You can even use gradients and patterns.
Setting Colors
Canvas uses two main properties to define colors:
-
fillStyle
: Determines the color or style used to fill shapes. -
strokeStyle
: Determines the color or style used for outlines.
If you want to know more about gradients and patterns visit the MDN Docs website or look out for upcoming articles in this series.
How do I draw multiple things?
If you want to draw multiple shapes on a canvas, it’s important to manage your paths carefully. You will have to stop and think about what you want to achieve.
The beginPath()
is essential for separating shapes, as it clears the current path buffer and starts a new one. Without beginPath()
, all your drawing commands will be treated as part of a single path, making it impossible to style or render the shapes independently.
If you want to create separate shapes, you must:
- Use
beginPath()
to start a new path. - Call
fill()
orstroke()
after completing each shape to render it to the canvas. - Remember that without
fill()
orstroke()
, your previous shape remains in the path buffer and can be unintentionally modified by subsequent commands.
Example: Two distinct triangles
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// First Triangle
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// First Triangle
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(100, 50);
ctx.lineTo(150, 150);
ctx.closePath();
ctx.fillStyle = "green";
ctx.fill();
// Second Triangle
ctx.beginPath();
ctx.moveTo(200, 150);
ctx.lineTo(250, 45);
ctx.lineTo(295, 150);
ctx.closePath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
ctx.stroke();
Result:
Example: One path (Shared style)
Alternatively, you can draw multiple shapes without using beginPath()
. However, this results in both shapes sharing the same style and also could be either filled or stroked:
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
// First Triangle
ctx.moveTo(50, 150);
ctx.lineTo(100, 50);
ctx.lineTo(150, 150);
ctx.closePath();
// Second Triangle
ctx.moveTo(200, 150);
ctx.lineTo(250, 50);
ctx.lineTo(300, 150);
ctx.closePath();
ctx.strokeStyle = "purple";
ctx.lineWidth = 2;
ctx.stroke();
Result:
Key takeaways:
- Use
beginPath()
when you want shapes to be independent, with unique styles or rendering methods (fill
,stroke
). - Without calling
fill()
orstroke()
after completing a shape, subsequent drawing commands will override the earlier shape because it remains part of the same path. - If you want multiple shapes to share the same style, you can omit additional
beginPath()
calls and render them together as a single path.
Do I have to draw each shape using paths?
Not necessarily. There are some prebuilt methods already available as part of the Canvas API.
Here are some of the built-in methods I think might be useful to know:
1. fillRect(x, y, width, height)
:
Draws a filled rectangle at the specified position and size.
Automatically uses the current fillStyle
.
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 50); // Draws a blue rectangle
2. strokeRect(x, y, width, height)
:
Outlines a rectangle using the current strokeStyle
and lineWidth
.
ctx.strokeStyle = "red";
ctx.lineWidth = 3;
ctx.strokeRect(200, 50, 100, 50); // Draws a red rectangle outline
3. clearRect(x, y, width, height)
:
Clears a rectangular area of the canvas, making all the pixels within it fully transparent.
Commonly used to erase parts of the canvas or reset it entirely.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clears the entire canvas
ctx.clearRect(100, 50, 200, 100); // Clears only a section of the canvas
4. roundRect(x, y, width, height, radius)
:
Draws a rectangle with rounded corners.
Where radius
can be a single number (for uniform corner rounding) or an object specifying individual corners ({tl, tr, br, bl} for top-left, top-right, bottom-right, and bottom-left).
ctx.beginPath();
ctx.roundRect(50, 150, 100, 50, 10); // Uniform 10px corner radius
ctx.fillStyle = "purple";
ctx.fill();
ctx.beginPath();
ctx.roundRect(200, 150, 100, 50, { tl: 20, tr: 10, br: 30, bl: 0 }); // Custom corner radius
ctx.strokeStyle = "orange";
ctx.lineWidth = 3;
ctx.stroke();
5. arc(x, y, radius, startAngle, endAngle, counterclockwise)
:
Draws a circle or arc.
Use startAngle = 0
and endAngle = 2 * Math.PI
to draw a full circle.
ctx.beginPath();
ctx.arc(150, 300, 40, 0, Math.PI * 2); // Full circle
ctx.fillStyle = "green";
ctx.fill();
What about texts?
Canvas API got you covered! You can write text on your canvas, customize its font, size, and alignment, and even apply styles like gradients or patterns.
Basics of Drawing Text
Canvas has two primary methods for rendering text:
-
fillText(text, x, y)
: Draws filled text at the specified position. -
strokeText(text, x, y)
: Draws the outline of the text using the currentstrokeStyle
andlineWidth
.
ctx.font = "24px Arial"; // Set font size and family
ctx.fillStyle = "blue"; // Set text color
ctx.fillText("Hello, Canvas!", 50, 50); // Draw filled text
ctx.strokeStyle = "red"; // Set stroke color
ctx.lineWidth = 1;
ctx.strokeText("Hello, Canvas!", 50, 100); // Draw outlined text
Result:
Concluision
I hope this article helped you understand the basics of HTML Canvas. While it might have felt like a lot to cover, we’ve only scratched the surface of what Canvas can do. But don’t worry, what you’ve learned here is more than enough to start creating some fun projects.
In the next article of the "Mastering HTML Canvas: From Basics to Beyond" series, I’ll take you through building a fun 2048 game using mostly the concepts we explored in this article.
If you’re curious about diving deeper into HTML Canvas, stay tuned for upcoming articles in this series:
Mastering HTML Canvas: From Basics to Beyond
- Getting Started with HTML Canvas: The Basics You Need to Know
- Use of the Basics: Case Study - Making a 2048 Game with Just JS Canvas (Upcoming)
- Advanced Topics of JS Canvas
- Use of Advanced Canvas: Interactive Page to Explore Canvas Features
- Exploring Canvas Libraries: Rebuilding the Interactive App with Konva, Fabric.js & More
Thank you for taking the time to read this article! If you have any specific topics you’d like me to cover, questions about Canvas, or just want to share your feedback, feel free to leave a comment below. Your thoughts and ideas are always welcome.
Have a lovely day, and see you in the next chapter!
Top comments (0)