使用 Golang 来入门 WebAssembly 开发

如果你想了解 WebAssembly ,又不想学习 C++ 或者 Rust ,刚好又对 Go 感兴趣,那你可以继续看下去。

目标: 使用 Go + WebAssembly 技术编写一个简单的小游戏,在固定画板上随机生成若干条横线,点击画板重新生成

环境: Go 1.11 及以上环境

代码演示

准备一个 html 和一个 js 文件

首先拿到 Go 为我们准备的 Go 和 JS 的“桥接器”

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

在 html 引入之后你便可以在全局访问到 Go 这个类

// ./index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Go + WebAssembly</title>
    <script src="wasm_exec.js"></script>
  </head>
  <body>
    <canvas id="canvas"></canvas>
  </body>

  <script>
    // 加载 wasm
    const go = new Go()
    WebAssembly.instantiateStreaming(fetch('lib.wasm'), go.importObject).then(
      (result) => {
        go.run(result.instance)
      }
    )
  </script>
</html>

编写我们的 Go 入口文件

// ./main.go
package main

func main() {
      println("wasm app works")
}

编译成 .wasm 文件

GOARCH=wasm GOOS=js go build -o lib.wasm main.go

使用 http 访问我们的 index.html 文件,可以看到

img

编写我们的基础绘图逻辑

// ./main.go
package main

// 需要导入 syscall/js 包以调用 js API
import (
	"math/rand"
	"syscall/js"
	"time"
)

const (
	width = 400
	height = 400
)

// 生成 0 - 1 的随机数
func getRandomNum() float32 {
	rand.New(rand.NewSource(time.Now().UnixNano()))
	n := float32(rand.Intn(10000))
	return  n / 10000.0
}

// 使用 canvas 绘制随机图
func draw() {
	var canvas js.Value = js.
		Global().
		Get("document").
		Call("getElementById", "canvas")

	var context js.Value = canvas.Call("getContext", "2d")

	// reset
	canvas.Set("height", height)
	canvas.Set("width", width)
	context.Call("clearRect", 0, 0, width, height)

        // 随机绘制 50 条直线
	for i := 0; i < 50; i ++ {
		context.Call("beginPath")
		context.Call("moveTo", getRandomNum() * width, getRandomNum() * height)
		context.Call("lineTo", getRandomNum() * width, getRandomNum() * height)
		context.Call("stroke")
	}
}

// 主程序入口
func main() {
	println("wasm app works")
	// bootstrap app
	draw()
}

重新按照第二步流程编译一次,查看浏览器效果

img

绑定点击事件,实现点击重新随机画图

// ./main.go
package main

// 绘制随机图形
func draw() {
	// ...
}

// 绑定点击事件
func addEventListener()  {
        // 回调中的操作将阻塞事件循环,需要明确地启动一个新的 goroutine
	done := make(chan struct{})
	var cb js.Callback = js.NewCallback(func(args []js.Value) {
		go func() {
			println("click")
			draw()
		}()
	})
	js.
		Global().
		Get("document").
		Call("getElementById", "canvas").
		Call("addEventListener", "click", cb)
	<-done

}

// 启动应用
func bootstrapApp() {
	draw()
	addEventListener()
}

func main() {
	println("wasm app works")
	// bootstrap app
	bootstrapApp()
}

重新编译,重新访问浏览器,Done!

参考资料