# Chapter 2: Intro to CanvasRenderingContext2D (/guides/handmade-web-games/intro-to-canvasrenderingcontext2d)



Picking up from Chapter 1, you now have a canvas on the page and a 2D context to draw into. If you're looking at the size of the scrollbar on this page and wondering what you've gotten yourself into--don't worry. This chapter is a reference as much as a tutorial, so skip to whatever you need.

With that said, there are a few essentials you should know first.

***

## Canvas intro, basic concepts, and setup [#canvas-intro-basic-concepts-and-setup]

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>
      Chapter 1 left off with starter code and a simple `fillRect` draw call. Remove the blue rectangle and what is left is the necessary code that must precede all of the other examples in this chapter.

      <details>
        <summary>
          <strong>What does each line do?</strong>
        </summary>

        The first line creates a canvas element, exactly like writing `<canvas>` in `index.html` would have done.

        This canvas element hasn't actually been *mounted* anywhere yet, though. So immediately after that, we append the canvas to the `<body>` element of our html document.

        The next line determines which drawing API we'll be using. Options other than `"2d"` include:

        * `"webgl"` (often used for 3D games and animations, or anything that requires shaders)
        * `"webgpu"` (many of the same use cases as `webgl`, but with a lower-level and newer API, so [browser support](https://caniuse.com/webgpu) <small className="opacity-75">\[caniuse]</small> is limited)
        * `"imagebitmaprenderer"` (a very specialized tool to get frames rendered by a worker thread efficiently into the main thread's canvas)

        We'll be sticking with `"2d"`, which gives us all of the functionality you see in the examples throughout this chapter, including `fillRect` which you have already used.

        You'll also notice the `!` non-null assertion at the end of this line. `canvas.getContext("2d")` can return `null` *ONLY IF* the canvas has already been set up for one of the other contexts above. So you can't, for example, change a `webgl` canvas into a `webgpu` canvas on the fly. The TypeScript compiler isn't able to statically determine that we won't be doing that, but *we* know that, so the `null` case is not a concern.

        The final line with [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) <small className="opacity-75">\[MDN]</small> provides the current dimensions of our canvas. `bounds` will be an object with the properties `top`, `right`, `bottom`, `left`, `x`, `y`, `width`, and `height`. We'll only need those last two properties in this chapter.
      </details>
    </>
  </slot>
</Scrollycoding>

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>
      To begin, draw text with `fillText`.

      Notice that `fillText` takes only a string and position as arguments, yet the text renders with a font and color. Those come from state that is set on `ctx` at the moment it runs--here, `"32px sans-serif"` and `"deepskyblue"`.
    </>
  </slot>

  <slot path="steps">
    <>
      Yes, shared mutable state 😱

      Any property you set on the context (`fillStyle`, `lineWidth`, `font`, for example) stays set until something changes it. Here, `fillRect` inherits the same `fillStyle` that colored the text blue.
    </>
  </slot>

  <slot path="steps">
    The two numbers after "Hello, world!" are the `x` and `y` coordinates, measured in pixels from the left edge and pixels from the top. `y` increases *downward*, unlike the coordinates you learned in school.
  </slot>

  <slot path="steps">
    Paint operations run in order, so whatever was drawn last sits on top.
  </slot>

  <slot path="steps">
    <>
      If you're following along and your canvas looks blurry, your screen can handle more pixels than the canvas has at its default pixel density. A display with a `devicePixelRatio` of `2` has four physical pixels for every CSS pixel. But by default the canvas doesn't automatically scale itself for high-density displays, resulting in a stretched, blurry image instead.

      The fix is the following standard boilerplate, which we'll cover in depth at the end of the chapter.
    </>
  </slot>
</Scrollycoding>

***

## CanvasRenderingContext2D API [#canvasrenderingcontext2d-api]

Finally, the fun part. Here's a curated cheatsheet with the Context2D functionality you should know about for games.

### Paths and shapes [#paths-and-shapes]

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    As you saw above, use it to draw rectangles.
  </slot>

  <slot path="steps">
    Draw borders around rectangles. The stroke is centered along the edge of the rectangle, so adjust sizing according to `lineWidth` as needed.
  </slot>

  <slot path="steps">
    Use `ctx.setLineDash([length, gap])`. Change the dash phase offset like `ctx.lineDashOffset = 4` or animate it for a marching ants effect. To reset, pass an empty array: `ctx.setLineDash([])`. More examples on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#some_common_patterns).
  </slot>

  <slot path="steps">
    <>
      A circle is just an arc that extends 2π radians. So if you arc from `0` to `Math.PI * 2` with some radius, you'll get a circle.

      `ctx.arc(x, y, radius, startAngle, endAngle)`

      <Callout type="idea">
        If you find that you're drawing a lot of circles, `ctx.arc` is a clunky way to build them each time. A tiny helper function can aid readability.

        ```js
        function circle(ctx, x, y, radius) {
          ctx.beginPath();
          ctx.arc(x, y, radius, 0, Math.PI * 2);
          ctx.fill();
        }
        ```
      </Callout>
    </>
  </slot>

  <slot path="steps">
    <>
      The circle example above introduced a new concept: paths!

      You can draw your own shapes with the following path methods:

      ```ts twoslash
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d")!;
      const bounds = new DOMRect(0, 0, 376, 180);
      const x: number = 0;
      const y: number = 0;
      const w: number = 0;
      const h: number = 0;
      const controlX: number = 0;
      const controlY: number = 0;
      const x1: number = 0;
      const y1: number = 0;
      const x2: number = 0;
      const y2: number = 0;
      const rx: number = 0;
      const ry: number = 0;
      const radius: number = 0;
      const startAngle: number = 0;
      const endAngle: number = 0;
      const radii: number = 0;
      const rotation = 0;
      // ---cut---
      // various path-related methods:
      ctx.beginPath(); // start drawing a path
      ctx.moveTo(x, y); // jumps to a new position
      ctx.lineTo(x, y); // draw a line to a coordinate
      ctx.closePath(); // line straight back to path origin
      ctx.stroke(); // outline the path
      ctx.fill(); // fill the path

      // shapes:
      ctx.rect(x, y, w, h); // add rectangle to path
      ctx.roundRect(x, y, w, h, radii); // rounded rectangle
      ctx.ellipse(x, y, rx, ry, rotation, startAngle, endAngle);

      // curved lines:
      ctx.arc(x, y, radius, startAngle, endAngle);
      ctx.arcTo(x1, y1, x2, y2, radius);
      ctx.quadraticCurveTo(controlX, controlY, x, y);
      ctx.bezierCurveTo(x1, y1, x2, y2, x, y);
      ```
    </>
  </slot>

  <slot path="steps">
    By default corners are sharp (`lineJoin="miter"`) but you can round them with `lineJoin="round"` or bevel them with `lineJoin="bevel"`.
  </slot>

  <slot path="steps">
    Line caps are used for the ends of a line that are visible when you don't close a path. `"square"` is similar to the default, `"butt"`, except that it extends the line on each end by half the `lineWidth`.
  </slot>

  <slot path="steps">
    <>
      While `ctx.arc` is commonly used for circles, it can also be useful for progress indicators.

      `ctx.arc(x, y, radius, startAngle, endAngle)`
    </>
  </slot>
</Scrollycoding>

### Text [#text]

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Let's return to our hello world example.
  </slot>

  <slot path="steps">
    Like many of the other `fill*` APIs, there's a `stroke` equivalent for text.
  </slot>

  <slot path="steps">
    <>
      Typography enthusiasts should be excited to learn that you can read many font metrics directly with `ctx.measureText("...")`. Unfortunately, some metrics such as `emHeightAscent` cannot currently be used due to [poor browser support](https://caniuse.com/mdn-api_textmetrics_emheightascent) <small className="opacity-75">\[caniuse]</small>.

      <details>
        <summary>
          <strong>`measureText` returns...</strong>
        </summary>

        * `fontBoundingBoxAscent` - How many px above the baseline the tallest letter (or highest ascender) can possibly go *font-wide* (for any text)
        * `fontBoundingBoxDescent` - How many px below the baseline the lowest descender goes *font-wide*
        * `actualBoundingBoxAscent` - For the *specific text being measured*, how many px above the baseline does it go
        * `actualBoundingBoxDescent` - For the *specific text being measured*, how many px below the baseline do descenders go
        * `actualBoundingBoxLeft` and `actualBoundingBoxRight` - Distances from the origin to each edge. When you draw text starting at some `x` origin, the actual characters can be drawn to the left or the right of it depending on the `textAlign` and whether they're italicized. `actualBoundingBoxLeft` and `actualBoundingBoxRight` give you the distance from `x` to the true left (or right) edge.
        * `width` - Confusingly, is &#x2A;not the sum of `actualBoundingBoxLeft + actualBoundingBoxRight`*. `width` refers to the number of pixels that a cursor would advance when typing the measured text. Each glyph has an "advance" value, and `width` is their sum which also takes into account kerning. Another way to think about this is that it is the right property to use for a cursor if you're implementing a text editor because it's a *layout value*, not a precise measurement of the pixels of the text.
        * some additional properties: `emHeightAscent`/`emHeightDescent` that aren't widely supported but measure the distance to the top and bottom of the letter `M`, and `ideographicBaseline`/`hangingBaseline` typically for internationalization.
      </details>
    </>
  </slot>

  <slot path="steps">
    By default text is expected to be aligned to the bottom-left. However, you can see that the descenders of the comma and letter `g` are clipped. Using the right vertical alignment can fix this.
  </slot>

  <slot path="steps">
    Use `textBaseline` for vertical alignment.
  </slot>

  <slot path="steps">
    Use `textAlign` for horizontal alignment. Possible values are `left`/`right`/`center`/`start`/`end`. The default is `start`, which maps to `left` in left-to-right locales and maps to `right` in right-to-left locales.
  </slot>

  <slot path="steps">
    You can specify these properties (and [others](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font) <small className="opacity-75">\[MDN]</small>) in the `ctx.font` as well.
  </slot>

  <slot path="steps">
    <>
      You have a few options:

      1. Download a font and use [`@font-face` in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face) <small className="opacity-75">\[MDN]</small>, allowing the browser to decide when it loads and whether to show a fallback during loading.

      <details>
        <summary>
          <strong>Option 1 deep-dive</strong>
        </summary>

        Option 1 requires that we return to the `index.html` from Chapter 1. For this example we'll use the [variable font](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) [Doto](https://fonts.google.com/specimen/Doto).

        One benefit to option 1 over option 3 is that the font can start downloading even before your JavaScript bundle is parsed by the browser, as long as it's also applied to some text outside of your canvas. (You have to trick the browser by creating a hidden text element that uses it.) On the other hand, the main trade-off with this approach is that you'll sometimes see a brief flash of text in a fallback font before the font has loaded, especially on the first visit.

        *Even if you change `font-display: swap` to `font-display: block`*, browsers will often still briefly display a fallback font, especially to users with slower internet. `font-display` is a hint to the browser, not an instruction.

        ```html index.html
        <!doctype html>
        <html lang="en">
          <head>
            <meta charset="utf-8" />
            <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Your Game</title>
            <style>
              @font-face {
                font-family: "Doto";
                font-style: normal;
                font-weight: 100 900; /* range of weights */
                font-display: swap;
                src: url(/fonts/doto.ttf) format("truetype");
              }
              p {
                color: transparent;
                font-family: "Doto";
              }
              canvas {
                position: fixed;
                inset: 0;
              }
            </style>
          </head>
          <body>
            <p>Hidden text to trick the browser into downloading the font asap</p>
            <script type="module" src="/src/main.ts"></script>
          </body>
        </html>
        ```
      </details>

      2. Use a service that does (1) for you, like [Google Fonts](https://fonts.google.com/). (Don't do this.)

      <details>
        <summary>
          <strong>Why not?</strong>
        </summary>

        Creating a dependency on a third party service can be tempting when it offers convenience, but this has downsides. First of all, you now require the service to always host your font at this exact URL. Suppose Google is no longer licensed to serve your game's font. Or perhaps they decide to charge for this service at some point. Or maybe someday their servers have downtime. Because the font is not a part of your game's bundle, in any of these scenarios you're simply out of luck. We want to guarantee that your game is playable with all of its fonts 20 or even 100 years from now.

        The cross-site network request also complicates things when we work on preparing your game to work offline. Self-hosting your fonts and other assets makes offline mode dead simple.

        For these and many other reasons, option 2 is a poor choice.
      </details>

      3. For precise control over the timing of the loading of your font, download it and load with JavaScript. This is typically a solid choice for games, so it's our pick in this guide, but it does load the font sequentially after the JavaScript bundle has been parsed, rather than taking advantage of parallel loading like option 1.

      To load a custom font in JavaScript, you'll first need to download a font file (typically `.ttf`, `.otf`, or `.woff2`) from a source like [Google Fonts](https://fonts.google.com/) or [itch.io](https://itch.io/game-assets/tag-fonts). If you have a choice, `woff2` is usually preferred for its smaller file sizes.

      You'll then asynchronously load the font. See MDN for explanations of the global [`FontFace`](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace) constructor and [`document.fonts.add`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/add).
    </>
  </slot>

  <slot path="steps">
    <>
      Although the previous code works, you'll probably see a flash of unstyled text ([FOUT](https://fonts.google.com/knowledge/glossary/fout)) in a fallback font while the custom font initially loads.

      One option is to simply render nothing until the font has loaded. You can decide for your project whether a fallback font or no text at all while it loads feels better.

      One way to make these loading states invisible to users is to make the text on your first screen a lightweight image inlined into your bundle, and display that while your custom fonts load in the background.
    </>
  </slot>
</Scrollycoding>

### Opacity and blending [#opacity-and-blending]

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    `fillStyle` and `strokeStyle` accept a wide variety of color formats. Many of these support an Alpha channel, which you can use to control the opacity of whatever you are painting.
  </slot>

  <slot path="steps">
    <>
      Often, simply using colors with an alpha channel for opacity won't be sufficient.

      Fading in and out an image asset is one case where that approach breaks down.

      Coordinating translucency changes across many different shapes, especially during a fade animation, is another example where the previous technique would be a pain.

      For these and many other uses, we have `globalAlpha`.
    </>
  </slot>

  <slot path="steps">
    <>
      You can save the full state of the Context2D at any time with `ctx.save()`, then restore it with `ctx.restore()`.

      <details>
        <summary>
          <strong>Why do this?</strong>
        </summary>

        Subtle bugs can arise when you mutate the shared state in the canvas Context2D. Imagine that you have `ctx.lineWidth = 5` deep within some nested function call. Then you call that function in another place and suddenly a bunch of unrelated shapes which had expected to inherit a different `lineWidth` are now incorrectly using `5`.

        A clunky solution to this might be to collect the "before" values for every property that is updated. Then change those properties, execute the paint, and restore each one back to its original value. When you're doing that potentially thousands of times across your code, this approach becomes a burden. Thankfully, we have a much more elegant option.

        Enter `save()` and `restore()`.

        `ctx.save()` takes a snapshot of the entire current state of the context and pushes it onto a stack. `ctx.restore()` pops the top item off of this state stack and restores the values back to what they were at the time of the snapshot being saved.

        You can save and restore multiple times and in any order. Just remember--like closing your parentheses in code--the number of `ctx.save()` calls MUST equal the number of `ctx.restore()` calls by the end of each animation frame or you'll run into problems.
      </details>
    </>
  </slot>
</Scrollycoding>

### Images [#images]

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>
      [`ctx.drawImage(image, x, y)`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) <small className="opacity-75">\[MDN]</small> paints an image with its top-left corner at `(x, y)` drawn at its intrinsic size.

      <Callout type="info">
        Just like fonts, images load asynchronously as soon as `src` is set. If you want to show a spinner while waiting until the image can be shown, you can `await` its `decode` promise, which resolves not just after the image has loaded, but after the bitmap is in memory and ready to be rendered.

        ```ts twoslash
        async function loadImage(src: string) {
          const image = new Image();
          image.src = src;
          await image.decode();
          return image;
        }
        ```
      </Callout>

      <small>
        *Pixel art image credit:
        [ansimuz](https://ansimuz.itch.io/underwater-fantasy-pixel-art-environment)*
      </small>
    </>
  </slot>

  <slot path="steps">
    <>
      The same `drawImage` method has another form that accepts a destination width and height. The entire image is mapped onto the rectangle defined by `(x, y, width, height)`, so the original aspect ratio is ignored.

      ```tsx
      ctx.drawImage(image, x, y, width, height);
      ```
    </>
  </slot>

  <slot path="steps">
    <>
      The third, final form that `drawImage` supports uses nine arguments to crop according to a source rectangle then scale and draw according to a destination rectangle. This is super useful for spritesheets.

      Nine-argument function calls are a lot to look at, so the key is to think of the two rectangles separately:

      * The source rectangle `(sx, sy, sw, sh)` (in image pixels) defines which parts of the source image to use.
      * The destination rectangle `(x, y, w, h)` (on the canvas) defines where to paint the source image.
    </>
  </slot>

  <slot path="steps">
    To contain the full image within the canvas (like [`object-fit: contain`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/object-fit) <small className="opacity-75">\[MDN]</small>), figure out whether the height or width will be the smaller factor, then scale uniformly according to that factor and center the image. Since we're drawing the *entire* source image, we only need the five-argument form of `drawImage`.
  </slot>

  <slot path="steps">
    <>
      For `object-fit: cover` behavior, figure out which dimension needs the larger scale factor, then derive the crop dimensions. The source rect starts at `(0, 0)`, so extra pixels are chopped off the right or bottom edges.

      This requires the nine-argument `drawImage` function signature.

      <details>
        <summary>
          <strong>Center-based origin for `object-fit: cover`</strong>
        </summary>

        Instead of lopping off pixels from just the bottom/right, you can anchor based on the center of the image with the following code.

        ```ts
        import imageUrl from "./assets/example.png";
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d")!;
        const image = new Image();
        image.src = imageUrl;

        const bounds = canvas.getBoundingClientRect();
        const { width, height } = bounds;
        const nw = image.naturalWidth;
        const nh = image.naturalHeight;
        const scale = Math.max(width / nw, height / nh);
        const sw = width / scale;
        const sh = height / scale;
        const sx = (nw - sw) / 2;
        const sy = (nh - sh) / 2;
        const source = { x: sx, y: sy, w: sw, h: sh };
        const dest = { x: 0, y: 0, w: width, h: height };
        ctx.drawImage(
          image, // source bitmap
          source.x,
          source.y,
          source.w,
          source.h,
          dest.x,
          dest.y,
          dest.w,
          dest.h,
        );
        ```
      </details>
    </>
  </slot>

  <slot path="steps">
    The pixel art in the previous demo looks bad! To scale images up sharply, you'll also need to set `ctx.imageSmoothingEnabled = false` to disable antialiasing.
  </slot>
</Scrollycoding>

### Filters [#filters]

The [filter](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter) <small className="opacity-75">\[MDN]</small> property allows you to apply post-processing effects such as blur, contrast adjustment, and hue rotation. Filters [still don't work in Safari](https://caniuse.com/mdn-api_canvasrenderingcontext2d_filter) <small className="opacity-75">\[caniuse]</small> (including iOS Safari), so you may want to hold off on applying them for now.

<Callout title="ctx.filter is expensive!!" type="warn">
  **Use sparingly**, and ideally not inside of your game loop.
</Callout>

Before you reach for `ctx.filter`, ask yourself: can you achieve this effect in another way? Pre-process your assets when you can rather than applying post-processing effects at runtime. With that out of the way, these effects are quite fun to look at--here they are:

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Applies a Gaussian blur to all following paints. &#x2A;This will get your fans humming if done within a game loop!*
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    Fun fact: `invert(50%)` always creates middle gray.
  </slot>

  <slot path="steps">
    Included here for completeness, but please never do this. Use `globalAlpha` or semitransparent colors instead.
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    You can combine as many filters as you'd like.
  </slot>
</Scrollycoding>

### Gradients [#gradients]

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>
      `fillStyle` and `strokeStyle` can be used for more than just colors. Gradients also work!

      We'll start with linear gradients. You'll pick a starting coordinate and ending coordinate, then add color stops some % along that line.

      It's important to note that the gradient exists in global canvas pixels. This demo shows how it's not scaled to the shape you're applying it to.
    </>
  </slot>

  <slot path="steps">
    Same deal as vertical gradients, just change the coordinates of the line for the gradient. You can follow the same approach for angled linear gradients.
  </slot>

  <slot path="steps">
    <>
      For radial gradients, instead of defining a line you define two circles:
      `createRadialGradient(x1, y1, r1, x2, y2, r2)`

      The first circle is usually a point in the middle and the second circle is the full size of the glow you want to create.
    </>
  </slot>

  <slot path="steps">
    If you misalign the inner and outer circles, you can create a downlight effect. <span className="dark:hidden">(This is easier to see in dark mode.)</span>
  </slot>

  <slot path="steps">
    Useful for color wheels.
  </slot>
</Scrollycoding>

### Blend modes [#blend-modes]

If you've worked with image editors like Photoshop you will be familiar with the concept of [Blend Modes](https://www.sketch.com/blog/blend-modes/).

These determine how the next painted layer interacts with what is beneath it. Common blend modes used in games include:

* `lighter` which picks the lighter of the two values (often in particles/explosions)
* `multiply` (darkening shadows, ink staining effects)
* `screen` similar to lighter but preserves highlights better (fog, light rays)
* `overlay` / `soft-light` / `hard-light` boost contrast (often for applying textures or color grading)
* `hue` / `color` for color grading

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    Similar to `hue` but also applies saturation.
  </slot>

  <slot path="steps">
    <>    </>
  </slot>

  <slot path="steps">
    <small>
      *Texture credit: [Becca Lavin](https://unsplash.com/photos/gray-concrete-surface-f-UkGU8GcWo)*
    </small>
  </slot>
</Scrollycoding>

### Composite operations [#composite-operations]

[Composite operations](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) <small className="opacity-75">\[MDN]</small> use the same `globalCompositeOperation` property as blend modes. Instead of color mixing, these determine which pixels from the source and destination survive.

In image editors you may have seen "intersect" or "mask", for example. Those same techniques can be applied in a canvas with `globalCompositeOperation="destination-in"` and `globalCompositeOperation="destination-out"`.

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    `destination-out` subtracts everything from the background that overlaps with the drawn shape.
  </slot>

  <slot path="steps">
    `destination-in` subtracts everything from the background *outside* of the drawn shape.
  </slot>

  <slot path="steps">
    Useful for weapon scopes, flashlights, and fog-of-war effects when you need feathered or complex edges. Otherwise, `clip()` is often simpler.
  </slot>

  <slot path="steps">
    <small>
      *Decal credit:
      [pngkey](https://www.pngkey.com/detail/u2q8r5a9i1r5q8y3_permalink-explosion-decal-texture/). Brick
      texture credit: [Kenny Eliason](https://unsplash.com/photos/red-bricks-wall-XEsx2NVpqWY)*
    </small>
  </slot>
</Scrollycoding>

### Clipping [#clipping]

`clip()` turns the current path into a mask, so pixels only change when they fall inside the clipped region.

A common game use is drawing a large image once while only revealing part of it (eg. minimaps).

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Build a circle on the path, call `clip()`, then `drawImage`. Remember to `save()` and `restore()` to release the clipping mask.
  </slot>

  <slot path="steps">
    You can also pass a [`Path2D`](https://developer.mozilla.org/en-US/docs/Web/API/Path2D) <small className="opacity-75">\[MDN]</small> to `clip(path)` without touching the context's current path. Calling `clip()` again with another path intersects the new region with the previous clip.
  </slot>

  <slot path="steps">
    Combined with a `filter` you can create a glassmorphic effect.
  </slot>
</Scrollycoding>

### Transforms [#transforms]

Transforms modify the coordinate system itself rather than individual draw calls. Instead of calculating where to draw each shape, you shift, stretch, or rotate the entire canvas and then draw at simple coordinates.

<Scrollycoding preview="canvas2d">
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    <>
      `ctx.translate(x, y)` shifts the origin of the coordinate system. After translating, all drawing operations are offset by that amount. Here we draw the character at (0, 0) but it appears at (80, 30) because the origin has moved.

      <small>
        *Character asset credit:
        [craftpix](https://free-game-assets.itch.io/free-city-trader-character-sprite-sheets-pixel-art)*
      </small>
    </>
  </slot>

  <slot path="steps">
    <>
      Each `translate` shifts the origin from its *current* position, not from (0, 0). Two `translate(100, 0)` calls are the same as one `translate(200, 0)`.

      The same is true of `rotate` and `scale`.
    </>
  </slot>

  <slot path="steps">
    `ctx.scale(x, y)` multiplies all coordinates and sizes. This also affects positions, not just dimensions!
  </slot>

  <slot path="steps">
    `ctx.scale(-1, 1)` mirrors the x-axis, flipping your drawing horizontally. Because the axis reverses, you need to translate first so the result stays in view. Similarly, `ctx.scale(1, -1)` mirrors the y-axis.
  </slot>

  <slot path="steps">
    `ctx.rotate(angle)` rotates the coordinate system clockwise by `angle` radians. Rotation happens around the current origin (0, 0) by default--which is usually not what you want.
  </slot>

  <slot path="steps">
    To rotate around a specific point, translate to that point first, then rotate. When you draw, you'll also need to offset the `x` and `y` by half the object's width and height.
  </slot>

  <slot path="steps">
    <>
      `ctx.setTransform(a, b, c, d, e, f)` and `ctx.resetTransform()` replace the current transform matrix entirely rather than multiplying onto it like `translate`/`scale`/`rotate` do.

      You'll mainly use it to:

      1. Perform advanced transformations
      2. Handle DPI scaling

      **As a general tool for transforms**

      `setTransform` can apply any of the above transformations without accumulating its effect with repeat calls. The arguments `a–f` map cleanly to each of the other transforms.

      <details>
        <summary>
          <strong>Translate, scale, rotate, and skew using `setTransform`</strong>
        </summary>

        * `translate` sets `e` and `f`
        * `scale` sets `a` and `d`
        * `rotate` sets `a` through `d`, where `a = cosθ` `b = sinθ` `c = -sinθ` and `d = cosθ`
        * `skew`, lacking a built-in method, sets `b` and `c`, where `b = tan(skewY)` and `c = tan(skewX)`
      </details>

      **DPI scaling in-depth**

      Part of the difficulty with DPI scaling is that a &#x2A;"pixel"* can refer to many different concepts. So we'll begin with a few definitions:

      1. **Physical pixels** -- the actual hardware pixels on the screen. A 2019 MacBook Pro has 2560×1600 of these on its "retina" display.
      2. **CSS pixels** -- the abstract unit browsers use for layout. That same MacBook runs its browser at 1280×800 CSS pixels. These are device-independent and can be affected by browser zoom. When you write `width: 100px` in CSS, you mean 100 CSS pixels.
      3. **Canvas buffer pixels** -- the internal resolution of the canvas element, set by `canvas.width` and `canvas.height`. This is completely independent of the canvas's size on the page.

      Suppose we're working with a canvas 800px wide on that 2019 MacBook Pro. Its `devicePixelRatio` is `2`, so there are 1,600 physical pixels spanning the width of the canvas. Our internal canvas buffer still only has 800 slots on the `x` axis for pixels, though. So our first task is to remedy that by setting `canvas.width = width * dpr`.

      This creates two new problems:

      1. Within the canvas, we're operating on a much larger area. If you call `fillText("Hello", 20, 100)` you meant 20 CSS pixels from the left, but now 20 buffer pixels is a tiny fraction of the way across. Everything would draw in the wrong place at the wrong size.
      2. The canvas itself now occupies twice as much space in the layout.

      `ctx.setTransform(dpr, 0, 0, dpr, 0, 0)` fixes the first problem. It scales the `x` and `y` axes within the canvas so that `fillText("Hello", 20, 100)` actually lands at `(40, 200)` but visually appears 20 CSS pixels from the left. It's just like `ctx.scale(dpr, dpr)` but without the cumulative effects. `scale` compounds with previous transforms, `setTransform` does not.

      `canvas.style.width = width + "px"` addresses the second problem. Explicitly setting the number of CSS pixels occupied in the layout by the canvas ensures the canvas only takes up its original CSS size.
    </>
  </slot>
</Scrollycoding>

## Up next [#up-next]

In the next chapter, we'll lay the groundwork for how to animate the canvas. Chapter 3 deals with game loops: updating the canvas at a high, variable frame rate, while handling physics at a reliable fixed interval.
