笔者注: 随着 Flutter 1.0 稳定版本的发布,跨平台开发的趋势收到愈来愈多人的关注,然而 Flutter 的野心不止在跨移动平台 Android 和 iOS,在2019的当下,Flutter将逐步开展跨 Web 端的工做并取得了初步结论和经验,本文原文为 Flutter 团队发布的 Web 端运行 Flutter 试验性成果报告,内容丰富且极具启发性,所以特地将其翻译为中文供你们学习。 原文:Hummingbird: Building Flutter for the Webcss
在今天的 Flutter Live 上,咱们宣布正尝试在 Web 端运行 Flutter 。 在这篇文章中,咱们描述了咱们如何应对挑战以及的当前的技术情况。 在帖子的最后,你将找到有关交互操做和嵌入问题的答案。 html
Flutter Engine 做为 Flutter 中最低级别暴露的库,dart:ui。它对 widgets、物理 (physics)、动画或布局(文本布局除外)一无所知。它所知道的是如何将 pictures 组合到屏幕上并将它们变成像素。在 dart:ui 上直接编写应用程序是很困难的。这就是建立更高层级的缘由。git
dart:ui 之上的全部东西是咱们所谓的“框架 (framework)”。它下面的一切都是“引擎 (engine)”。该框架彻底使用 Dart 编程语言编写。大多数引擎都是用 C++ 编写的,特定于 Android 的部分用 Java 编写,而 iOS 特定的部分用 Objective-C 编写。 dart 中的一些基本类和函数:ui 是用 Dart 编写的,主要用做 Dart 和 C++ 之间的桥梁。github
Flutter 还提供插件系统。插件是用一种语言编写的代码,能够直接访问移动生态系统随着时间累积的 OEM 库和第三方库。要为 Android 建立插件,你能够编写 Java 或 Kotlin。 iOS 插件是使用 Objective-C 或 Swift 编写的。web
Web 平台已经发展了数十年,包括许多技术和规范。有一些总括性术语用于描述大量相关功能:HTML,CSS,SVG,JavaScript,WebGL。为了在 Web 上运行 Flutter,咱们须要:编程
只要语言存在,Dart 就一直在编译 JavaScript。许多重要的应用程序从 Dart 编译为 JavaScript,并在今天的产品中运行。 Flutter 的编译策略依赖于一样的基础。canvas
当咱们开始探索时,咱们面临着 UI 渲染的几种选择。咱们很快意识到咱们想要支持的特定 Flutter 层决定了咱们将用于实现的 Web 技术。咱们制做了三个原型:api
只有 widgets:这个原型实现了 Flutter 的 widget 框架,并提供了一组核心布局 widgets 做为构建自定义 widget 的基础。对于布局和定位,它依赖于 Web 的内置功能,例如 flexbox,grid layout,浏览器滚动经过 overflow:scroll
等。数组
Widgets + 自定义布局:此原型包括 Flutter 的布局系统(由 RenderObject
提供),但将渲染对象直接映射到 HTML 元素。浏览器
Flutter Web 引擎:这个原型保留了 dart:ui 之上的全部层,并提供了一个在浏览器中运行的 dart:ui 实现。
Flutter 最有价值的功能之一是它能够跨平台移植。跨平台能够共享相同的代码,虽然你能够(有时甚至鼓励)编写自定义平台特定代码。这容许使用单个代码库编写面向多个平台的应用程序。
在尝试将几个示例应用程序移植到 Web 以后,咱们意识到原型#1和#2不能提供 Flutter 开发人员喜欢的可移植性级别。所以,咱们决定使用 Flutter Web Engine 设计的原型#3,由于这将容许平台之间最高框架级的代码重用:
如今咱们知道咱们想要实现整个 dart:ui API,咱们须要选择一组Web技术来构建。 Flutter 一次呈现一帧 UI。 在每一个框架内,Flutter 构建 widgets,执行布局,最后在屏幕上绘制它们。
Widgets 构建机制不依赖于应用程序运行的环境。该过程只是实例化内存中的对象,跟踪它们的状态,以及状态更改什么时候,计算系统的较低层级、布局、绘制所需的最小更新。 将此部分移植到 Web 上很是简单。 在Dart 团队在 dart2js 中实现了 super-mixin 支持后,编译器将全部 widgets 和 widgets 框架编译为JavaScript,几乎没有任何问题。
布局系统有点棘手。 最大的挑战是文本布局。 其余全部内容 - 中心,行,列,堆栈,可滚动,填充,换行等 - 由框架布局,所以无需修改便可编译到Web。
在 Flutter 中,你能够经过建立 Paragraph 对象并调用其 layout() 方法来布置一段文本。 不幸的是,Web 缺乏直接的文本布局 API。 咱们用来测量文本布局属性的技巧是让浏览器将其布局,而后从 DOM 元素中读回相关属性。
在布置一段文本时,Flutter测量段落的高度、宽度、最大内在宽度、最小内在宽度以及字母和表意基线。 这些属性以下所示。
你能够在 Paragraph documentation 中找到更多详细信息。
要测量这些属性,咱们首先在 HTML DOM 元素中放置一个段落,而后咱们读取元素的维度。 这会致使浏览器将其布局。 例如,要获取元素的宽度和高度,咱们调用 offsetWidth 和 offsetHeight。 为了测量基线,咱们将段落放置在一个元素中,该元素被配置为使用 flex 行进行自我布局。 在段落旁边,咱们放置另外一个名为 “probe” 的元素。 由于 probe 与文本的基线对齐,因此调用 getBoundingClientRect 就能够获得基线。 咱们使用相似的技巧来测量最小和最大内在宽度。
最后,咱们须要绘制 widgets。这部分的探索中咱们花费了最大的功夫,它仍然是咱们研究中最活跃的部分之一。在框架结束时,咱们全部的 widgets 都须要在屏幕上变成像素。在浏览器中,这意味着它们必须归结为 HTML / CSS,Canvas,SVG 和 WebGL 的某种组合。
咱们尚未看过 WebGL,主要是由于它是低级别的而且要求咱们从新实现浏览器已经能够作的事情,例如文本布局和光栅化 2D 图形,并且咱们尚未弄清楚它的可访问性、文本选择以及是否能够将非 Flutter 组件与WebGL一块儿使用。
咱们的早期原型之一为每一个 RenderObject 生成了一个 HTML 元素。咱们确实得到了有但愿的结果,但结果证实 API 变化太大了。咱们必须用 Flutter 维持一个巨大的代码增量,因此咱们搁置了这个想法。
咱们目前正在同时探索两种方法:
经过这种方法,咱们将框架生成的图片分类为使用 HTML + CSS 表达的图片,以及使用 Canvas 2D 表达的图片。而后,咱们输出结合了 HTML,CSS和 2D canvases 的 HTML DOM。
咱们更喜欢 HTML + CSS,由于它有浏览器 display list 的支持。这意味着咱们能够优化图片的光栅化到浏览器的渲染引擎。这也意味着咱们能够应用任意变换,尤为是旋转和缩放,而没必要担忧像素化。咱们将此 Canvas 实现称为 DomCanvas。
若是咱们没法使用 HTML + CSS 表达图片,咱们会回到 canvas。 Canvas 2D 容许咱们绘制几乎全部的 Flutter 绘图命令。若是你将 Flutter 的 Canvas 与 Web 的 CanvasRenderingContext2D 进行比较,你会发现许多类似之处。在 canvas 上绘制是有效的,由于它不会建立须要随时间维护的可变树节点,如 HTML DOM 或 SVG。
2D canvas 的一个挑战是浏览器将其表示为位图,即存储宽度 x 高度像素的内存缓冲区。所以,缩放 canvas 会致使像素化。若是缩放致使调整图片大小,咱们须要调整 canvas 大小。咱们发现分配 canvases 代价很高,所以调整它们的大小。最重要的是,当将多个 canvases 合成到同一页面上时,浏览器必须执行栅格合成,这也会显示在咱们的配置文件中。合成栅格与显示列表的工做方式不一样。你能够将多个显示列表绘制到同一个内存缓冲区中。咱们调用 Canvas 2D 支持的 canvas 实现 BitmapCanvas。咱们正在研究使位图画布更有效的方法。
为了表达 Flutter 的不透明度、变换、偏移、剪辑矩形和其余图层,咱们使用纯 HTML 元素。例如,不透明层变为 <flt-opacity>
元素,其上具备 opacity
CSS 属性,变换图层变为带有变换 CSS 属性的 <flt-transform>
元素,剪辑 rect 变为带 overflow: hidden
的 <flt-clip-rect >
。
完成全部操做后,框架将做为 HTML 元素树呈如今页面上,其中 DomCanvas 和 BitmapCanvas 做为叶节点。例如:
Flutter Engine 中的等效Flutter 层树(称为 flow layer )以下所示:
在结构上它们很是类似。最大的区别是,在Web上,咱们必须根据内容选择不一样的图片实现。
HTML + CSS + Canvas 适用于全部现代浏览器。 可是,咱们已经在展望将来:
CSS Paint 是一个新的 Web API,是 Houdini 的一个更有用的一部分。 Houdini 是许多浏览器供应商之间的合做成果,旨在向开发人员公开 CSS 引擎的某些部分。特别是,CSS Paint API 容许开发人员在这些元素请求绘制时将自定义图形绘制成 HTML 元素。例如,你能够将元素 background 的绘制分配给自定义 CSS 绘制工具。它与 canvas 很是类似,但有如下重要区别:
这个绘制不是由主要的 JavaScript 独立完成的,而是由一个叫作 paint worklet 的东西完成的。它有点像 Web 工做者,由于它有本身的内存空间。在提交 DOM 更改以后,在浏览器的绘制阶段执行绘制工做。
CSS 绘制由显示列表支持,而不是位图。这为咱们提供了一箭双鵰 - 2D canvas 般的绘制效率和无像素化。
目前 CSS 绘制不支持绘制文本。
在撰写本文时,Chrome 和 Opera 是惟一支持 CSS Paint 生产的浏览器。可是,其余浏览器处于实现过程的各个阶段。
咱们在 Flutter for Web 中对 CSS Paint API 进行了实验性支持,它已经显示出良好的结果,特别是在性能方面。咱们的实现只是将 paint 命令序列化为自定义 CSS 属性。paint worklet读取这些命令并执行它们。咱们使用普通的 <p>
和 <span>
HTML元素渲染文本。
咱们当前的序列化机制不是特别有效——它是一个嵌套列表转换为 JSON 的树——但 Houdini 项目的一部分是添加对类型化数组 (typed arrays) 的支持。当它变得可用时,咱们将绘制命令编码为类型化数组而不是 JSON 字符串。类型化数组是可转换的 (Transferable) ,这意味着它们能够经过引用从主隔离区传递到绘制工做区。不涉及复制内存。
Flutter Web 应用程序能够彻底访问当今在 Web 上运行的全部现有 Dart 库。
Flutter Web 应用程序彻底支持 Dart 的 JS-interop 包:package:js
和 dart:js
。
目前,Flutter 假设彻底控制网页的正确性和性能。例如,咱们只使用遵循某些性能指南的一小部分 CSS,如https://csstriggers.com/。在页面上放置任意 CSS 可能会致使 Flutter 表现不可预测。
在 Flutter for Web 应用程序中避免使用 CSS 的另外一个缘由是,在设计时,Flutter 须要在呈现框架时知道全部布局属性。 CSS 像是个黑盒子。例如,若是要显示可滚动的 widgets 列表,则必须实例化并为全部 widgets 生成 HTML 并应用必要的 CSS 属性(例如,flex-direction row 和 overflow:scroll)。而后浏览器将全部内容都放出并将其呈现为屏幕。应用程序代码不参与布局过程。
最后,本着保持Flutter代码可跨平台移植的精神,咱们避免使用 CSS,所以咱们能够在 Android 和 iOS上本机运行相同的代码。
咱们还没有为此添加适当的支持,但咱们打算在未来进行探索。 咱们正在考虑的几种方法是 <iframe>
和 shadow DOM。
咱们还没有添加对在 Flutter Web 应用程序中嵌入非 Flutter 组件(自定义元素、React 组件、Angular 组件)的支持,但咱们打算在未来探讨这一点。 一种可能的途径是使用平台视图将外来内容放入 Flutter Web 应用程序中。 须要考虑的一个重要方面是外来内容可能对应用程序的性能和正确性产生何种影响。 由于非 Flutter 组件可能包含任意 CSS,如上所述,它可能会有问题。 须要更多的研究。
咱们的目标是尽量多地将框架移植到 Web 上。 可是,这并不意味着任何 Flutter 应用程序将在 Web 上运行而不会更改代码。 Flutter 网络应用程序仍然是一个 Web 应用程序; 它在浏览器中被沙盒化,只能执行 Web 浏览器容许的操做。 例如,若是你的 Flutter 应用程序使用没有 Web 实现的本机插件(例如 ARCore ),你将没法在Web上运行该应用程序。 一样,没有直接访问文件系统或低级网络的权限。
咱们构建了足够的 Web 引擎来渲染大部分的 Flutter Gallery。 咱们尚未移植 Cupertino widget,但全部Material widget、Material Theming,以及 Shrine 和 Contact Profile 的 demo 都在 Web 上运行。
咱们计划很快开源这个项目,而且很高兴与开源社区分享。 该项目最初是做为 Google 内部源代码树的一项探索而开始的。 咱们的代码稳定后,咱们打算将开发转移到 GitHub,咱们有机会将其从内部基础架构中移出来。与此同时,若是你在 github.com/flutter 组织下看到与 Web 相关的拉取请求,请不要感到惊讶!
我但愿这篇文章能让你了解咱们正在解决的问题,以使 Flutter 在 Web 上很好的运行。 咱们欢迎你的想法和创意。
请继续关注Google I / O 2019!
若是喜欢的话能够点个关注或收藏吧!本文为我的原创翻译,转载请注明出处。