原文做者:Yegor Jbanov
css
譯者:UC 國際研發 Jothy前端
寫在最前:歡迎你來到「UC國際技術」公衆號,咱們將爲你們提供與客戶端、服務端、算法、測試、數據、前端等相關的高質量技術文章,不限於原創與翻譯。git
今天,咱們在 Flutter Live 上宣佈了一個消息:咱們正嘗試在 Web 上運行 Flutter。 這篇文章描述了咱們應對挑戰的方式,以及該技術的當前狀態。 在文末,咱們附上了協同工做和嵌入等問題的答案。
github
讓咱們快速回顧一下 Flutter 的架構。 Flutter 是一個多層系統,這樣高的層更易用,用不多的代碼就能表達不少,而較低的層能提供更多的控制,代價是必須處理一些複雜性。 當較高層不能知足開發者的需求時,它們能夠降到較低層。 開發者能夠訪問 Flutter Engine 之上的全部層。
web
Flutter 的 Mobile 架構算法
在 Flutter 中,Flutter Engine 做爲最低級別的庫 dart:ui 暴露。它不關心 組件,物理實現,動畫或佈局(文本佈局除外)。它所關心的是如何將圖片組合到屏幕上,渲染變成像素。在 dart:ui 上直接編寫應用是很困難的。這正是咱們建立更高層的緣由。canvas
dart:ui 之上的一切是咱們所謂的「框架」。它下面的一切都是「引擎」。該框架徹底使用 Dart 語言編寫。大多數引擎都是用 C++ 編寫的,特定於 Android 的部分用 Java 編寫,而 iOS 特定的部分用 Objective-C 編寫。 dart:ui 中的一些基本類和函數是用 Dart 編寫的,主要用做 Dart 和 C++ 之間的橋樑。數組
Flutter 還提供插件系統。插件使用指定語言編寫,能夠直接訪問移動生態系統日積月累的 OEM 庫和第三方庫。你可使用 Java 或 Kotlin 爲 Android 建立插件。 iOS 插件開發是使用 Objective-C 或 Swift。瀏覽器
Hello, The Web網絡
Web 平臺已經發展了數十年,包含了許多技術和規範。 有一些涵蓋性術語用於描述大量相關功能:HTML,CSS,SVG,JavaScript,WebGL。 爲了在 Web 上運行 Flutter,咱們須要:
編譯 Dart 代碼:Flutter 是用 Dart 編寫的,咱們須要在 Web 上運行 Dart。
選擇要在 Web 上運行的 Flutter 子集:在 Web 上運行全部 Flutter 代碼是不切實際的。 其中一些是特定於平臺的,例如 Android 和 iOS。
選擇足夠的 Web 功能子集:隨着時間的推移,Web 平臺會累積重複的功能。 例如,你可使用 HTML + CSS,SVG,Canvas 和 WebGL 繪製圖形。
從 Dart 誕生之初,它就一直在編譯 JavaScript。 如今有許多重要的應用都從 Dart 編譯爲 JavaScript,並在生產環境中運行。 Flutter 的編譯策略依賴於一樣的基礎設施。
當咱們開始探索時,咱們面臨着 UI 渲染的幾種選擇。 咱們很快意識到,要想支持的特定 Flutter 層,決定了咱們將用什麼 Web 技術。 咱們構建了三個原型:
僅僅是 Widgets(組件):這個原型實現了 Flutter 的 widget 框架,並提供了一組核心佈局 widget 做爲構建自定義 widget 的基礎。 對於佈局和定位,它依賴於 Web 的內置功能,例如 flexbox,grid 佈局,瀏覽器滾動(經過 overflow:scroll 實現)等。
Widgets + 自定義佈局:此原型包括 Flutter 的佈局系統(由 RenderObject 提供),但將渲染對象直接映射到 HTML 元素。
Flutter Web Engine:這個原型保留了 dart:ui 之上的全部層,並提供了一個在瀏覽器中運行的 dart:ui 實現。
Flutter 最有價值的功能之一是它能夠跨平臺移植。 你徹底能夠(有時甚至被鼓勵)編寫自定義的特定平臺代碼,代碼無需跨平臺定製便可共享。 意味着使用單個代碼庫就能夠編寫面向多個平臺的應用。
在嘗試將幾個示例應用移植到 Web 以後,咱們意識到原型 #1 和 #2 不能提供 Flutter 開發者喜歡的可移植性級別。 所以,咱們決定使用 Flutter Web Engine 設計的原型 #3,由於它有着平臺之間最高的框架級代碼重用:
Flutter的Web架構 (Hummingbird)
既然咱們知道咱們想要實現整個 dart:ui API,咱們須要選擇一組 Web 技術來構建。 Flutter 一次渲染一幀 UI。 在每一個幀內,Flutter 會構建 widgets,執行佈局,最後在屏幕上繪製它們。
構建 Widgets
Widget 構建機制不依賴於應用運行的環境。該過程只是實例化內存中的對象,跟蹤其狀態、以及狀態變動什麼時候計算系統低級別的最小更新,佈局和繪製等。 將此部分移植到 Web 上很是簡單。 在 Dart 團隊用 dart2js 中實現了 super-mixin 支持以後,編譯器將全部 widget 和 widget frame 都編譯成了 JavaScript,幾乎沒有 issue 產生。
佈局
佈局系統有點棘手。 最大的挑戰是文本佈局。 除了 Center,Row, Column,Stack,Scrollable,Padding,Wrap 等以外的全部內容都由框架佈局,所以無需修改便可編譯到 Web。
在 Flutter 中,你能夠建立 Paragraph 對象並調用其 layout() 方法來實現文本佈局。 不幸的是,Web 缺乏直接的文本佈局 API。 咱們用來測量文本佈局屬性的技巧是:先讓瀏覽器佈局,而後從 DOM 元素中讀回相關屬性。
佈局文本段落時,Flutter 會測量段落的高度,寬度,最大內在寬度,最小內在寬度以及字母和表意基線。 這些屬性以下所示。
Paragraph layout attributes
你能夠在 Flutter 的 Paragraph 文檔中找到更多詳細信息。
要測量這些屬性,咱們首先在 HTML DOM 元素中放置一個段落,而後讀取元素的維度。 這會引發瀏覽器佈局。 例如,要獲取元素的寬度和高度,咱們調用 offsetWidth 及 offsetHeight。 爲了測量基線,咱們將段落放置在一個元素中,該元素配置爲使用 flex 行進行佈局。 在段落旁邊,咱們放置另外一個名爲 probe 的元素。 由於 probe 與文本的基線對齊,因此調用 getBoundingClientRect 就能夠獲得基線。 咱們使用相似的技巧來測量最小和最大固有寬度。
不得不提的是,咱們得繪製 widgets。 對這個區域的探索最是麻煩,它仍然是咱們的研究中最活躍的領域。 在框架最後,咱們全部的 widget 都須要在屏幕上繪製成像素。 在瀏覽器中,這意味着它們必須歸結爲 HTML / CSS,Canvas,SVG 和 WebGL 的某種組合。
咱們尚未看過 WebGL,主要是由於它級別較低,而且要求咱們從新實現瀏覽器已經能夠作的事情,例如文本佈局和光柵化 2D 圖形。另外一個緣由是咱們尚未弄清楚非 Flutter 組件與 WebGL 如何結合才能實現可訪問性,文本選擇,和組合。
咱們的早期原型爲每一個 RenderObject 生成了一個 HTML 元素。 結果符合預期,但最後事實卻證實 API 的變化太大了。 咱們必須用 Flutter 維持一個巨大的代碼增量,因此咱們擱置了這個想法。
咱們目前正在同時探索兩種方法:
HTML+CSS+Canvas
CSS Paint API
咱們更喜歡 HTML + CSS,由於它受瀏覽器的顯示列表支持。這意味着咱們能夠把圖片的光柵化優化留給瀏覽器的渲染引擎去作。而且,咱們還能夠應用任意變換,尤爲是旋轉和縮放,而沒必要擔憂像素化。咱們將此畫布實現稱爲 DomCanvas。
若是咱們沒法使用 HTML + CSS 表達圖片,咱們會用 Canvas。 Canvas 2D 容許咱們繪製幾乎全部的 Flutter 繪圖命令。若是將 Flutter 的 Canvas 與 Web 的 CanvasRenderingContext2D 進行比較,你會發現許多類似之處。在 Canvas 上繪畫很高效,由於它不會建立須要隨時間維護的可變樹節點,如 HTML DOM 或 SVG。
2D Canvas 的一個挑戰是瀏覽器將其表示爲位圖,即存儲 Width x Height 像素的內存緩衝區。所以,縮放 canvas 會致使像素化。若是縮放致使圖片大小變化,咱們也須要調整 canvas 大小。咱們發現分配 canvas 操做至關昂貴,所以方案改爲調整它們的大小。最重要的是,當將多個 canvas 合成到同一頁面上時,瀏覽器必須執行柵格合成,這也須要配置。合成柵格與顯示列表的方式不一樣。你能夠將多個顯示列表繪製到同一個內存緩衝區中。咱們調用 Canvas 2D 支持的 canvas 實現 BitmapCanvas。咱們正在發掘使位圖 canvas 更高效的方法。
爲了表達 Flutter 的不透明度,變換,偏移,剪輯矩形和其餘圖層,咱們使用純 HTML 元素。例如,不透明度層變爲 <flt-opacity> 元素,其上具備 opacity CSS 屬性,變換圖層變爲帶有 transform CSS 屬性的 <flt-transform> 元素,剪輯 rect 變爲使用 overflow: hidden
的 <flt-clip-rect >。
完成全部操做後,框架將做爲 HTML 元素樹呈如今頁面上,其中 DomCanvas 和 BitmapCanvas 做爲葉節點。舉個例子🌰:
Sample HTML DOM structure of a frame
Flutter Engine 中的等效 Flutter layer tree(稱爲flow layer)以下所示:
Sample Flutter Engine layer structure
它們的結構很是類似。 最大的區別是,在 Web 上,咱們必須根據內容選擇不一樣的圖片實現。
HTML + CSS + Canvas 適用於全部現代瀏覽器。 可是,咱們已經在展望將來:
CSS Paint 是一個新的 Web API,是 Houdini 的更大組成部分。 Houdini 是多個瀏覽器廠商合做的項目,旨在向開發者展現 CSS 引擎的某些部分。 特別的是,CSS Paint API 容許開發者在這些元素請求繪製時將自定義圖形繪製成 HTML 元素。 例如,你能夠將元素背景的繪製分配給自定義 CSS 繪製器。 它與 canvas 很是像,但有如下重要區別:
這個繪畫不是由核心 JavaScript 獨立完成的,而是由一個叫作 paint worklet 的東西完成的。 它有點像 web worker,由於它有本身的內存空間。 它會在 DOM 更改提交以後,在瀏覽器的繪製階段執行繪製工做。
CSS paint 由顯示列表支持,而不是位圖。 這真是一箭雙鵰 - 2D canvas 般的繪畫效率和無像素化。
目前 CSS paint 不支持繪製文本。
在撰寫本文時,Chrome 和 Opera 是惟一在正式版本中支持 CSS Paint 的瀏覽器。 而其餘瀏覽器正處於發佈各自實現的不一樣階段。
咱們在 Flutter for Web 中對 CSS Paint API 進行了實驗性支持,它已經展示出良好的結果,特別是在性能方面。 咱們的實現只是將 paint 命令序列化爲自定義 CSS 屬性。 paint worklet 讀取這些命令並執行它們。 咱們使用像 <p> 和 <span> 這樣普通的 HTML 元素來渲染文本。
咱們當前的序列化機制不是特別有效 - 它是一個嵌套列表轉換成的 JSON 樹 - 但 Houdini 項目的一部分是添加對類型化數組的支持。 當它可用時,咱們會將繪製命令編碼爲類型化數組而不是 JSON 字符串。 類型化數組是可轉移的,這意味着它們能夠經過引用從主 JavaScript 傳遞到 paint worklet,而不復制內存。
從 Flutter 調用 Dart 庫
Flutter Web 應用能夠訪問當前在 Web 上運行的全部 Dart 庫。
從 Flutter 調用 JavaScript 庫
Flutter Web 應用徹底支持 Dart 的 JS-interop 軟件包:package:js和 dart:js
。
在 Flutter Web 應用中使用 CSS
目前,Flutter 假定徹底控制網頁的正確性和性能。 例如,咱們只使用遵循某些性能指南的一小部分 CSS,例如 https://csstriggers.com/。 在頁面上隨意使用 CSS 可能會致使 Flutter 出現不可預期的後果。
在 Flutter for Web 應用中避免使用 CSS 的另外一個緣由是,在設計時,Flutter 須要在渲染框架時知道全部佈局屬性。 CSS 充當黑盒。 例如,若是要顯示可滾動的窗口 widget 列表,則必須實例化併爲全部 widgets 生成 HTML 並應用必要的 CSS 屬性(例如,flex-direction row 和 overflow:scroll)。 而後瀏覽器將全部內容都佈局並將其渲染到屏幕上。 應用代碼不參與佈局過程。
最後,本着保持 Flutter 代碼可跨平臺移植的精神,咱們儘可能避免使用 CSS,所以咱們能夠在 Android 和 iOS 上本機運行相同的代碼。
將 Flutter 嵌入現有的 Web 應用中
咱們尚未爲此添加適當的支持,但咱們打算在未來探索。 咱們正在考慮的方法是 <iframe> 和 shadow DOM。
在 Flutter 中嵌入非 Flutter 組件
咱們還未支持在 Flutter Web 應用中嵌入非 Flutter 組件 - 自定義元素、React 組件、Angular 組件,但咱們打算在未來探索。 有多是使用平臺視圖將外部內容放入 Flutter Web 應用中。須要考慮的是外部內容可能對應用的性能和正確性產生影響。 由於非 Flutter 組件可能包含任意 CSS,如上所述,它可能會有問題。 須要更多的研究。
可移植性
咱們的目標是儘量多地將框架移植到 Web 上。 可是,這並不意味着任何 Flutter 應用將在 Web 上運行而不更改代碼。 Flutter Web 應用仍然是一個 Web 應用; 它在瀏覽器中被沙箱化,只能執行 Web 瀏覽器容許的操做。 例如,若是你的 Flutter 應用使用 Web 未實現的本機插件(例如 ARCore),你將沒法在 Web 上運行該應用。 一樣,也無權限直接訪問文件系統或低級網絡。
當前狀態
咱們構建了足夠的 Web 引擎來渲染大部分 Flutter Gallery。 咱們還未移植 Cupertino widgets,但全部 Material widgets,Material Theming,以及 Shrine 和 Contact Profile 演示應用均已運行在 Web 上。
Flutter running in desktop Chrome 演講視頻https://www.youtube.com/watch?v=5IrPi2Eo-xM
咱們計劃很快開源這個項目,並很高興與開源社區分享。 該項目最初是做爲 Google 內部源代碼樹的一項探索而開始的。 待代碼穩定後,咱們打算將開發轉移到 GitHub,咱們有機會將其從內部基礎架構中剝離出來。 與此同時,若是您在 github.com/flutter 組織下看到與 Web 相關的 pull 請求,請不要感到驚訝!
結論
但願這篇文章能讓你瞭解咱們正在解決的問題,以幫助 Flutter 在 Web 上更好運行。 歡迎表達您的觀點和意見。
請繼續關注 Google I/O 2019!
英文原文:
https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8
好文推薦:
我想學Flutter,可是我不知道應該如何開始?
Google Flutter團隊成員李宇騫 確認出席第13屆D2前端技術論壇
「UC國際技術」致力於與你共享高質量的技術文章
歡迎關注咱們的公衆號、將文章分享給你的好友