查看原文
其他

Lath(纯前端容器)打造页面间的无缝平滑连接体验

林阳(連山) 大淘宝前端技术 2022-10-02

前端性能现状

提到前端你最先想到什么?前端工程?web?JavaScript、CSS、HTML?

如果站在用户的视角那是他们距离信息最近的地方,无论前端在生产技术上如何演变,最终服务于信息表达的根本不会变。在互联网诞生的初期就已经存在前端技术了,起初它就和传真没有什么区别,只能表达一些简单的基础信息,但随着浏览器不断的技术演变,如今的前端领域已是拥有超级复杂的信息表达系统了。

如今的前端除了可轻松构建起丰富的信息表达形式,更具有可共享、易更新、多平台运行等等的优点,随着生态体量的不断壮大,前端技术也逐渐慢慢渗透到了其它终端领域。比如在移动应用中使用 Hybrid 混合技术,通过结合前端技术与原生技术来同时兼顾与获得两者的优势,以及在另外的领域通过削减复杂度和实现支持部分 API 来使前端达到更高性能的方案,比如 RN、miniApp 等。

数据统计约 70% 的移动 App 中都内置了 webview/混合的技术方案,主要进行能力供给与 Web 页面的管理。恰如其名、web 页面以单位“页面”进行度量,可见其在构建大型应用时的局限性,就如纸张一般,可以肆意的书画其内容,但是脱离纸张之外就无能无力了,因而才需要一个“导航器”将纸张进行串联成册以进行管理,浏览器的基础功能也恰是如此了。

与原生技术的性能差异是什么?

使用混合或转换技术来替代纯前端技术产品是有违于互联网开放与共享本质的,而究其原因,为什么而不得不采用混合技术?原因主要有两个:1 是可提供系统级的能力,比如修改系统信息等,这在 Web 上是完全做不到的(但这也并非 Web 领域内所专注的事情)。2 是对页面间连接的管理,包括转场效果、加载性能、内存回收等等。问题 1 是 Native Apps 领域里本身就需要解决的问题,而“问题 2”正是当前 Web 能力不足的关键之处。

当前我们大部分真正需要以“信息形式”共享到外部的页面基本上都是符合“问题 2”的状况,对于这一部分页面存在两个态:一个是在混合技术内呈现的态,一个是在混合技术外呈现的态。由于技术的差异性,必然造成后者的整体使用体验比前者要差距甚大。

现在为了弥补这种体验上的差距,我们需要一个纯前端的技术来抹平因这种技术差异所造成的体验缺陷。

我们可以进行具体一点的思考,这些所谓的体验差距到底是什么呢?

我觉得可以从心理感受上总结为“快”和“稳”,快首先体现在加载速度,首次见面可不能慢了,另外则是在动画效果上的帧率要足够顺滑,不可出现卡、闪的现象,再一个就是在手指交互的一些行为上能做到及时的响应,不能出现延迟响应和不响应的状况。

如果说快是物理现象,那么在稳的层面则更倾向于“心理感受”,这也是人们自动区分事物属性所具有的本能,比如 web 页面更容易出现结构异变,比如在加载的初期呈现不完整的结构信息,以及更容易出现加载时白屏,甚至是出现错误导致不得不强制刷新,这都令 Web 感觉更加的“脆弱”,(比如很多人会遇到表单填写失败刷新后被清空也是脆弱感的体现)并且它实际上还很“单薄”,因为 Web 应用往往功能和结构都相对简单,在 Web 中页面关系是串联的,要么向前要么向后是一个二维的线形的结构,而原生 App 则可以是网状的,能够产生跳跃的, 因此这些印象都深刻的为 Web 带来了偏见。

除此以外 Web 还有一些天生的缺陷或是说从来就不是很普遍被解决的问题,例如离线访问、与其他应用的共享和交互、推送通知、键盘与文本编辑、后台更新、文件系统等等,但乐观的是这些在一些最新的浏览器中已被得到了支持。

性能问题该从哪个角度来解呢?

我们再来回顾来看一下 SPA + PWA 的组合为什么没能普遍流行就很好得到解释了。

虽然 SPA 增加了应用的“厚度感”,PWA 也提供了上面提到的诸如离线缓存,消息推送等能力,但这都没能让 Web 看起来能像一个原生应用,它仅仅是一个更好的 Web 而已,在体验问题上并没有得到巨大的进步。

当然交互的体验问题,也是多年来前端容易忽视的问题,因为人们常常认为前端的“所谓性能”主要指的就是“首屏加载”的时间,而非真实的使用感受,因为真实世界里我们常常归咎于用户手机性能的好坏,在这一点上所有人对 Web 的包容性明显比原生应用的包容性高的多,只要页面不出错就是可被接受的,对于 Web 性能本就如此早就产生了一些不必说的共识。

以“首屏加载”作为“性能”的主要指标的另一个重要原因是该指标能够被进行可直观的可量化的衡量,不仅可作为技术纬度的衡量,更重要的是在业务数据方面也是一个重要的衡量指标。

相比一些硬指标,体验性能则就较为抽象了,就拿 Android 和 IOS 相比,我们都知道两者在交互体验上存在着明显的差异,但在功能性方面对比两者则可能是仲伯之间,很难在一个单一维度中对比某一体验设计对整体的影响, 因为体验设计本身是一个整体性的东西,因此体验感的缺失也是整体性的,站在一个单点来看细节体验在一些功能性面前简直不值一提,但站在一个系统来看,一个整体的体验感却是胜过不痛不痒的功能点的。

因而当功能性与同类产品相佐时就更需要提升整体的体验感来获得忠实的用户。

在一件事情达到瓶颈时人们往往就会寻求新的发展方向,比如发展替代产物或提升工程效率或甚至于覆盖到其他领域,都是从实际效能比出发去驱动工作内容的,就如芯片领域是一样,这都是很容易理解的事物发展规律。

让我们回归到互联网的本质,用第一性原理来拨析 Web 发展的方向,若我们从一个信息共享的未来俯瞰观测,那么打开与开放一定会是趋势而一定不会是走向一个封闭领域,所以一切的“变种”一定都是短暂的洪流。重回正题,我们要面向未来地解决前端体验问题,就必须用前端来解决前端问题。注意我说的是体验问题,而没有归纳为性能问题,因为 Web 的普遍性体验并非都是因为受限于性能,而绝大部分是开发思维的问题。

纯前端容器 Lath 是什么?

既然我们明确了必须由前端解决前端体验问题,那么我们到底要解决哪一些具体的问题呢?嗯,很棒的是我们已经这样做了,并且诞生了很棒的产品,就是我们今天要讲的主角“Lath”,抱歉主题来的有点晚,因为我必须交代以上背景。

Lath 是一个纯前端的容器,它能够把任何一个普通的 Web 页面变为可丝滑连接与其关联页面的集合,也就是可以将任何页面变为一个 SPA 单页 Web 应用。Lath 所带来了一些交互效果,但要注意的是它并非是一个库或框架,准确来说 Lath 并不参与到你页面内部的逻辑,比如你组件自己的动画效果和逻辑,它主要关注页面之外的逻辑与效果,总结一下它的核心功能就是建立页面间的丝滑连接,也就是 App 中的视图窗口的管理能力。

Lath 通过哪些细节提升用户体验?

Lath 的所有核心能力都是围绕如何建立丝滑的页面连接所展开的,接下来我们就来看一下它其中的部分亮点。

注意这并非是一篇技术细节和原理的解读文章,其中大部分是对概念与功能的介绍,我认为技术细节可以有很多种方式去实现,而去做这样一个有概念的事情比讲解技术实现更有意义。

关于交互前置

第一个亮点就是从点击的开始就增加交互效果,我们普遍的 web 页面交互的起点都是从“click”事件开始的,这是从 PC 时代遗传下来的习惯,而实际上在任何 Button、Item、Card 等效果上都应具有 touch 的三种状态效果,尤其是需要被响应的交互。我们可以点击一下屏幕上的 App Icon,或是微信的联系人列表,再或是任何一个原生 App 中的 Item 导航条,你都会发现,从你手指和屏幕接触的一刻即产生了交互效果,只要你不松手效果将会一直存在,直到产生新的事件分支,比如长按、拖拽、点击等。而从开始触摸到屏幕到手指离开屏幕,确定为点击事件的整个过程大概需要花费 300ms 左右,也意味着在 1/3 秒内整个屏幕上的交互部分都是静止的,这就会令人产生一个错觉,就是交互被延迟了,尤其当你后续操作需要更多耗时时,直观的感受就是卡顿的。

这是一个很小的体验 Tips,但也是很普遍很通用性的能力,也是 Lath 提供的一个基础小能力,会对页面中符合条件的各类元素提供点击事件前的增强效果。(对组件质量不对齐又比较懒的开发者非常贴心)

及时可预期的交互

除了交互前置,使用及时且可预期的交互也是加强体验的重要手段,什么是可预期交互?我们一般认为是符合常识、物理规律、现实经验的可被理解的交互反馈,比如 Loading、以及产生弹性/惯性的手势、页面间转场动画等都属于可预期交互。

在 Lath 中已经预置好了强大的窗口管理系统,从最初的点击到页面的加载以及转场动画等都覆盖了整套的体验方案。

接下来我们来简单说一下预加载,例如我当前停留在 A 页面,A 页面将可能会访问 B、C 页面,那么此时用户在决定进入某个页面前 Lath 已经在任务空闲期偷偷帮你加载好了必要资源文件,当你点击进入时就无需进行前期的加载,可直接执行页面代码逻辑。对比传统的 Web 跳转方式能平均节省 50ms ~ 1s 的前期载入时间,当然这都是在网络良好的基础上。

当然在实际过程中还有着各种各样的周期性问题,Lath 都通过配置文件提供了多样的管理方案。

平滑的下拉刷新

说完预加载,另一个和加载有点关系的就是刷新加载,但是这两者在体验上有着很大的区别,首次加载需要的是速度,而刷新加载则更强调“稳定”,主要体现在不能闪,不能白。传统的 Web 应用,在下拉刷新时往往都会经历上图的三个主要阶段,而在加载的中间过程则会出现非常具有 Web 特色的白屏和渲染过程,信息在闪烁过程中忽有忽无,造成了不安全与脆弱感。

Lath 为每个页面提供了配置化的平滑下拉刷新,注意它并不是一个下拉刷新组件,因而开发者不需要引入一些代码,也不需要为其书写调用逻辑,作为容器能力它仅需一个简单的配置即可自动完成这一切。我们可以看到在下拉刷新的过程中没有加载过程,也没有替换和修改的过程,而是直接呈现一个完整的新内容,这是非常贴近原生 App 的体验的。

内容如何进行平滑的更新是下拉刷新最核心的能力,基于这个核心能力我们还可以做到平滑的主题切换和语言切换。

系统级的窗口管理

提到窗口管理,前端往往没有关心这个领域,因为我们所做的工作都是基于窗口内来完成的,对于窗口本身的管理往往没人关心,那应该都是交由系统和浏览器去决定的吧。

如果有一个课题,实现一个系统级的窗口管理,如果仅仅是用前端来实现,那么应该如何实现呢?

首先系统级的窗口管理就是非常复杂的一件事,包含全屏窗口、嵌合窗口、Split 窗口、Modality 窗口、Sheets 窗口、 Tab 窗口、Slide 窗口等等,每一种窗口又回形成串联、并联的历史关系,同时还要根据历史关系进行前进与后退的各种类型间的窗口动画效果切换,以及窗口间的手势操作和动画效果、窗口的拖拽、窗口镜像,除此之外窗口还存在纵向的以及相互嵌套的窗口等问题。

对于这窗口问题我个人还是对此挺执着的,每当看到 IOS 上的炫酷交互,都让我有种冲动想去用前端技术去实现一个,曾经在 15 年的时候我就开始了这个计划,从一个系统桌面开始,桌面中又包含了各种应用,应用中又包含了各种窗口效果。接下来我们列举一些比较常用的窗口效果

Multilayer 叠加窗口(范型)

叠加窗口是一种可透视窗口,窗口是非全屏属性的,比如一个主窗口只负责 Tab 卡的切换,而在主窗口正下方还叠加了内容窗口,此时根据两者的透视关系会叠加为一个“全屏窗口”,而实际上这是由多个窗口合并而来的窗口。我们最常见的使用就是创造一个 Tab View,而实际上 Lath 并没有像传统框架那样提供一个 Tab 组件,而是通过叠加窗口 + 无缝连接 的特性由开发者自行创造的,每一个 Tab 按钮都可以是一个 A 连接,当被点击时之要将其跳转页面的动画设置为无动画即可做到 Tab 切换的效果,当然主内容视图设置成 Slide 窗口效果时也可做到可划动的 Tab 切换效果。

全屏窗口(范型)

全屏窗口是最寻常的窗口类型,有着较高的覆盖层级,比如当全屏窗口和 Multilayer 叠加窗口产生交互时,全屏窗口将始终在 Multilayer 叠加窗口的前景出现。全屏窗口和叠加窗口都是一种范类型,而其他功能型窗口都属于这两种范型窗口。

可交互窗口

多数的窗口类型都具有可自定义的窗口动画效果,而我们一般所说的 Web 转场动画是不具备可交互性的,可交互性动画不是一个固定的动画方式,而是包含了更多的输入信息,比如手指触发的方向/位置,从哪个来源触发的,比如由边缘回退、浏览器按钮、手势操作等触发源所触发的动画。可交互动画相比固定动画更具灵动性,也更符合事物发展的预期。另外可交互动画一般还会具有可中断、可干预的特性,比如通过手势划动来另程序返回到上一个窗口,过程是连续且可中断的。下面介绍的几种窗口类型则就是符合可交互窗口的特性。

Slide 切卡窗口

切卡类型也是一种常见的多页面交互方式,比如早期的 IOS 任务管理列表,横向滑动即可快速的在多页面间进行切换,切卡类型窗口准确来说是一个“窗中窗”的窗口类型,本身是依托于某个全屏窗口或 Multilayer 叠加窗口内的。作为可交互型窗口最重要的则是其滑动的性能部分,大家都知道 JS 到 Dom 的渲染线程是单一线程,除了预先执行的 CSS 动画和一些原生默认行为不受阻塞外,其余之外的任何节点操作都受制于 JS 与 Dom 渲染引擎的双向性能限制,如果想做到绝对媲美原生性能的交互体验就不得不用到两个重要的原则,核心要点已经透露了,看破不说破,细心的小伙伴能从中发现,而另一个要点也将会在后面进行提及。

切卡型窗口还可以创造一种常见的 App 交互形式,就是近几年在国内的很流行的“二楼”效果。

Sheets 弹出层窗口

弹出层是一种更加扁平的窗口效果,其从形态上讲看起来像是一种叠加窗口,单实质上它却是属于全屏窗口的,只不过它具有部分半透明特性的全屏窗口。弹层窗口会让信息变得更饱满,很适合需要延伸阅读的场景,在信息阅读完后能够快速回归到主信息流中。

Sheets 弹出层窗口也是一个重要的可交互窗口,其体验的核心依旧体现在交互的性能上,是否跟手是否吊帧等。

另外还有一些细节体验,比如支持横向和纵向的两个方向进行关闭窗口等。除了一段位的弹出层,Lath 还支持两段位的弹出层能更扁平的展示延展信息,使整体交互体验更上一层楼。

窗口手势回退

在 IOS 中我们最常用的功能是什么?无意就是返回操作了,由于 IOS 没有实体返回按钮,因此使用从边缘划动的方式成为了主流的回退方式。

在 Lath 中这种方式变得更激进了,可以无需从边缘划动,而是页面的任何位置滑动都可使页面进入回退状态,当然边缘划动依旧是强制返回的手势,这是针对可滚动区域相互嵌套带来的经验交互。当然整体交互的流畅感和跟手性依然是该效果最核心的卖点。

窗口导航管理

窗口导航是基于窗口管理所拓展出的一种快速链接能力,类似浏览器历史列表的能力,但在 Lath 中默认提供了一种可交互的窗口导航模式,交互模式类似于 Safari 的标签导航器,它最大的好处是能够扁平化的呈现用户的浏览路径,以快捷的方式访问历史页面信息。

窗口导航器中使用的能力都是由 Lath 本身所提供的对外能来进行构建的,当然你也可以构建一个适合自己的导航器。

内存管理

Lath 中的每一个 Applet(我们所指的页面)都有一个完整的上下文,当窗口不处于活动状态时或随着浏览深度的增加导致窗口数量的增多时,内存管理就非常重要的,在处理内存管理上有很多的细节,最核心的思想还是以栈的方式来存放窗口,当窗口数量超过设定值时则进行主动的销毁回收。根据 Applet 的类型和配置,有些可能被彻底销毁,有些则可能始终不被销毁,有些是销毁实例但保留了其镜像文件等等。

任务管理

虽然 Lath 对窗口数量是有控制机制的,但是当这些窗口同时存在一些多任务时,则需要有力的管控机制。比如 A 页面存在一个播放中的视频,当从 A 页面转场进入 B 页面时,若 A 页面未被销毁回收,那么 A 页面的视频则应该立即被暂停。当然这应该是开发者自己就应需要考虑的事情,比如通过 Segue 的转场事件自行管理播放状态。但对于容器层我们还额外的提供了一些自动化的管理手段,以帮助一些懒惰的开发者来提升开发体验与用户体验。在 Lath 中这是一个自动媒体管控的配置项,能够自动的根据一些情况进行符合预期的管控,比如还能够对频繁有 DOM 操作的不可见页面进行回收等。

除了媒体管控,在 Web 中还有两个常见的背景 Trigger 触发事件,就是 setTimeout 和 setInterval,对于在不同页面中的调用我们也将其做了自动管理,当页面 Segue 到不可见时管理器则会暂停相关 Trigger 的触发行为,直至下一次 Segue 到可见时再继续执行任务。

Lath 的技术特点

前面介绍了一些功能上的特色,而在技术上 Lath 也有着独特风格。Lath 被定义为一个纯前端的 Container,宗旨是建立页面间的无缝平滑连接。那么势必他将会在产生诸多的交互元素,而这些元素如何能够不干扰开发者本身呢?

不受限框架

我之前专门讲过 WebComponents 的技术优点,没错,Lath 本身就是以一个 WebComponents 的技术方式进行提供服务的。

因而它并不会局限于任何的框架体系,在常见的理性框架中都能够便捷的去使用它。

渐进式增强

另外 Lath 本身也是有原子化能力组成的,它的每个核心部分都能进行按需载入,在首屏的最初期它和一个 div 标签没有太大区别,随着渐进式的能力载入才逐渐起到更多的作用。容器中的很多功能都并非是立刻被需要的,而是根据上下文信息及实践顺序一次被按需载入的,因而在可拓展性方面它显得更加的灵活小巧。

开发友好

我们在 Web 中能看到 Lath 的部分就是它标志性的两个标签了,就像这样,只要将页面内容外包裹上该标签就能获得神奇的能力。这与框架最显著的区别在于你无需将各种交互元件作为你代码逻辑的一部分,而所有期望效果都是在原有页面上进行的交互体验增强而已,这是一个非破坏性的开发体验。

当然在我们打开控制台排查的时候也依然不会看到多余的交互元素和结构,简直就是和你的代码结构保持完全一致,明朗整洁。

最后

展望:Lath 旨解决页面到页面间的体验问题,而对于页面内的基础组件的性能问题,未来我们也给提上了计划日程,在组件问题上我们并不关心常规的逻辑,更不是想重复造轮子,而是将会带着一种新的关于交互性能的思想来解决组件中的基础问题,尤其是交互中采样率差和掉帧严重的交互组件,从而能更好的完整的打造高性能的 Web App。

最后希望大家能够喜欢它,有任何问题都可以加我进行交流。

---------------------------- 附件------------------------------- 附上产品技术文档以及添加到主屏幕后的离线状态下 Demo 演示视频 技术文档:https://lath.dev[1](注:文档服务来源 ghp,国内首次访问可能较慢,另源码仓库目前尚未开放,最新消息可从官网获取) Demo 视频:

参考资料

[1]

https://lath.dev: https://lath.dev/


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存