拆!对比详解 Flutter Widget 和 CSS,你关心的布局原理都在这儿了

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 这篇文章专门对比 Flutter Widget 的布局原理和 CSS 布局原理的差异,分享在对接过程中会遇到的问题和解决方案,帮大家理一理思路,内容可以分为这几部分:1、CSS 和 Widget 参赛选手介绍 2、Let's Battle! 从五个角度正面硬刚 3、Love & Peace 讨论取长补短的可行性 4、Happy Ending 最重要的环节

默认标题_公众号封面首图_2020-04-27-0.png

作者|张翰(门柳)
出品|阿里巴巴新零售淘系技术部

我在之前一篇文章《打破重重阻碍,Flutter 和 Web 生态如何对接?》提到了这么一句话:“CSS 和 Widget 的对接也是很繁琐的过程,而且存在完备性问题”。我直接给了结论,没有给出原因。

现在填上这个坑,这篇文章专门对比 Flutter Widget 的布局原理和 CSS 布局原理的差异,分享在对接过程中会遇到的问题和解决方案,帮大家理一理思路,内容可以分为这几部分:

  • CSS 和 Widget 参赛选手介绍
  • Let's Battle! 从五个角度正面硬刚
  • Love & Peace 讨论取长补短的可行性
  • Happy Ending 最重要的环节

CSS

CSS 是 Cascading Style Sheets(层叠样式表)的简写,是一种用来描述样式的标记语言,最初的想法诞生自 1994 年,1996 年落成第一版规范(参考 20 Years of CSS)。HTML 描述了页面的结构,CSS 描述页面呈现出来的样子,这对 CP 已经配合工作了二三十年,依然是布局圈里最高效的组合。

image.png

▐ CSS is Awesome

image.png

CSS 描述布局很高效,这点无需质疑。后续出现的各种布局方案,前端框架 CSS in JS、XML描述文件、Flutter 等等,即使没有直接照搬 CSS 的功能,也深受 CSS 设计的影响。

CSS 很容易上手,经典的盒模型一学就会,文本相关的属性看名字就知道怎么回事,Flexbox 也是很好用的,但是 CSS 逐渐出现了一些很难驾驭的功能,多列布局就稍微难了一点,CSS Grid … 这代码也太难写了吧,我觉得这是给 AI 设计的,不是让人手写的,再加上 clip-path, filter, css houdini 等等,功能越来越强大,可以做滤镜画皮卡丘画油画、甚至还可以做游戏。功能很强大,但是不实用,上述这些功能在生产环境基本上都是用 JS 实现的,没时间去磨 CSS。

▐ 渲染布局流程

浏览器里 CSS 的渲染过程,简化一下可以总结为 加载、解析、查询并作用到 DOM 节点、计算布局 这四个过程。浏览器首先加载 HTML 文件,遇到

image.png

然后浏览器根据带有 ComputedStyle 的 DOM 树生成 LayoutTree,节点的 display 特性不同,生成的 LayoutObject 的类型也不同,然后布局算法会多次遍历这课树,计算出每个节点的 Rect。这个过程是很复杂的,并不是一个 DOM 节点就对应一个 Layout 节点,要考虑 display:none、伪元素、文本节点、shadow dom 等等,而且整个过程是同步的,在解析出的 DOM 节点已经布局完成后,如果浏览器又解析到一个

布局完成之后是执行 Paint,把布局信息一层一层的提交到 compositor 线程,然后再划分成一块一块的交给 GPU 线程去绘制。我觉得最后这两步是比较快的,在主线程里的 Layout 和 Paint 才是最耗时间的,而且和 JS 代码的执行搅在了一起。

Flutter Widget

和 CSS 不同,Flutter Widget 设计得很细致,分类清晰功能明确,不像 CSS 那样各种属性耦合在一起,互相影响。Widget 的设计是比较原子化的,基本不会互相影响,为了保持布局算法的高效,对 Widget 的嵌套方式有要求。

image.png

Flutter 设计得比较合理,有一个原因不容忽视,Flutter 在 Google 的开发团队和 Chrome 有很大渊源,有些人已经参与制定 CSS 规范很多年,都是在这个领域里很有经验的人。整体上没有 CSS 那么多的冗余和历史包袱,新框架都可以吸取以前的教训,站在巨人的肩膀上设计得越来越好用。换句话说,如果让 CSS 的设计者们,抛开历史包袱重新设计,不考虑向下兼容,很有可能就设计成了现在 Flutter Widget 的样子。

▐ 渲染布局流程

关于 Flutter 的渲染流程,官方文档Flutter 工作原理就是很好的学习资料,最大的亮点是次线性的布局,有接近 O(n) 的性能,比 CSS 高效得多。

image.png

具体过程大家去官网学习吧,这里就放一张图,本文的重点是 Battle!

Let's Battle!

▐ Round 1: 背后的大佬

在真正开始比较之前,先看一下他们背后是什么样的组织在支撑和运营着他们。

CSS 背后是 W3C,这是业界认可的标准化组织,而且被各大浏览器实现,浏览器厂商也在积极的推到标准的发展。CSS 是个开放的技术,它背后的大佬是 W3C、Chrome、Safai、Firefox 等等一系列盈利或者非盈利组织,大家互惠互利共同发展。

但是 Flutter 背后是是只有 Google,虽然也是开源的,但是设计与实现都是由 Google 的团队来主导,其他人都是在使用,真正有能力有机会参与开发的很少,PR 都是小修小补。框架和标准的一个差别,就是会不会向下兼容,框架有可能明天宣布推出 2.0,带上牛逼的优化和 breaking change,不向下兼容,是司空见惯的事。

从这个角度看,CSS 虽然臃肿,但毕竟是个标准化的技术,生命力顽强。你现在写的 CSS 代码,五年之后依然可以运行,现在写的 Flutter 代码到五年后就不好说了。假如 Google 宣布不维护 Flutter 了,很可能社区里就瞬间丧失了信心,那 Flutter 就死了;假如 Chrome 宣布不支持 CSS 了,CSS 依然活得好好的,Chrome 很可能会死(参考 IE),FireFox 要笑醒了。

▐ Round 2: 学习成本

要说学习成本,当然是 CSS 高效,先不讨论原理,先从几个侧面的案例说明一下。

市面上能出现“零基础,三个月成为前端高手!”的培训班,也侧面说明了前端学习成本低,其实他们的口号是错的,三个月肯定学不会前端,能学会的只有用 CSS 切页面。但是没有培训班开“三个月掌握Flutter”的课,一个熟练的前端开发者三个月学会是可能的,客户端上手更快一点,没有编程基础的人学完肯定一脸懵逼的要求退钱了。

另外微信可以举办“青少年微信小程序编程创意营” ,面向中小学生,小程序的 UI 就是受限的 HTML + CSS 来写的,但是 Flutter 要搞这种比赛的话,就得面向有熟练编程经验的人了。

image.png

如果从语法角度考虑的话,CSS 容易学是因为它只是一种描述性语言,不含复杂的编程逻辑,设计目标就是用来描述“我想要的 UI 是什么样子”的,是面向结果的描述,而 Widget 是要通过写 Dart 代码来实现的,UI 和代码逻辑写一起,通过代码一行行描述“我怎么把 UI 组合出来”的,是面向过程的描述,所以 CSS 更直观一些,写出来的代码更容易让人理解。还有个小原因,就是像 Fluter Widget 这样层层嵌套的代码,写起来和改起来都很麻烦,太依赖编辑器,复制粘贴不太方便(差不多的话,是可以抄一下代码的嘛)。

另外 CSS 诞生了这么多年,学习资料简直多到爆!本身规范事无巨细,还有 MDN 、CSS Tricks 、CodePen 等网站即授之以渔又授之于鱼,各种线上培训也把知识框架和学习路线都给你安排的明明白白的。相比之下,Flutter Widget 就只有官网,学习资料和社区生态都还差很多。

▐ Round 3: 开发效率

CSS 上手虽然简单,但是很难掌握,没有个几年的开发经验,没被它虐过千百遍,是根本驾驭不住它的。

image.png

假如要画出来 CSS 的学习曲线的话,初期肯定是快速上升,到了一定高度后就变慢了,甚至还迷之下降。但是 Flutter 刚开始学习时要了解很多概念,要转变思维,上手稍微慢了一点,但是越学越快。说个不恰当的比喻,写 CSS 可以看做是操作提线木偶,写 Widget 就相当于是搭乐高积木。

image.png

因为 CSS 各种属性之间是可以互相影响的,输入和输出不是简单的对应关系,你以为你写了 width: 100px 它的宽度就一定是 100px 了?就像是绑了几百根线的木偶,让你拉动其中的五根来做一个 OK 的手势,你牵了其中一条线,动的可能不只是一个部位,而是整个上半身。

Flutter Widget 就更加原子化,而且对于谁可以嵌套谁是有要求的,就像是拼乐高积木,每块积木都很小,但是有明确型号的卡口,要先符合它的设计,然后再发挥创意拼装成各种造型。迫使你按照理想的方式去组合 Widget,避免写出性能太差的代码,CSS 就是任意属性都可以和任意属性在写在一起,而且标准里总能给出你一个合理的解释,所以写出来的代码很混乱,也增大了布局的难度。

所以 CSS 的开发效率初期较快,但是积累沉淀较难,大规模协作很难管理(耦合度高,全局作用域等),而 Flutter 的封装性更好,有利于协作,开发效率会越来越快的。

▐ Round 4: 天下武功,唯快不破!

要问 Flutter 的 Widget 和 CSS 谁更快?大家的共识也是 Flutter 更快吧。我觉得 Flutter 和 CSS 布局相比有两大性能优势:一个是次线性的布局算法,一个是更合理的线程模型。

Widget 的布局原理比 CSS 高效,这是牺牲了一部分灵活性换来的,以后扩展 Widget 时,都要遵循这些设计才能继续保持高效。CSS 简单的属性背后可以深挖出特别复杂的细节,使得布局模型越来越复杂,渲染管线越来越长,光 display 就有十几二十个值,每一条都对布局影响巨大,方便了开发者,但是给布局性能带来很大挑战。

关于线程模型,也是浏览器一直被诟病的性能瓶颈,主线程太忙了,JS 的执行、HTML/CSS 的解析、DOM 的构建,布局的运算,全都在主线程。相比之下 Flutter 划分的四个线程就比较均衡,GPU 线程做的工作和浏览器差不多,但是宿主平台(Android/iOS)的代码跑在 Platform 线程里,Flutter Framework 主要运行在 UI 线程里,另外还有 IO 线程实现网络和图片、字体等文件的加载。

▐ Round 5: 未来的发展

分享几个关于 CSS 现状的数据,W3C 官方定义的 CSS 样式有 520 条,Chrome 平台上统计的样式有 703 条,包括了一些带前缀的样式,有大量样式的使用率很低。支付宝小程序的同学总结过 Top 100 小程序里用到的不同 CSS 样式,有 184 条。

总结一下就是:W3C 标准里定义了 520 条样式,Chrome 还额外支持 180+ 条私货,但是常用的样式不超过 200 条。

CSS 有大量历史包袱,我自己也感觉大部分样式我都用不到,有些甚至是最佳实践里禁止使用的,学会 50 条 CSS 后就可以写 80% 种情况的布局了,对于难写的样式,我多加几层标签再写点 JS 也能实现。但是这些样式不能废弃,必须继续支持,新增一条好用的属性时,要解释清楚和现在所有属性的适配情况。

CSS 是负重前行,注定要越变越复杂。Widget 则是轻装上阵,而且解耦比较好,是可插拔可组合的,如果未来想废弃一些 Widget,把这部分 Widget 从主包里拆出来放到独立插件里就行了,想用的话自行引入。整个迭代过程受历史包袱的影响很小。

Love & Peace: 可否实现对接?

Battle 已经结束,友谊第一比赛第二,不讨论胜负,下面进入 Love & Peace 环节。

Flutter 的压线性布局让人眼馋, CSS 的灵活度又深受大家喜爱,可不可以取其精华去其糟粕,让开发者写 CSS 但是底层用 Flutter 来渲染?有这种想法的不是一个人,所以有很多方案把前端框架或小程序对接到 Flutter 上,在实现的时候,就会遇到如何把 CSS 转换为 Widget 的问题。

▐ 技术可行性

技术上当然是可行的,我在前一篇文章《打破重重阻碍,Flutter 和 Web 生态如何对接?》里介绍了各种实现方案,自己也写代码做过对接(用的C++魔改方案),跑通了整个渲染链路。

我介绍一下我的实现方式,可以简单分成三步:

1. 解析 CSS 语法

我写了个精简版的 CSSOM,是标准 CSSOM 的子集。用来实现样式表和样式属性和增删改查、样式值的解析与计算、选择器的匹配和查询等功能。(功能是独立的,有需要的话自取)

CSSOM 主要是为了处理 CSS 的上层语法,转成一致的数据格式,为下一步的转换做准备,有一部分 CSS 的语法是在这个过程实现的,例如 CSS 选择器,包括伪类选择器和选择器关系等,还有 @media 和 @keyframes 等功能,也可以实现 CSS variable 以及 calc()。无论上层是直接写 CSS 还是 CSS in JS,都保证下一步转换时输入的数据格式一致,可以简化后续的实现。

2. 实现 CSS 属性和 Widget 数据格式的映射

这部分要把 CSS 的基础数据格式转成构建 Flutter Widget 所依赖的数据格式。例如 CSS 的 color 属性会转成 Flutter 的 Color 类,margin 和 padding 会转成 EdgeInsets 类,flex-direction 将被转成 Axis 枚举,把文本相关的属性转换成 TextStyle 类。

这部分也是做原子性的转换,技术上看起来比较简单,只是转换数据格式而已,但是需要对 CSS 和 Widget 的设计都比较了解,知道同一个概念在双方语义中的对应关系,得搞清楚里边的技术细节。这个对应关系如果转换错了,后续的布局怎么调都调不对(过来人的经验…)

3. 构建 Widget 树

拿到 CSSOM 传来的数据,掌握了 CSS 和 Widget 数据结构的语义转换,然后在结合 HTML 定义的结构,就可以生成真正的 Widget 树了。

构建 Widget 树就不能只考虑 CSS 了, HTML + CSS 才和 Widget 对等。这里还要处理不同类型 HTML 节点和 Widegt 的对应关系,节点上带的布局样式不同,生成的节点也不同,Widget 的层次深度比 HTML 的要深。例如普通的 div 标签,如果仅包含普通盒模型样式,就转换成 Container;如果包含了 flex 相关的属性,根据具体配置的不同,转成 Flex/Center/Row/Column 等;如果包含了绝对定位就要转成 Positioned/Stack。这个过程也是很繁琐,包含了大量细节,需要理解默认 HTML 标签的语义、CSS 的层模型以及 BFC 等等,还需要理解 Flutter Widget 之间的嵌套限制,组合成 CSS 想要的效果。

▐ 使用限制

对于开头提到的问题,前面的技术可行性分析回应了「繁琐」,下面讨论一下「完备性」。

CSS 是灵活的,Widget 是受限的,把一个灵活的语法转换成受限的实现,注定是不完备的。

在 CSS 里,任意属性可以和任意属性写在一起,W3C 标准里总有一个明确的解释方式,HTML 和 CSS 是没有错误的,只有不符合预期。但是在 Flutter 里,某个 Widget 里可以放哪些 Widget 是明确的限制的,例如 Positioned 外层必须有个 Stack、Center 只能有一个子 Widget,不符合预期的嵌套是会报错的,写出的代码不会出现匪夷所思的混用,所以布局算法可以很快。从这个角度讲,CSS 是 O(n!) 的复杂度,而 Widget 是多项式复杂度,用 Widget 去实现 CSS 注定是不完备的。(这难道是个 P 和 NP 问题……?)

在限制 CSS 写法的情况下,能不能对接到 Widget 的实现?这个是可以的。

想要 Widget 次线性布局的性能,就必须牺牲 CSS 的一部分灵活性。想要用技术突破这个限制?那就要改这套次线性的布局算法了,改完之后就不再是 Flutter 了,性能优势也没有了。

以我的实践经验来看,Widget 只能支持一定范围内的 CSS 样式。它对 CSS 的使用限制不在于样式条数,并不是说某个样式实现不了,而是在于样式的混用,即使支持了 500 条 CSS,但是某些属性依然不能同时使用,外层用了样式 A 内层再用样式 B 就是无效的,C 和 D 写一起就只有 C 有效。我评估 Widget 对 CSS 支持的范围上限会比现在的 ReactNative/Weex 还要大一些,能够满足大部分业务和小程序的需求,然而业务需求是会增长的,达到支持范围上限以后就很难再扩大了,就需要教育开发者了,对开发体验有影响。

We are hiring

好了,PK 完毕,下面是大家最喜闻乐见的招聘环节。

欢迎大家加入淘系技术部基础平台部的小程序与跨平台技术团队!是支撑淘系小程序、小游戏、Flutter 等跨平台技术的核心团队,有技术广度和也有技术深度,我们需要 iOS、Android、C++、Flutter、Canvas、WebAssembly、WebGL 等各方面的人才。如果你善于学习,这是一个很好的接触跨领域知识的机会!欢迎对技术有追求的同学加入!

简历请发送至邮箱:hanks.zh@alibaba-inc.com

相关文章
|
5天前
|
开发框架 数据安全/隐私保护 开发者
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计,涵盖布局基础、常用组件、样式设计、实战应用、响应式布局及性能优化等方面,助力开发者打造精美用户界面。
23 7
|
28天前
|
前端开发 UED 容器
在 CSS 中使用 Flex 布局实现页面自适应时需要注意什么?
【10月更文挑战第22天】在使用 Flex 布局实现页面自适应时,需要对其基本原理和特性有深入的理解,同时结合具体的布局需求和场景,进行细致的调整和优化。通过合理的设置和注意事项的把握,才能实现理想的自适应效果,提升用户体验。还可以根据实际情况进行更深入的探索和实践,以不断提升 Flex 布局的应用能力。
|
24天前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
72 8
|
24天前
|
开发者 容器
Flutter&鸿蒙next 布局架构原理详解
本文详细介绍了 Flutter 中的主要布局方式,包括 Row、Column、Stack、Container、ListView 和 GridView 等布局组件的架构原理及使用场景。通过了解这些布局 Widget 的基本概念、关键属性和布局原理,开发者可以更高效地构建复杂的用户界面。此外,文章还提供了布局优化技巧,帮助提升应用性能。
82 4
|
22天前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
94 2
|
24天前
|
存储 Dart 前端开发
flutter鸿蒙版本mvvm架构思想原理
在Flutter中实现MVVM架构,旨在将UI与业务逻辑分离,提升代码可维护性和可读性。本文介绍了MVVM的整体架构,包括Model、View和ViewModel的职责,以及各文件的详细实现。通过`main.dart`、`CounterViewModel.dart`、`MyHomePage.dart`和`Model.dart`的具体代码,展示了如何使用Provider进行状态管理,实现数据绑定和响应式设计。MVVM架构的分离关注点、数据绑定和可维护性特点,使得开发更加高效和整洁。
151 3
|
24天前
|
容器
深入理解 Flutter 鸿蒙版的 Stack 布局:适配屏幕与层叠样式布局
Flutter 的 Stack 布局组件允许你将多个子组件层叠在一起,实现复杂的界面效果。本文介绍了 Stack 的基本用法、核心概念(如子组件层叠、Positioned 组件和对齐属性),以及如何使用 MediaQuery 和 LayoutBuilder 实现响应式设计。通过示例展示了照片展示与文字描述、动态调整层叠布局等高级用法,帮助你构建更加精美和实用的 Flutter 应用。
111 2
|
21天前
|
前端开发 容器
实现CSS品字布局
【10月更文挑战第27天】
|
22天前
|
Dart JavaScript 前端开发
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
Flutter 是 Google 开发的开源 UI 框架,用于快速构建高性能的移动、Web 和桌面应用。Flutter 通过 Widget 构建 UI,每个 UI 元素都是 Widget,包括文本、按钮、图片等。Widget 不仅描述外观,还描述行为,是不可变的。常见的 Widget 包括结构型(Container、Column、Row)、呈现型(Text、Image)、交互型(ElevatedButton)和状态管理型(StatefulWidget)。Flutter 与鸿蒙 Next 在组件化架构、开发语言、布局系统、性能和跨平台支持方面各有优势
68 0
下一篇
无影云桌面