翻譯:https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8
在今天的Flutter Live上冈绊,我們宣布我們正在嘗試在網(wǎng)上運(yùn)行Flutter刽虹。在這篇文章中国夜,我們描述了我們?nèi)绾螒?yīng)對挑戰(zhàn)以及技術(shù)的當(dāng)前狀態(tài)威始。在帖子的最后,您將找到有關(guān)互操作和嵌入的問題的答案肤寝。
讓我們快速回顧一下Flutter的架構(gòu)当辐。Flutter是一個(gè)多層系統(tǒng),這樣更高的層更容易使用鲤看,并允許您用很少的代碼表達(dá)很多缘揪,而較低的層為您提供更多的控制,代價(jià)是必須處理一些復(fù)雜性。當(dāng)較高層不能滿足開發(fā)人員的需求時(shí)找筝,它們可以降到較低層蹈垢。開發(fā)人員可以訪問Flutter Engine上方的所有層。
Flutter Engine作為Flutter中最低級別的庫暴露袖裕,dart:ui耘婚。它對小部件,物理陆赋,動(dòng)畫或布局(文本布局除外)一無所知。它所知道的是如何將圖片組合到屏幕上并將它們變成像素嚷闭。在dart:ui上直接編寫應(yīng)用程序是很困難的攒岛。這就是創(chuàng)建更高層的原因。
以上所有事情:ui是我們所謂的“框架”胞锰。它下面的一切都是“引擎”灾锯。該框架完全使用Dart編程語言編寫。大多數(shù)引擎都是用C ++編寫的嗅榕,特定于Android的部分用Java編寫顺饮,而iOS特定的部分用Objective-C編寫。dart中的一些基本類和函數(shù):ui是用Dart編寫的凌那,主要用作Dart和C ++之間的橋梁兼雄。
Flutter還提供插件系統(tǒng)。插件是用一種語言編寫的代碼帽蝶,可以直接訪問移動(dòng)生態(tài)系統(tǒng)隨著時(shí)間累積的OEM庫和第三方庫赦肋。要為Android創(chuàng)建插件,您可以編寫Java或Kotlin励稳。iOS插件是使用Objective-C或Swift編寫的佃乘。
你好,網(wǎng)絡(luò)
Web平臺已經(jīng)發(fā)展了數(shù)十年驹尼,包括許多技術(shù)和規(guī)范趣避。有一些總括性術(shù)語用于描述大量相關(guān)功能:HTML,CSS新翎,SVG程帕,JavaScript,WebGL地啰。為了在Web上運(yùn)行Flutter骆捧,我們需要:
- 編譯Dart代碼: Flutter是用Dart編寫的,我們需要在Web上運(yùn)行Dart髓绽。
- 選擇要在Web上運(yùn)行的Flutter子集:在Web上運(yùn)行所有Flutter代碼是不實(shí)際或有用的敛苇。其中一些是特定于平臺的,例如Android和iOS位。
- 選擇足夠的Web功能子集:隨著時(shí)間的推移枫攀,Web平臺會累積功能重疊的功能括饶。例如,您可以使用HTML + CSS来涨,SVG图焰,Canvas和WebGL繪制圖形。
只要語言存在蹦掐,Dart就一直在編譯JavaScript技羔。許多重要的應(yīng)用程序從Dart編譯為JavaScript,并在今天的生產(chǎn)中運(yùn)行卧抗。Flutter的編譯策略依賴于同樣的基礎(chǔ)設(shè)施藤滥。
當(dāng)我們開始探索時(shí),我們面臨著UI渲染的幾種選擇社裆。我們很快意識到我們想要支持的特定Flutter層決定了我們將用于實(shí)現(xiàn)的Web技術(shù)拙绊。我們制作了三個(gè)原型:
-
只是小部件:這個(gè)原型實(shí)現(xiàn)了Flutter的小部件框架,并提供了一組核心布局小部件作為構(gòu)建自定義小部件的基礎(chǔ)泳秀。對于布局和定位标沪,它依賴于Web的內(nèi)置功能,例如flexbox嗜傅,網(wǎng)格布局金句,瀏覽器滾動(dòng)瀏覽
overflow:scroll
等。 -
小部件+自定義布局:此原型包括Flutter的布局系統(tǒng)(由提供
RenderObject
)吕嘀,但將渲染對象直接映射到HTML元素趴梢。 - Flutter Web Engine:這個(gè)原型保留了dart:ui之上的所有層,并提供了一個(gè)在瀏覽器中運(yùn)行的dart:ui實(shí)現(xiàn)币他。
Flutter最有價(jià)值的功能之一是它可以跨平臺移植坞靶。雖然您可以(有時(shí)甚至鼓勵(lì))編寫自定義平臺特定代碼,但可以共享跨平臺不需要不同的代碼蝴悉。這允許使用單個(gè)代碼庫編寫面向多個(gè)平臺的應(yīng)用程序彰阴。
在嘗試將幾個(gè)示例應(yīng)用程序移植到Web之后,我們意識到原型#1和#2不能提供Flutter開發(fā)人員喜歡的可移植性級別拍冠。因此尿这,我們決定使用Flutter Web Engine設(shè)計(jì)的原型#3,因?yàn)檫@將允許平臺之間最高的框架級代碼重用:
現(xiàn)在我們知道我們想要實(shí)現(xiàn)整個(gè)dart:ui API庆杜,我們需要選擇一組Web技術(shù)來構(gòu)建射众。Flutter一次呈現(xiàn)一幀UI。在每個(gè)框架內(nèi)晃财,F(xiàn)lutter 構(gòu)建小部件叨橱,執(zhí)行布局,最后在屏幕上繪制它們。
構(gòu)建小部件
窗口小部件構(gòu)建機(jī)制不依賴于應(yīng)用程序運(yùn)行的環(huán)境罗洗。該過程只是實(shí)例化內(nèi)存中的對象愉舔,跟蹤它們的狀態(tài),以及狀態(tài)更改何時(shí)計(jì)算系統(tǒng)的較低級別伙菜,布局和繪制所需的最小更新轩缤。將此部分移植到Web上非常簡單。在Dart團(tuán)隊(duì)在dart2js中實(shí)現(xiàn)了super-mixin支持后贩绕,編譯器將所有小部件和小部件框架編譯為JavaScript火的,幾乎沒有任何問題。
布局
布局系統(tǒng)有點(diǎn)棘手淑倾。最大的挑戰(zhàn)是文本布局馏鹤。其他所有內(nèi)容 - 中心,行踊淳,列,堆棧陕靠,可滾動(dòng)迂尝,填充,換行等 - 由框架布局剪芥,因此無需修改即可編譯到Web垄开。
在Flutter中,您可以通過創(chuàng)建Paragraph對象并調(diào)用其layout()方法來布置一段文本税肪。不幸的是溉躲,Web缺少直接的文本布局API。我們用來測量文本布局屬性的技巧是讓瀏覽器將其布局益兄,然后從DOM元素中讀回相關(guān)屬性锻梳。
在布置一段文本時(shí),F(xiàn)lutter測量段落的高度净捅,寬度疑枯,最大內(nèi)在寬度,最小內(nèi)在寬度以及字母和表意基線蛔六。這些屬性如下所示荆永。
您可以在Flutter的段落文檔中找到更多詳細(xì)信息。
要測量這些屬性国章,我們首先在HTML DOM元素中放置一個(gè)段落具钥,然后我們讀取元素的維度。這會導(dǎo)致瀏覽器將其布局液兽。例如骂删,要獲取元素的寬度和高度,我們調(diào)用offsetWidth及其兄弟offsetHeight。為了測量基線桃漾,我們將段落放置在一個(gè)元素中坏匪,該元素被配置為使用flex行進(jìn)行自我布局。在段落旁邊撬统,我們放置另一個(gè)名為“probe”的元素适滓。因?yàn)樘结樑c文本的基線對齊,所以調(diào)用getBoundingClientRect就可以得到基線恋追。我們使用類似的技巧來測量最小和最大內(nèi)在寬度凭迹。
繪畫
最后但并非最不重要的是,我們需要繪制小部件苦囱。這個(gè)區(qū)域在我們的探索中經(jīng)歷了最多的流失嗅绸,它仍然是我們研究中最活躍的領(lǐng)域之一。在框架結(jié)束時(shí)撕彤,我們所有的小部件都需要在屏幕上變成像素鱼鸠。在瀏覽器中,這意味著它們必須歸結(jié)為HTML / CSS羹铅,Canvas蚀狰,SVG和WebGL的某種組合。
我們還沒有看過WebGL职员,主要是因?yàn)樗堑图墑e的并且要求我們重新實(shí)現(xiàn)瀏覽器已經(jīng)可以做的事情麻蹋,例如文本布局和光柵化2D圖形,還因?yàn)槲覀冞€沒有弄清楚可訪問性焊切,文本選擇扮授,使用非Flutter組件的組合可以與WebGL一起使用。
我們的早期原型之一為每個(gè)RenderObject生成了一個(gè)HTML元素专肪。我們確實(shí)獲得了有希望的結(jié)果刹勃,但結(jié)果證明API變化太大了。我們必須用Flutter維持一個(gè)巨大的代碼增量嚎尤,所以我們擱置了這個(gè)想法深夯。
我們目前正在同時(shí)探索兩種方法:
- HTML + CSS +帆布
- CSS Paint API
HTML + CSS +帆布
通過這種方法,我們將框架生成的圖片分類為使用HTML + CSS表達(dá)的圖片诺苹,以及使用Canvas 2D表達(dá)的圖片咕晋。然后,我們輸出結(jié)合了HTML收奔,CSS和2D畫布的HTML DOM掌呜。
我們更喜歡HTML + CSS,因?yàn)樗蔀g覽器的顯示列表支持坪哄。這意味著我們可以優(yōu)化圖片的光柵化到瀏覽器的渲染引擎质蕉。這也意味著我們可以應(yīng)用任意變換势篡,尤其是旋轉(zhuǎn)和縮放,而不必?fù)?dān)心像素化模暗。我們將此畫布實(shí)現(xiàn)稱為DomCanvas禁悠。
如果我們無法使用HTML + CSS表達(dá)圖片,我們會回到畫布兑宇。Canvas 2D允許我們繪制幾乎所有的Flutter繪圖命令碍侦。如果您將Flutter的Canvas與Web的CanvasRenderingContext2D進(jìn)行比較,您會發(fā)現(xiàn)許多相似之處隶糕。在畫布上繪畫是有效的瓷产,因?yàn)樗粫?chuàng)建需要隨時(shí)間維護(hù)的可變樹節(jié)點(diǎn),如HTML DOM或SVG枚驻。
2D畫布的一個(gè)挑戰(zhàn)是瀏覽器將其表示為位圖濒旦,即存儲寬度 x 高度像素的內(nèi)存緩沖區(qū)。因此再登,縮放畫布會導(dǎo)致像素化尔邓。如果縮放導(dǎo)致調(diào)整圖片大小,我們需要調(diào)整畫布大小锉矢。我們發(fā)現(xiàn)分配畫布相當(dāng)昂貴梯嗽,因此調(diào)整它們的大小。最重要的是沈撞,當(dāng)將多個(gè)畫布合成到同一頁面上時(shí)慷荔,瀏覽器必須執(zhí)行柵格合成雕什,這也會顯示在我們的配置文件中缠俺。合成柵格與顯示列表的工作方式不同。您可以將多個(gè)顯示列表繪制到同一個(gè)內(nèi)存緩沖區(qū)中贷岸。我們調(diào)用Canvas 2D支持的canvas實(shí)現(xiàn)BitmapCanvas壹士。我們正在研究使位圖畫布更有效的方法。
為了表達(dá)Flutter的不透明度偿警,變換躏救,偏移,剪輯矩形和其他圖層螟蒸,我們使用純HTML元素盒使。例如,不透明層變?yōu)?code><flt-opacity>具有opacity
CSS屬性的元素七嫌,變換層變?yōu)?code><flt-transform>具有transform
CSS屬性的元素少办,剪輯rect變?yōu)?code><flt-clip-rect>with overflow: hidden
。
完成所有操作后诵原,框架將作為HTML元素樹呈現(xiàn)在頁面上英妓,其中DomCanvas和BitmapCanvas作為葉節(jié)點(diǎn)挽放。例如:
Flutter Engine中的等效Flutter層樹(稱為流層)如下所示:
在結(jié)構(gòu)上它們非常相似。最大的區(qū)別是蔓纠,在Web上辑畦,我們必須根據(jù)內(nèi)容選擇不同的圖片實(shí)現(xiàn)。
HTML + CSS + Canvas適用于所有現(xiàn)代瀏覽器腿倚。但是纯出,我們已經(jīng)在展望未來:
CSS Paint API
CSS Paint是一個(gè)新的Web API,是Houdini的一個(gè)更大的努力的一部分猴誊。Houdini是許多瀏覽器供應(yīng)商之間的合作潦刃,旨在向開發(fā)人員公開CSS引擎的某些部分。特別是懈叹,CSS Paint API允許開發(fā)人員在這些元素請求繪制時(shí)將自定義圖形繪制成HTML元素乖杠。例如,您可以將元素的繪制分配給background
自定義CSS畫家澄成。它與canvas非常相似胧洒,但有以下重要區(qū)別:
- 這個(gè)繪畫不是由主要的JavaScript獨(dú)立完成的,而是由一個(gè)叫做paint worklet的東西完成的墨状。它有點(diǎn)像Web工作者卫漫,因?yàn)樗凶约旱膬?nèi)存空間。在提交DOM更改之后肾砂,在瀏覽器的繪制階段執(zhí)行繪制工作列赎。
- CSS繪制由顯示列表支持,而不是位圖镐确。這為我們提供了兩全其美 - 2D畫布般的繪畫效率和無像素化包吝。
- 目前CSS繪畫不支持繪畫文本。
在撰寫本文時(shí)源葫,Chrome和Opera是唯一支持CSS Paint生產(chǎn)的瀏覽器诗越。但是,其他瀏覽器處于運(yùn)送其實(shí)現(xiàn)的各個(gè)階段息堂。
我們在Flutter for Web中對CSS Paint API進(jìn)行了實(shí)驗(yàn)性支持嚷狞,它已經(jīng)顯示出良好的結(jié)果,特別是在性能方面荣堰。我們的實(shí)現(xiàn)只是將paint命令序列化為自定義CSS屬性床未。paint worklet讀取這些命令并執(zhí)行它們。我們使用普通<p>
和<span>
HTML元素渲染文本振坚。
我們當(dāng)前的序列化機(jī)制不是特別有效 - 它是一個(gè)嵌套列表轉(zhuǎn)換為JSON的樹 - 但Houdini項(xiàng)目的一部分是添加對類型化數(shù)組的支持薇搁。當(dāng)它變得可用時(shí),我們將繪制命令編碼為類型化數(shù)組而不是JSON字符串屡拨。類型化數(shù)組是可轉(zhuǎn)換的只酥,這意味著它們可以通過引用從主隔離區(qū)傳遞到繪制工作區(qū)褥实。不涉及復(fù)制內(nèi)存。
互操作和嵌入
從Flutter調(diào)用Dart庫
Flutter Web應(yīng)用程序可以完全訪問當(dāng)今在Web上運(yùn)行的所有現(xiàn)有Dart庫裂允。
從Flutter調(diào)用JavaScript庫
Flutter Web應(yīng)用程序完全支持Dart的JS-interop軟件包:package:js
和dart:js
损离。
在Flutter Web應(yīng)用程序中使用CSS
目前,F(xiàn)lutter假設(shè)完全控制網(wǎng)頁的正確性和性能绝编。例如僻澎,我們只使用遵循某些性能指南的一小部分CSS,例如https://csstriggers.com/十饥。在頁面上放置任意CSS可能會導(dǎo)致Flutter表現(xiàn)不可預(yù)測窟勃。
在Flutter for Web應(yīng)用程序中避免使用CSS的另一個(gè)原因是,在設(shè)計(jì)時(shí)逗堵,F(xiàn)lutter需要在呈現(xiàn)框架時(shí)知道所有布局屬性秉氧。CSS充當(dāng)黑盒子。例如蜒秤,如果要顯示可滾動(dòng)的窗口小部件列表绅络,則必須實(shí)例化并為所有窗口小部件生成HTML并應(yīng)用必要的CSS屬性(例如列牺,flex-direction row和overflow:scroll)。然后瀏覽器將所有內(nèi)容都放出并將其呈現(xiàn)為屏幕作喘。應(yīng)用程序代碼不參與布局過程灭袁。
最后尸红,本著保持Flutter代碼可跨平臺移植的精神绩郎,我們避免使用CSS蔼水,因此我們可以在Android和iOS上本機(jī)運(yùn)行相同的代碼。
將Flutter嵌入現(xiàn)有的Web應(yīng)用程序中
我們尚未為此添加適當(dāng)?shù)闹С峙遥覀兇蛩阍趯磉M(jìn)行探索蚤假。我們正在考慮的幾種方法是<iframe>
影子DOM。
在Flutter中嵌入非Flutter組件
我們尚未添加對在Flutter Web應(yīng)用程序中嵌入非Flutter組件(自定義元素田绑,React組件勤哗,Angular組件)的支持抡爹,但我們打算在將來探討這一點(diǎn)掩驱。一種可能的途徑是使用平臺視圖將外來內(nèi)容放入Flutter Web應(yīng)用程序中。需要考慮的一個(gè)重要方面是外國內(nèi)容可能對應(yīng)用程序的性能和正確性產(chǎn)生何種影響冬竟。因?yàn)榉荈lutter組件可能包含任意CSS欧穴,如上所述,它可能會有問題泵殴。需要更多的研究涮帘。
可移植性
我們的目標(biāo)是盡可能多地將框架移植到Web上。但是笑诅,這并不意味著任何Flutter應(yīng)用程序?qū)⒃赪eb上運(yùn)行而不會更改代碼调缨。Flutter網(wǎng)絡(luò)應(yīng)用程序仍然是一個(gè)Web應(yīng)用程序; 它在瀏覽器中被沙箱化疮鲫,只能執(zhí)行Web瀏覽器允許的操作。例如弦叶,如果您的Flutter應(yīng)用程序使用沒有Web實(shí)現(xiàn)的本機(jī)插件(例如ARCore)俊犯,您將無法在Web上運(yùn)行該應(yīng)用程序。同樣伤哺,沒有直接訪問文件系統(tǒng)或低級網(wǎng)絡(luò)的權(quán)限燕侠。
當(dāng)前狀態(tài)
我們構(gòu)建了足夠的Web引擎來渲染大部分的Flutter Gallery。我們還沒有移植Cupertino小部件立莉,但所有Material小部件绢彤,Material Theming,以及Shrine和Contact Profile演示應(yīng)用程序都在Web上運(yùn)行蜓耻。