Dev's offline page is fun and we did implement it with Rust and WebAssembly.
Go is simple. Can we do it in Go and WebAssembly? And the answer is Yes...
I have the sample application here. If you want to write from scratch check this tutorial.
If you just want to see the source code, check out this branch.
Code Code Code
We will first import the "syscall/js" and "fmt" in the go/main.go
.
The "syscall/js" package gives access to the WebAssembly host environment when using the js/WASM architecture. Its API is based on JavaScript semantics. This package is EXPERIMENTAL. ๐ฆ ๐ฆ
package main
import(
"fmt"
"syscall/js"
)
Add some variables globally available, these are the variables that we might need to use it when the functions are called from the JavaScript land or the browsers.
var (
isPainting bool
x float64
y float64
ctx js.Value
color string
)
We will manually add the canvas element in the out/index.html
.
<canvas id="canvas"> </canvas>
Remove the Hello World
line inside the main
function and replace it with the following.
Get the document
object from the Browser. Using the document
get the canvas
element that we added just now.
We can do that by using the syscall/js
API.
func main() {
doc := js.Global().Get("document")
canvasEl := doc.Call("getElementById", "canvas")
}
The API is simple. From the JavaScript's Global namespace get the document.
Inside the document call the canvas element by Id.
We will set some attributes of the canvasEl
like width, height. We will also get the canvas' context
.
bodyW := doc.Get("body").Get("clientWidth").Float()
bodyH := doc.Get("body").Get("clientHeight").Float()
canvasEl.Set("width", bodyW)
canvasEl.Set("height", bodyH)
ctx = canvasEl.Call("getContext", "2d")
We will then define the three event listeners mousedown
| mousemove
| mouseup
to the canvasElement
.
Mouse Down
We will start the painting when we clicked down the mouse.
startPaint := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
isPainting = true
x = e.Get("pageX").Float() - canvasEl.Get("offsetLeft").Float()
y = e.Get("pageY").Float() - canvasEl.Get("offsetTop").Float()
return nil
})
canvasEl.Call("addEventListener", "mousedown", startPaint)
Please note that return nil is important otherwise the program will not compile.
The entire API is based on Reflect API.
Mouse Move
We will define the paint function.
paint := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if isPainting {
e := args[0]
nx := e.Get("pageX").Float() - canvasEl.Get("offsetLeft").Float()
ny := e.Get("pageY").Float() - canvasEl.Get("offsetTop").Float()
ctx.Set("strokeStyle", color)
ctx.Set("lineJoin", "round")
ctx.Set("lineWidth", 5)
ctx.Call("beginPath")
ctx.Call("moveTo", nx, ny)
ctx.Call("lineTo", x, y)
ctx.Call("closePath")
// actually draw the path*
ctx.Call("stroke")
// Set x and y to our new coordinates*
x = nx
y = ny
}
return nil
})
canvasEl.Call("addEventListener", "mousemove", paint)
Mouse Up
Finally, we will define the exit method that is called when the Mouse Up event is triggered.
exit := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
isPainting = false
return nil
})
canvasEl.Call("addEventListener", "mouseup", exit)
At last, we will define the color selector. Add a colors div in the out/index.html
and the necessary CSS.
<style>
.color {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
margin: 10px;
}
</style>
<div id="colors></div>
We will define the colors as an array. Then loop through the colors and create color swatch node. Add an on click event listener to the color swatch.
divEl := doc.Call("getElementById", "colors")
colors := [6]string {"#F4908E", "#F2F097", "#88B0DC", "#F7B5D1", "#53C4AF", "#FDE38C"}
for _, c := range colors {
node := doc.Call("createElement", "div")
node.Call("setAttribute","class", "color")
node.Call("setAttribute", "id", c)
node.Call("setAttribute","style", fmt.Sprintf("background-color: %s", c))
setColor := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
color = e.Get("target").Get("id").String()
return nil;
})
node.Call("addEventListener", "click", setColor)
divEl.Call("appendChild", node)
}
That is it.
Now start the web server by running go run webServer.go
.
Compile the Go into WebAssembly module using
tinygo build -o ./out/main.wasm -target wasm -no-debug ./go/main.go
Visit the localhost:8080 to see the awesome canvas board ready for your creativity ๐ฆ.
๐ก The generated WebAssembly
module is just 52KB. TinyGo is awesome and creates insanely smaller WebAssembly binaries. How does it create it? Stay tuned for the next post in this series. ๐ก
Note: Use Firefox / Firefox Nightly. Chrome sometimes crash.
More optimization โค๏ธ
Remove the
fmt
import and replacefmt.Sprintf
with a normal String concatenation, this will shave off another 26KB. Check the commit here.
Thanks to Justin Clift for sharing this tip :)
Do you โค๏ธ Rust? check out here on how to use Rust for WebAssembly.
I hope this gives you a motivation to start your awesome WebAssembly journey. If you have any questions/suggestions/feel that I missed something feel free to add a comment.
You can follow me on Twitter.
If you like this article, please leave a like or a comment. โค๏ธ
Thanks
for the article.
Top comments (2)
So cool :)
Go for it ๐๐