astwerk

Canvas

A typed 2D context and an animation loop, with the drawing buffer matched to the device pixel ratio so it stays sharp.

Move the pointer over the canvas.

How it works

The context

go
canvas := root.Find("[data-canvas]").AsCanvas()
ctx2d := canvas.Context2D()
w, h := canvas.FitToDisplay(ctx2d)

AsCanvas and Context2D wrap the same objects getContext("2d") returns, in Go types. FitToDisplay matches the pixel buffer to the CSS size times the device pixel ratio — skipping it is why canvas drawings look blurry on high-DPI screens.

The pointer

go
pointer := reactive.NewSignal(0.0)
reactive.On(canvas.Element(), "pointermove", func(e wasmwrap.Event) {
	x, _ := e.PosIn(canvas.Element())
	pointer.Set(float64(x))
})

Pointer moves write a signal. PosIn gives coordinates relative to the canvas, whatever its position on the page.

The loop

go
elapsed := 0.0
cancel := ctx2d.RequestAnimationFrame(func(dt float64) {
	elapsed += dt
	ctx2d.Clear(0, 0, w, h)
	for i := 0.0; i < w; i += 6 {
		wave := math.Sin(i/40+elapsed*2) * 40
		lift := math.Exp(-math.Pow(i-pointer.Peek(), 2) / 4000) * 30
		ctx2d.FillStyle("#4f7cff").FillRect(i, h/2+wave-lift, 3, 3)
	}
})

RequestAnimationFrame reschedules itself, handing the frame's delta time to the callback; the returned cancel stops the loop. The loop reads the pointer with Peek, not Get, because it redraws every frame anyway.

Full source
go
canvas := root.Find("[data-canvas]").AsCanvas()
ctx2d := canvas.Context2D()
w, h := canvas.FitToDisplay(ctx2d)

pointer := reactive.NewSignal(0.0)
reactive.On(canvas.Element(), "pointermove", func(e wasmwrap.Event) {
	x, _ := e.PosIn(canvas.Element())
	pointer.Set(float64(x))
})

elapsed := 0.0
cancel := ctx2d.RequestAnimationFrame(func(dt float64) {
	elapsed += dt
	ctx2d.Clear(0, 0, w, h)
	for i := 0.0; i < w; i += 6 {
		wave := math.Sin(i/40+elapsed*2) * 40
		lift := math.Exp(-math.Pow(i-pointer.Peek(), 2) / 4000) * 30
		ctx2d.FillStyle("#4f7cff").FillRect(i, h/2+wave-lift, 3, 3)
		}
	})
How this demo runs

scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the animation stays put.