Skip to content

Web view(网页视图) ​

ui.WebView 在原生 UI 窗口中显示一个网页:由 web 引擎渲染的文档与原生 控件并排显示,每个标签页一个页面,或预览应用生成的 HTML。该页面是系统 的 web view(WKWebView、WebView2、WebKitGTK),由 Window.NewWebView 创建,其工作方式与窗口的页面相同:加载应用的前端,通过类型化客户端调用 已绑定的 Go 方法,并监听事件。在 web view 之后绘制的内容会显示在页面 之上:从上方按钮弹出的菜单、select 的选项、对话框及其变暗的背景、工具 提示、提示条、角落上的徽标。

go
type browser struct {
	win  *mygo.Window
	docs *mygo.WebView
	menu bool
}

func (app *browser) view(c *ui.Context) {
	ui.Column(c).Fill().AlignItems(ui.Stretch).Children(func() {
		ui.Row(c).Padding(8).Gap(8).Children(func() {
			menu := ui.Button(c, "Menu ▾")
			if menu.Clicked() {
				app.menu = !app.menu
			}
			// The popover shows over the page.
			ui.Popover(c, menu, &app.menu, func() { ui.Text(c, "Over the page") })
		})
		ui.WebView(c, app.docs).Grow(1).Radius(8)
	})
}

func main() {
	app := &browser{}
	mygo.App.WhenReady(func() {
		app.win = mygo.NewWindow(mygo.WindowOptions{Title: "Docs", Content: ui.View(app.view)})
		app.docs, _ = app.win.NewWebView(mygo.WebViewOptions{URL: "/docs"})
		app.win.Invalidate()
	})
	if err := mygo.App.Run(); err != nil {
		log.Fatal(err)
	}
}

创建 web view ​

Window.NewWebView 为一个显示 Content 的窗口创建 web view,并随窗口 一起关闭。它的选项包括页面选项(Page,同窗口的 PageOptions)、要 加载的 URL、在页面绘制之前用于填充的 BackgroundColor,以及 Transparent——它使 web view 背后的内容透过页面的透明部分显示出来: 即窗口的背景或材质,因为原生 UI 不会在 web view 下方绘制任何内容。 WebView.Page 返回其页面,页面的方法与窗口页面的方法相同:LoadURL、 LoadHTML、Eval、GoBack、OnDidNavigate、OnWillNavigate 等。 Focus 将键盘交给它,SetBackgroundColor 更改其填充颜色,例如应用自身 主题变化时(以 light-dark(<light>, <dark>) 给定的颜色会自动跟随外观), CapturePage 对其页面截图,Destroy 将其关闭。

web view 对应用自有页面的信任与窗口相同,包括自定义 scheme 的页面:若 要在不可信页面中显示第三方或用户提供 的 HTML(PageOptions.Untrusted),或来自它不信任的来源,请参见 信任。

与窗口一样,web view 只创建一次,而不是在视图里创建:视图只负责显示它。 视图未构建的 web view 会被隐藏并保留其页面,因此标签页视图只构建当前 显示标签页的 web view,切换回来时页面仍停留在原来的位置。

go
if app.tab == "docs" {
	ui.WebView(c, app.docs).Grow(1)
} else {
	ui.WebView(c, app.changelog).Grow(1)
}

它的外框 ​

web view 没有固有尺寸。该元素会横向撑满其容器(AlignSelf(Stretch)), 并沿该方向用 Grow 或指定尺寸占据空间。页面显示在元素的内容盒中:位于 其边框与内边距之内,边框连同元素的背景绘制在其四周,并按 Radius 形成的 边框内缘呈现圆角。周围的滚动容器和裁剪会像裁剪任何元素一样裁剪它,包括 圆角;滚出视野的 web view 会被隐藏。Opacity 会让页面随元素一起淡化, 例如 Router 页面淡入时那样:页面与元素下方绘制的内容混合叠加。

go
ui.WebView(c, app.preview).Height(320).Radius(12).Border(1, t.Border)

显示在其上的内容 ​

web view 位于窗口的原生 UI 之下:该元素在窗口绘制的内容上"挖"出一个洞, 页面就显示在这里。在元素之后绘制的一切都会画在页面之上,就像画在任何 元素之上一样的:其子元素、绝对定位在其上的元素,以及覆盖层中的弹出面板、 菜单、select、组合框、对话框、工具提示和提示条。阴影和半透明背景会与 页面混合叠加。

go
ui.WebView(c, app.page).Grow(1).Children(func() {
	// A badge over the page's corner.
	ui.Text(c, "Draft").Absolute().Top(8).Right(8).Padding(2, 8).Radius(8).Background(t.Accent)
})

指针会落到页面显示的区域,绘制在其上的内容不会夺走指针:对话框的遮罩或 select 的遮罩在打开期间会挡住页面,弹出面板只在其面板上方拦截指针,而带 PassThrough 的元素会让指针穿过。拖拽到这里的文件会投放到页面上。在 页面上按下,会关闭其所在区域之外的弹出面板(PressedOutside),并从 原生 UI 接管键盘;当用户点击原生 UI 时,键盘会重新归还给它。页面会自行 设置其上方的光标。

在原生 UI 的控件之间按 Tab 会在 web view 处停留:其页面在首个元素处接管 键盘,或用 Shift+Tab 从最后一个元素开始;Tab 会在页面元素间移动,越过 最后一个后继续到 web view 之后的控件。Focus 会把键盘交给页面,并回到 它此前获得焦点的位置。

辅助技术在 web view 的元素内部找到页面,页面因此身处原生 UI 之中: VoiceOver 和 Orca 会像浏览周围的控件一样朗读并在其中移动。

它的页面 ​

web view 的页面与窗口的页面工作方式相同(参见绑定)。 mygo.CallerPage(ctx) 返回调用已绑定方法的页面,Page.WebView 返回其 web view;mygo.CallerWindow(ctx) 返回 web view 所在的窗口,页面的窗口 控制(mygo.window.minimize()、拖拽区域)作用于该窗口。向窗口 Event.Emit 会送达其所有 web view 的页面,Event.EmitPage 只送达一个 页面,Broadcast 送达所有页面。

go
func (s *Notes) Save(ctx context.Context, text string) error {
	page := mygo.CallerPage(ctx) // the web view's
	...
}

页面的 window.close() 不会关闭其所在窗口。内容规则会拦截 web view 页面加载的内容,与拦截窗口页面的方式相同(拦截内容), 可通过 WebViewOptions.Page.ContentRules 或 Page.SetContentRules 设置。

类型化客户端 ​

页面通过 mygo generate 生成的客户端调用 Go,与窗口页面的方式相同: 客户端并不感知 web view。原生 UI 项目没有前端,因而也没有客户端:为它 配一个即可,比如放在 frontend 目录中的 Vite 应用,并在其 mygo.json 中 配置前端的各项字段:

json
{
  "name": "Notes",
  "devUrl": "http://localhost:5173",
  "devCommand": "bun run --cwd frontend dev",
  "buildCommand": "bun run --cwd frontend build",
  "frontendDist": "frontend/dist"
}

客户端随后位于 frontend/src/mygo.ts(配置 bindings 可改变位置), mygo dev 会在 Go 代码变化时重写它,mygo build 会在构建前端之前重写 它;前端依赖 mygo-runtime,客户端会导入它。加载 / 的 web view 会显示 前端——在 mygo dev 期间来自开发服务器,在构建版本中则内嵌于应用(参见 页面如何加载);其页面调用用 mygo.Bind 绑定的服务,并监听用 mygo.NewEvent 声明的事件:

go
// Notes 已为页面绑定。
type Notes struct{ likes atomic.Int64 }

// Liked 告知页面有多少个赞。
var Liked = mygo.NewEvent[int]("liked")

// Like 累加一次点赞并通知所有页面。
func (n *Notes) Like() int {
	likes := int(n.likes.Add(1))
	Liked.Broadcast(likes)
	return likes
}

var page *mygo.WebView

func view(c *ui.Context) {
	if page != nil {
		ui.WebView(c, page).Fill()
	}
}

func main() {
	mygo.Bind(&Notes{})
	mygo.App.WhenReady(func() {
		win := mygo.NewWindow(mygo.WindowOptions{Title: "Notes", Content: ui.View(view)})
		page, _ = win.NewWebView(mygo.WebViewOptions{URL: "/"})
		win.Invalidate()
	})
	// ...
}
ts
import { Notes, events } from "./mygo";

likeButton.onclick = async () => {
  count.textContent = String(await Notes.like());
};
events.liked.on((likes) => (count.textContent = String(likes)));

带上下文的调用、错误、通道和事件的工作方式 与窗口中相同。没有构建产物的页面,例如应用用 LoadHTML 加载的 HTML, 可以通过桥接注入每个页面的运行时按名称调用:mygo.call("Notes.Like") 和 mygo.on("liked", …)(参见不依赖生成的 客户端)。非应用自有的页面, 例如 web view 显示 https://example.com,则完全无法调用 Go:参见谁可以 调用。

限制 ​

页面是系统的 web view,由窗口与原生 UI 合成;MyGo 并不绘制它。与 SwiftUI 的 WebView 一样:

  • 读取其背后内容的效果,例如 glass 插件的模糊,看不到页面:页面不属于 窗口的帧。
  • 对窗口调用 Window.CapturePage 时,页面所在位置会显示为一个洞; WebView.CapturePage 则捕获页面。

在 Windows 上,WebView2 会把以这种方式显示的页面的元素放在其自己的窗口 之下:Narrator 能在原位找到 web view,而页面的元素位于那个窗口中,而非 其内部。

测试 ​

在 Tester 中,WebViewAt 返回在窗口某一点接收指针的 web view,以最后 一帧的呈现为准:即位于该点之下的 web view,除非绘制在其上方的内容在该点 拦截了指针。在 web view 上方执行的 ClickAt 和 Press 会按压页面, web view 会像平台告知它那样感知到这一事件。