Guides /Coming from the Web

Guides

Coming from the Web

For JavaScript and TypeScript developers. How the HTML canvas, requestAnimationFrame and DOM events map onto Codetoy.

If you've drawn on an HTML <canvas>, Codetoy will feel familiar. Its drawing API is a trimmed-down CanvasRenderingContext2D, and its loop is requestAnimationFrame with the boilerplate removed. There's no HTML, no bundler, no getContext, and no resize handling to write.

Use AssemblyScript

AssemblyScript is TypeScript syntax compiled to WebAssembly, so you can read it immediately. It's also the language behind Builds and Discord Activities. The snippets below follow the sidebar's language; leave it on AssemblyScript for the closest match.

#What you don't have to write

Browser canvas
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")

function resize() {
  canvas.width = innerWidth * devicePixelRatio
  canvas.height = innerHeight * devicePixelRatio
  ctx.scale(devicePixelRatio, devicePixelRatio)
}
addEventListener("resize", resize)
resize()

let last = performance.now()
function frame(now) {
  const dt = (now - last) / 1000
  last = now
  ctx.fillStyle = "#14141e"
  ctx.fillRect(0, 0, innerWidth, innerHeight)
  ctx.fillStyle = "#64b4ff"
  ctx.beginPath()
  ctx.arc(mouse.x, mouse.y, 24, 0, Math.PI * 2)
  ctx.fill()
  requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
Codetoy
import { fill, rect, circle } from "codetoy/canvas"
import * as screen from "codetoy/screen"
import * as input from "codetoy/input"

export function update(): void {
  fill(20, 20, 30)
  rect(0, 0, screen.width(), screen.height())
  fill(100, 180, 255)
  circle(input.mouseX(), input.mouseY(), 24)
}
Screen.OnUpdate(() => {
    Canvas.Fill(20, 20, 30);
    Canvas.Rect(0, 0, Screen.Width, Screen.Height);
    Canvas.Fill(100, 180, 255);
    Canvas.Circle(Input.MouseX, Input.MouseY, 24);
});
from codetoy.canvas import fill, rect, circle
from codetoy.screen import width, height
from codetoy._input import mouseX, mouseY

def update():
    fill(20, 20, 30)
    rect(0, 0, width(), height())
    fill(100, 180, 255)
    circle(mouseX(), mouseY(), 24)

Codetoy handles high-DPI scaling, resizing, the frame loop and timing for you.

#Canvas API mapping

CanvasRenderingContext2D Codetoy
ctx.fillStyle = "…" fillStyle()Canvas.FillStyle()fillStyle(), or fill()Canvas.Fill()fill() with RGB(A)
ctx.strokeStyle = "…" strokeStyle()Canvas.StrokeStyle()strokeStyle(), or stroke()Canvas.Stroke()stroke()
ctx.fillRect / ctx.roundRect rect()Canvas.Rect()rect() (fifth argument is the corner radius)
ctx.arc(…, 0, 2π) + fill() circle()Canvas.Circle()circle()
ctx.ellipse ellipse()Canvas.Ellipse()ellipse()
ctx.moveTo + lineTo + stroke() line()Canvas.Line()line()
ctx.lineWidth = n lineWidth()Canvas.LineWidth()lineWidth()
ctx.lineCap, lineJoin, miterLimit lineCap()Canvas.LineCap()lineCap(), lineJoin()Canvas.LineJoin()lineJoin(), lineMiterLimit()Canvas.LineMiterLimit()lineMiterLimit()
ctx.save() / ctx.restore() push()Canvas.Push()push() / pop()Canvas.Pop()pop()
ctx.translate / rotate / scale translate()Canvas.Translate()translate() / rotate()Canvas.Rotate()rotate() / scale()Canvas.Scale()scale()
ctx.resetTransform() resetTransform()Canvas.ResetTransform()resetTransform()
ctx.beginPath … fill() / stroke() beginPath()Canvas.BeginPath()beginPath() … fillPath()Canvas.FillPath()fillPath() / strokePath()Canvas.StrokePath()strokePath()
ctx.font = "bold 24px Arial" font()Canvas.Font()font() with "Arial", 24, "bold"
ctx.fillText text()Canvas.Text()text() (top-left aligned)
ctx.measureText(s).width measureText()Canvas.MeasureText()measureText()
ctx.reset() reset()Canvas.Reset()reset()

Two differences to know:

  • Shapes fill and stroke in one call. rect()Canvas.Rect()rect(), circle()Canvas.Circle()circle() and friends use the current fill and stroke (a thin black outline by default). Call noStroke()Canvas.NoStroke()noStroke() or noFill()Canvas.NoFill()noFill() to turn either off.
  • Text is drawn from its top-left corner, as if textBaseline = "top".

#Events

DOM Codetoy
requestAnimationFrame loop export function update()Screen.OnUpdate()def update()
keydown / keyup export function onKeyDown()Input.OnKeyDown()def keyDown() / export function onKeyUp()Input.OnKeyUp()def keyUp() (same event.key names)
Tracking pressed keys in a Set input.isKeyDown()Input.IsKeyDown()isKeyDown()
mousemove → clientX/Y input.mouseX()Input.MouseXmouseX(), input.mouseY()Input.MouseYmouseY() (already in canvas pixels)
mousedown / mouseup export function onMouseDown()Input.OnMouseDown()def mouseDown() / export function onMouseUp()Input.OnMouseUp()def mouseUp() (same event.button numbers)
touchstart / touchmove / touchend export function onTouchStart()Input.OnTouchStart()def touchStart() / export function onTouchMove()Input.OnTouchMove()def touchMove() / export function onTouchEnd()Input.OnTouchEnd()def touchEnd()
resize export function resize()not in C#def resize()
console.log console.log()Console.Log()print()

#TypeScript vs. AssemblyScript

AssemblyScript looks like TypeScript but compiles ahead of time to WebAssembly, so it's stricter:

  • Numbers are concrete types: f64 (like JS number), i32, u8 and so on. let x = 0 is an i32, so write let x: f64 = 0.
  • No any, undefined, or union types (except nullable classes like Enemy | null).
  • Closures can't capture local variables; use top-level variables or class fields.
  • No DOM, fetch or setTimeout: everything goes through Codetoy's API.
  • Bonus: operator overloading with @operator("+").

More in Language Notes and the AssemblyScript docs.

#From prototype to published

Because AssemblyScript compiles to a compact .wasm, finished games can be captured as a build: a hosted, game-only page at https://<build-id>.codetoy.dev/. You can embed it on your own site or run it as a Discord Activity.

Esc