拆!对比详解 Flutter Widget 和 CSS,你关心的布局原理都在这儿了
簡介:?這篇文章專門對比 Flutter Widget 的布局原理和 CSS 布局原理的差異,分享在對接過程中會遇到的問題和解決方案,幫大家理一理思路,內(nèi)容可以分為這幾部分:1、CSS 和 Widget 參賽選手介紹 2、Let's Battle! 從五個角度正面硬剛 3、Love & Peace 討論取長補短的可行性 4、Happy Ending 最重要的環(huán)節(jié)
作者|張翰(門柳)
出品|阿里巴巴新零售淘系技術(shù)部
我在之前一篇文章《打破重重阻礙,Flutter 和 Web 生態(tài)如何對接?》提到了這么一句話:“CSS 和 Widget 的對接也是很繁瑣的過程,而且存在完備性問題”。我直接給了結(jié)論,沒有給出原因。
現(xiàn)在填上這個坑,這篇文章專門對比 Flutter Widget 的布局原理和 CSS 布局原理的差異,分享在對接過程中會遇到的問題和解決方案,幫大家理一理思路,內(nèi)容可以分為這幾部分:
- CSS 和 Widget 參賽選手介紹
- Let's Battle! 從五個角度正面硬剛
- Love & Peace 討論取長補短的可行性
- Happy Ending 最重要的環(huán)節(jié)
CSS
CSS 是 Cascading Style Sheets(層疊樣式表)的簡寫,是一種用來描述樣式的標記語言,最初的想法誕生自 1994 年,1996 年落成第一版規(guī)范(參考 20 Years of CSS)。HTML 描述了頁面的結(jié)構(gòu),CSS 描述頁面呈現(xiàn)出來的樣子,這對 CP 已經(jīng)配合工作了二三十年,依然是布局圈里最高效的組合。
? CSS is Awesome
CSS 描述布局很高效,這點無需質(zhì)疑。后續(xù)出現(xiàn)的各種布局方案,前端框架 CSS in JS、XML描述文件、Flutter 等等,即使沒有直接照搬 CSS 的功能,也深受 CSS 設(shè)計的影響。
CSS 很容易上手,經(jīng)典的盒模型一學(xué)就會,文本相關(guān)的屬性看名字就知道怎么回事,Flexbox 也是很好用的,但是 CSS 逐漸出現(xiàn)了一些很難駕馭的功能,多列布局就稍微難了一點,CSS Grid … 這代碼也太難寫了吧,我覺得這是給 AI 設(shè)計的,不是讓人手寫的,再加上 clip-path, filter, css houdini 等等,功能越來越強大,可以做濾鏡、畫皮卡丘、畫油畫、甚至還可以做游戲。功能很強大,但是不實用,上述這些功能在生產(chǎn)環(huán)境基本上都是用 JS 實現(xiàn)的,沒時間去磨 CSS。
? 渲染布局流程
瀏覽器里 CSS 的渲染過程,簡化一下可以總結(jié)為 加載、解析、查詢并作用到 DOM 節(jié)點、計算布局 這四個過程。瀏覽器首先加載 HTML 文件,遇到
然后瀏覽器根據(jù)帶有 ComputedStyle 的 DOM 樹生成 LayoutTree,節(jié)點的 display 特性不同,生成的 LayoutObject 的類型也不同,然后布局算法會多次遍歷這課樹,計算出每個節(jié)點的 Rect。這個過程是很復(fù)雜的,并不是一個 DOM 節(jié)點就對應(yīng)一個 Layout 節(jié)點,要考慮 display:none、偽元素、文本節(jié)點、shadow dom 等等,而且整個過程是同步的,在解析出的 DOM 節(jié)點已經(jīng)布局完成后,如果瀏覽器又解析到一個
布局完成之后是執(zhí)行 Paint,把布局信息一層一層的提交到 compositor 線程,然后再劃分成一塊一塊的交給 GPU 線程去繪制。我覺得最后這兩步是比較快的,在主線程里的 Layout 和 Paint 才是最耗時間的,而且和 JS 代碼的執(zhí)行攪在了一起。
Flutter Widget
和 CSS 不同,Flutter Widget 設(shè)計得很細致,分類清晰功能明確,不像 CSS 那樣各種屬性耦合在一起,互相影響。Widget 的設(shè)計是比較原子化的,基本不會互相影響,為了保持布局算法的高效,對 Widget 的嵌套方式有要求。
Flutter 設(shè)計得比較合理,有一個原因不容忽視,Flutter 在 Google 的開發(fā)團隊和 Chrome 有很大淵源,有些人已經(jīng)參與制定 CSS 規(guī)范很多年,都是在這個領(lǐng)域里很有經(jīng)驗的人。整體上沒有 CSS 那么多的冗余和歷史包袱,新框架都可以吸取以前的教訓(xùn),站在巨人的肩膀上設(shè)計得越來越好用。換句話說,如果讓 CSS 的設(shè)計者們,拋開歷史包袱重新設(shè)計,不考慮向下兼容,很有可能就設(shè)計成了現(xiàn)在 Flutter Widget 的樣子。
? 渲染布局流程
關(guān)于 Flutter 的渲染流程,官方文檔Flutter 工作原理就是很好的學(xué)習(xí)資料,最大的亮點是次線性的布局,有接近 O(n) 的性能,比 CSS 高效得多。
具體過程大家去官網(wǎng)學(xué)習(xí)吧,這里就放一張圖,本文的重點是 Battle!
Let's Battle!
? Round 1: 背后的大佬
在真正開始比較之前,先看一下他們背后是什么樣的組織在支撐和運營著他們。
CSS 背后是 W3C,這是業(yè)界認可的標準化組織,而且被各大瀏覽器實現(xiàn),瀏覽器廠商也在積極的推到標準的發(fā)展。CSS 是個開放的技術(shù),它背后的大佬是 W3C、Chrome、Safai、Firefox 等等一系列盈利或者非盈利組織,大家互惠互利共同發(fā)展。
但是 Flutter 背后是是只有 Google,雖然也是開源的,但是設(shè)計與實現(xiàn)都是由 Google 的團隊來主導(dǎo),其他人都是在使用,真正有能力有機會參與開發(fā)的很少,PR 都是小修小補。框架和標準的一個差別,就是會不會向下兼容,框架有可能明天宣布推出 2.0,帶上牛逼的優(yōu)化和 breaking change,不向下兼容,是司空見慣的事。
從這個角度看,CSS 雖然臃腫,但畢竟是個標準化的技術(shù),生命力頑強。你現(xiàn)在寫的 CSS 代碼,五年之后依然可以運行,現(xiàn)在寫的 Flutter 代碼到五年后就不好說了。假如 Google 宣布不維護 Flutter 了,很可能社區(qū)里就瞬間喪失了信心,那 Flutter 就死了;假如 Chrome 宣布不支持 CSS 了,CSS 依然活得好好的,Chrome 很可能會死(參考 IE),FireFox 要笑醒了。
? Round 2: 學(xué)習(xí)成本
要說學(xué)習(xí)成本,當(dāng)然是 CSS 高效,先不討論原理,先從幾個側(cè)面的案例說明一下。
市面上能出現(xiàn)“零基礎(chǔ),三個月成為前端高手!”的培訓(xùn)班,也側(cè)面說明了前端學(xué)習(xí)成本低,其實他們的口號是錯的,三個月肯定學(xué)不會前端,能學(xué)會的只有用 CSS 切頁面。但是沒有培訓(xùn)班開“三個月掌握Flutter”的課,一個熟練的前端開發(fā)者三個月學(xué)會是可能的,客戶端上手更快一點,沒有編程基礎(chǔ)的人學(xué)完肯定一臉懵逼的要求退錢了。
另外微信可以舉辦“青少年微信小程序編程創(chuàng)意營” ,面向中小學(xué)生,小程序的 UI 就是受限的 HTML + CSS 來寫的,但是 Flutter 要搞這種比賽的話,就得面向有熟練編程經(jīng)驗的人了。
如果從語法角度考慮的話,CSS 容易學(xué)是因為它只是一種描述性語言,不含復(fù)雜的編程邏輯,設(shè)計目標就是用來描述“我想要的 UI 是什么樣子”的,是面向結(jié)果的描述,而 Widget 是要通過寫 Dart 代碼來實現(xiàn)的,UI 和代碼邏輯寫一起,通過代碼一行行描述“我怎么把 UI 組合出來”的,是面向過程的描述,所以 CSS 更直觀一些,寫出來的代碼更容易讓人理解。還有個小原因,就是像 Fluter Widget 這樣層層嵌套的代碼,寫起來和改起來都很麻煩,太依賴編輯器,復(fù)制粘貼不太方便(差不多的話,是可以抄一下代碼的嘛)。
另外 CSS 誕生了這么多年,學(xué)習(xí)資料簡直多到爆!本身規(guī)范事無巨細,還有 MDN 、CSS Tricks 、CodePen 等網(wǎng)站即授之以漁又授之于魚,各種線上培訓(xùn)也把知識框架和學(xué)習(xí)路線都給你安排的明明白白的。相比之下,Flutter Widget 就只有官網(wǎng),學(xué)習(xí)資料和社區(qū)生態(tài)都還差很多。
? Round 3: 開發(fā)效率
CSS 上手雖然簡單,但是很難掌握,沒有個幾年的開發(fā)經(jīng)驗,沒被它虐過千百遍,是根本駕馭不住它的。
假如要畫出來 CSS 的學(xué)習(xí)曲線的話,初期肯定是快速上升,到了一定高度后就變慢了,甚至還迷之下降。但是 Flutter 剛開始學(xué)習(xí)時要了解很多概念,要轉(zhuǎn)變思維,上手稍微慢了一點,但是越學(xué)越快。說個不恰當(dāng)?shù)谋扔?#xff0c;寫 CSS 可以看做是操作提線木偶,寫 Widget 就相當(dāng)于是搭樂高積木。
因為 CSS 各種屬性之間是可以互相影響的,輸入和輸出不是簡單的對應(yīng)關(guān)系,你以為你寫了 width: 100px 它的寬度就一定是 100px 了?就像是綁了幾百根線的木偶,讓你拉動其中的五根來做一個 OK 的手勢,你牽了其中一條線,動的可能不只是一個部位,而是整個上半身。
Flutter Widget 就更加原子化,而且對于誰可以嵌套誰是有要求的,就像是拼樂高積木,每塊積木都很小,但是有明確型號的卡口,要先符合它的設(shè)計,然后再發(fā)揮創(chuàng)意拼裝成各種造型。迫使你按照理想的方式去組合 Widget,避免寫出性能太差的代碼,CSS 就是任意屬性都可以和任意屬性在寫在一起,而且標準里總能給出你一個合理的解釋,所以寫出來的代碼很混亂,也增大了布局的難度。
所以 CSS 的開發(fā)效率初期較快,但是積累沉淀較難,大規(guī)模協(xié)作很難管理(耦合度高,全局作用域等),而 Flutter 的封裝性更好,有利于協(xié)作,開發(fā)效率會越來越快的。
? Round 4: 天下武功,唯快不破!
要問 Flutter 的 Widget 和 CSS 誰更快?大家的共識也是 Flutter 更快吧。我覺得 Flutter 和 CSS 布局相比有兩大性能優(yōu)勢:一個是次線性的布局算法,一個是更合理的線程模型。
Widget 的布局原理比 CSS 高效,這是犧牲了一部分靈活性換來的,以后擴展 Widget 時,都要遵循這些設(shè)計才能繼續(xù)保持高效。CSS 簡單的屬性背后可以深挖出特別復(fù)雜的細節(jié),使得布局模型越來越復(fù)雜,渲染管線越來越長,光 display 就有十幾二十個值,每一條都對布局影響巨大,方便了開發(fā)者,但是給布局性能帶來很大挑戰(zhàn)。
關(guān)于線程模型,也是瀏覽器一直被詬病的性能瓶頸,主線程太忙了,JS 的執(zhí)行、HTML/CSS 的解析、DOM 的構(gòu)建,布局的運算,全都在主線程。相比之下 Flutter 劃分的四個線程就比較均衡,GPU 線程做的工作和瀏覽器差不多,但是宿主平臺(Android/iOS)的代碼跑在 Platform 線程里,Flutter Framework 主要運行在 UI 線程里,另外還有 IO 線程實現(xiàn)網(wǎng)絡(luò)和圖片、字體等文件的加載。
? Round 5: 未來的發(fā)展
分享幾個關(guān)于 CSS 現(xiàn)狀的數(shù)據(jù),W3C 官方定義的 CSS 樣式有 520 條,Chrome 平臺上統(tǒng)計的樣式有 703 條,包括了一些帶前綴的樣式,有大量樣式的使用率很低。支付寶小程序的同學(xué)總結(jié)過 Top 100 小程序里用到的不同 CSS 樣式,有 184 條。
總結(jié)一下就是:W3C 標準里定義了 520 條樣式,Chrome 還額外支持 180+ 條私貨,但是常用的樣式不超過 200 條。
CSS 有大量歷史包袱,我自己也感覺大部分樣式我都用不到,有些甚至是最佳實踐里禁止使用的,學(xué)會 50 條 CSS 后就可以寫 80% 種情況的布局了,對于難寫的樣式,我多加幾層標簽再寫點 JS 也能實現(xiàn)。但是這些樣式不能廢棄,必須繼續(xù)支持,新增一條好用的屬性時,要解釋清楚和現(xiàn)在所有屬性的適配情況。
CSS 是負重前行,注定要越變越復(fù)雜。Widget 則是輕裝上陣,而且解耦比較好,是可插拔可組合的,如果未來想廢棄一些 Widget,把這部分 Widget 從主包里拆出來放到獨立插件里就行了,想用的話自行引入。整個迭代過程受歷史包袱的影響很小。
Love & Peace: 可否實現(xiàn)對接?
Battle 已經(jīng)結(jié)束,友誼第一比賽第二,不討論勝負,下面進入 Love & Peace 環(huán)節(jié)。
Flutter 的壓線性布局讓人眼饞, CSS 的靈活度又深受大家喜愛,可不可以取其精華去其糟粕,讓開發(fā)者寫 CSS 但是底層用 Flutter 來渲染?有這種想法的不是一個人,所以有很多方案把前端框架或小程序?qū)拥?Flutter 上,在實現(xiàn)的時候,就會遇到如何把 CSS 轉(zhuǎn)換為 Widget 的問題。
? 技術(shù)可行性
技術(shù)上當(dāng)然是可行的,我在前一篇文章《打破重重阻礙,Flutter 和 Web 生態(tài)如何對接?》里介紹了各種實現(xiàn)方案,自己也寫代碼做過對接(用的C++魔改方案),跑通了整個渲染鏈路。
我介紹一下我的實現(xiàn)方式,可以簡單分成三步:
1. 解析 CSS 語法
我寫了個精簡版的 CSSOM,是標準 CSSOM 的子集。用來實現(xiàn)樣式表和樣式屬性和增刪改查、樣式值的解析與計算、選擇器的匹配和查詢等功能。(功能是獨立的,有需要的話自取)
CSSOM 主要是為了處理 CSS 的上層語法,轉(zhuǎn)成一致的數(shù)據(jù)格式,為下一步的轉(zhuǎn)換做準備,有一部分 CSS 的語法是在這個過程實現(xiàn)的,例如 CSS 選擇器,包括偽類選擇器和選擇器關(guān)系等,還有 @media 和 @keyframes 等功能,也可以實現(xiàn) CSS variable 以及 calc()。無論上層是直接寫 CSS 還是 CSS in JS,都保證下一步轉(zhuǎn)換時輸入的數(shù)據(jù)格式一致,可以簡化后續(xù)的實現(xiàn)。
2. 實現(xiàn) CSS 屬性和 Widget 數(shù)據(jù)格式的映射
這部分要把 CSS 的基礎(chǔ)數(shù)據(jù)格式轉(zhuǎn)成構(gòu)建 Flutter Widget 所依賴的數(shù)據(jù)格式。例如 CSS 的 color 屬性會轉(zhuǎn)成 Flutter 的 Color 類,margin 和 padding 會轉(zhuǎn)成 EdgeInsets 類,flex-direction 將被轉(zhuǎn)成 Axis 枚舉,把文本相關(guān)的屬性轉(zhuǎn)換成 TextStyle 類。
這部分也是做原子性的轉(zhuǎn)換,技術(shù)上看起來比較簡單,只是轉(zhuǎn)換數(shù)據(jù)格式而已,但是需要對 CSS 和 Widget 的設(shè)計都比較了解,知道同一個概念在雙方語義中的對應(yīng)關(guān)系,得搞清楚里邊的技術(shù)細節(jié)。這個對應(yīng)關(guān)系如果轉(zhuǎn)換錯了,后續(xù)的布局怎么調(diào)都調(diào)不對(過來人的經(jīng)驗…)
3. 構(gòu)建 Widget 樹
拿到 CSSOM 傳來的數(shù)據(jù),掌握了 CSS 和 Widget 數(shù)據(jù)結(jié)構(gòu)的語義轉(zhuǎn)換,然后在結(jié)合 HTML 定義的結(jié)構(gòu),就可以生成真正的 Widget 樹了。
構(gòu)建 Widget 樹就不能只考慮 CSS 了, HTML + CSS 才和 Widget 對等。這里還要處理不同類型 HTML 節(jié)點和 Widegt 的對應(yīng)關(guān)系,節(jié)點上帶的布局樣式不同,生成的節(jié)點也不同,Widget 的層次深度比 HTML 的要深。例如普通的 div 標簽,如果僅包含普通盒模型樣式,就轉(zhuǎn)換成 Container;如果包含了 flex 相關(guān)的屬性,根據(jù)具體配置的不同,轉(zhuǎn)成 Flex/Center/Row/Column 等;如果包含了絕對定位就要轉(zhuǎn)成 Positioned/Stack。這個過程也是很繁瑣,包含了大量細節(jié),需要理解默認 HTML 標簽的語義、CSS 的層模型以及 BFC 等等,還需要理解 Flutter Widget 之間的嵌套限制,組合成 CSS 想要的效果。
? 使用限制
對于開頭提到的問題,前面的技術(shù)可行性分析回應(yīng)了「繁瑣」,下面討論一下「完備性」。
CSS 是靈活的,Widget 是受限的,把一個靈活的語法轉(zhuǎn)換成受限的實現(xiàn),注定是不完備的。
在 CSS 里,任意屬性可以和任意屬性寫在一起,W3C 標準里總有一個明確的解釋方式,HTML 和 CSS 是沒有錯誤的,只有不符合預(yù)期。但是在 Flutter 里,某個 Widget 里可以放哪些 Widget 是明確的限制的,例如 Positioned 外層必須有個 Stack、Center 只能有一個子 Widget,不符合預(yù)期的嵌套是會報錯的,寫出的代碼不會出現(xiàn)匪夷所思的混用,所以布局算法可以很快。從這個角度講,CSS 是 O(n!) 的復(fù)雜度,而 Widget 是多項式復(fù)雜度,用 Widget 去實現(xiàn) CSS 注定是不完備的。(這難道是個 P 和 NP 問題……?)
在限制 CSS 寫法的情況下,能不能對接到 Widget 的實現(xiàn)?這個是可以的。
想要 Widget 次線性布局的性能,就必須犧牲 CSS 的一部分靈活性。想要用技術(shù)突破這個限制?那就要改這套次線性的布局算法了,改完之后就不再是 Flutter 了,性能優(yōu)勢也沒有了。
以我的實踐經(jīng)驗來看,Widget 只能支持一定范圍內(nèi)的 CSS 樣式。它對 CSS 的使用限制不在于樣式條數(shù),并不是說某個樣式實現(xiàn)不了,而是在于樣式的混用,即使支持了 500 條 CSS,但是某些屬性依然不能同時使用,外層用了樣式 A 內(nèi)層再用樣式 B 就是無效的,C 和 D 寫一起就只有 C 有效。我評估 Widget 對 CSS 支持的范圍上限會比現(xiàn)在的 ReactNative/Weex 還要大一些,能夠滿足大部分業(yè)務(wù)和小程序的需求,然而業(yè)務(wù)需求是會增長的,達到支持范圍上限以后就很難再擴大了,就需要教育開發(fā)者了,對開發(fā)體驗有影響。
參考
1、
2、https://una.im/CSSgram/
3、https://projects.lukehaas.me/css-clip-path-pokemon/
4、https://css-art.com/pure-css-lace/
5、
6、
7、
8、
9、
We are hiring
好了,PK 完畢,下面是大家最喜聞樂見的招聘環(huán)節(jié)。
歡迎大家加入淘系技術(shù)部基礎(chǔ)平臺部的小程序與跨平臺技術(shù)團隊!是支撐淘系小程序、小游戲、Flutter 等跨平臺技術(shù)的核心團隊,有技術(shù)廣度和也有技術(shù)深度,我們需要 iOS、Android、C++、Flutter、Canvas、WebAssembly、WebGL 等各方面的人才。如果你善于學(xué)習(xí),這是一個很好的接觸跨領(lǐng)域知識的機會!歡迎對技術(shù)有追求的同學(xué)加入!
簡歷請發(fā)送至郵箱:hanks.zh@alibaba-inc.com
總結(jié)
以上是生活随笔為你收集整理的拆!对比详解 Flutter Widget 和 CSS,你关心的布局原理都在这儿了的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里数据:2020七大数据技术领域趋势展
- 下一篇: 自研云原生数据仓库AnalyticDB再