筆者注: 隨着 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!
若是喜歡的話能夠點個關注或收藏吧!本文爲我的原創翻譯,轉載請註明出處。