您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

Web 端如何低成本打造 Native 体验?

时间:2020-06-08 09:41:18  来源:  作者:
Web 端如何低成本打造 Native 体验?

 

阿里妹导读:Web 应用在实际体验上和 Native 应用仍然存在非常明显的差距,那么如何低成本地把一个现有的网站改造成类 Native 的体验呢?本文分享一种让网站低成本渐进式实现 Native 化体验的方式——同屏渲染。
作者: 当轩 阿里技术

Web 端体验

在有了 PWA(Progressive web Apps) 之后,Web Application 也具备了添加到桌面和离线访问等能力,但是实际体验上却总是和 Native 应用存在非常明显的差距。

我们可以看一下 Alibaba 的 M 站和 IOS 应用的录屏(左边为 WEB,右边为 iOS APP):

Web 端如何低成本打造 Native 体验?

 


Web 端如何低成本打造 Native 体验?

 

我们可以看到,对于 Web Applicaiton 来说,在页面中来回跳转时访问的总是割裂的,从上一个页面到下一个页面需要等 loading ,返回时很多内存状态又都不在了,导致无法正确定位回之前的列表位置(这一点其实和不同的浏览器以及列表本身的实现方式有关,也有一些方案可以规避这个问题,在这里只是其中一个 case)。

这样对于用户的体验伤害非常明显,他能明确感觉到自己在用的并非一个 Application 而是一个 Website,而且在进行复杂的操作时整个链路也非常容易被中断。

而其实这种体验差异的根源,在于 B/S(Browser/Server)和 C/S(Client/Server)的差异。ServiceWorker 虽然提供了一些方案(例如 App Shell)让我们较低成本的增强原有的体验,但仍然难以解决页面之间的割裂问题,很多相同的代码在不同页面间重复执行,每一次访问内存状态就会丢失。

渲染性能

当我们在说体验的时候会显得有点主观,性能相比之下就容易衡量的多,而页面割裂带来的最为直观的体验差距其实就来自于渲染性能的差异。

在 Web 端一个典型的 CSR(Client Side Rendering)要经过的流程大致如下:

Web 端如何低成本打造 Native 体验?

 

这其中有很多不符合我们预期的地方:

  • html/JS 都等到点击后才开始加载(这点可以通过预加载的手段解决,其中 HTML 的预加载可以通过 ServiceWorker 进行)。
  • framework 等 JS 在不同页面间总是重复执行的。
  • 加载 API 的时机非常晚,而加载 API 一般是耗时很长而且可以并行的部分,理论上加载时机越早越好。

所以理想中的渲染流程应该是下图这样:

Web 端如何低成本打造 Native 体验?

 

其实对于 Native 应用也是如此,用户点击时基本就会开始加载 API 并且执行下个页面的逻辑。其实一个优化的比较好(做了 preload 等)的 SPA 也是类似的效果,我们提前加载好下个页面的 vendor ,点击时直接只执行下个页面的逻辑即可。

然而实际上对于一个较大的现存站点来说(例如 m.alibaba.com ),把整个网站作为一个 SPA 来维护是不太现实的,一方面不能适应当前多人协作的现状,另外一方面稳定性上也不能接受修改一个页面整个网站都要发布的方案。

那么,如何低成本的把一个现有的网站改造成类 Native 的体验呢?

同屏渲染

在有了上面的思考后,我们就在想,有没有一个方案在不做改造的前提下,在用户点击后,立即开始数据的并行加载,同时把下个页面动态的加载进来,选择性的保留上个页面的一些内容(例如正在加载中的数据, jsonp , framework 层的对象等)而隔绝其他部分的干扰。

于是针对我们的场景产出了一个同屏渲染的方案:LightHub,所谓同屏渲染,即渲染过程中页面不需要被卸载,所有的渲染行为都在一个上下文中发生。

Web 端如何低成本打造 Native 体验?

 

这里我们需要几个东西:

  • 能直接附着到现有页面上的沙箱,用于把页面还原到初始状态(同时允许保留部分共享的部分)
  • 过渡动画
  • API 并行加载
  • 按照浏览器行为渲染 HTML
  • 按照浏览器行为触发事件
Web 端如何低成本打造 Native 体验?

 

沙箱

我们需要一个低成本把页面还原会初始状态、并且允许保留部分对象的沙箱机制,而且最好这个机制是可以直接低成本部署到现有页面上的。其实这里的诉求和微前端碰到的问题类似,我们受 qiankun 的沙箱机制启发,只需要在页面的 <head> 中插入一小段内联 JS 记录:

  • window 上的全局变量
  • window/document 的 eventListener
  • 定时器:setInterval/setTimeout/requestAnimationFrame/requestIdleCallback
  • MutationObserver

在我们需要时我们只需要清空页面的 DOM,还原变化的全局变量(这里和 qiankun 一样采用的浅拷贝),eventListener,定时器和 MutationObserver,就能把页面还原到初始状态。

同时,记录的状态也能封存到一个对象中,当用户从下个页面 back 到上个页面时,我们可以直接把状态还原到页面上。

这里就需要在清空页面状态时选择性的保留一些需要保留的对象:例如公共的 Framework,JSONP 请求的标签等。

过渡动画

这一点其实就没有多复杂了,在页面不需要被卸载和重新加载后,我们可以在用户点击后立即展示一个动画。目前采用的只是一个简单的从右侧 slide-in 的动画。

需要注意的是,由于在绘制动画的过程中我们往往正在执行下个页面的逻辑,我们需要注意使用 GPU 来绘制动画,从而确保动画不会被 JS 执行阻塞。这一点对于低端机尤为关键。

API 并行加载

其实在有了上面的沙箱机制后,API 的并行加载就不是难事了,需要注意的是我们需要保护 API 并行加载本身的过程中产生的状态(例如 setTimeout ),我们需要实现一个 runInSharedContext 确保这其中的定时器不会在页面切换时被卸载。

  •  
  •  
  •  
  •  
runInSharedContext(() => {  // 这里的 setTimeout 不能是被记录 & 清除的 setTimeout    setTimeout(() => window.sharedfetchDataPromise = fetch(res));});

而在下个页面消费的只需要 window.sharedfetchDataPromise || fetch(url) 就能直接复用并行加载的 API 请求。

在我们的场景下为了让这个问题更加开发者无感,封装了一个叫做 redfox 的工具库,在同一个页面环境执行多次相同配置的请求会自动复用,不需要开发者手动判断。

按照浏览器行为渲染 HTML

这可能是其中最复杂的部分了,在我们抓到下个页面的 HTML 后,不能只是简单的 document.innerHTML = nextHTML ,这样会导致和普通的浏览器行为完全不一致,样式加载会导致闪屏,脚本的执行顺序不符合预期等等。

Web 端如何低成本打造 Native 体验?

 

所以我们需要自己实现一个 renderHTML ,将抓到的 HTML 解析后模拟浏览器的行为进行渲染。

  • 播放动画
  • 先通过样式隐藏 body
  • 异步将 css append 到页面,等到 head 中的 CSS 加载完成并且动画播放完成后取消 body 的隐藏
  • 把 JS 按照类型和顺序 append 到页面
  • inline & 正常的阻塞后续 DOM 和 js 的 append
  • defer 丢到 defer 队列中
  • async 异步执行,不阻塞后续
  • 按次序执行 defer 队列

这个部分的行为比较复杂,需要在较多的场景进行测试,以及有相应的单元测试保障逻辑的正确性。

Web 端如何低成本打造 Native 体验?

 

按浏览器行为触发事件

其实和上面渲染 HTML 相似,在渲染的过程中需要按照浏览器的行为触发相应的事件。

例如上个页面卸载时依次触发 beforeunload => pagehide => unload ,在下个页面加载时先把 readyState 重置,然后按照次序触发 domInteractive defer 的执行和 DOMContentLoaded 。

同样的,单元测试在这个环节是必须的。

分析

Timeline 分析

从 Chrome 最后的 Timeline 看执行逻辑基本是符合我们预期的,点击后的瞬间 API 开始加载并且基本上就开始全力执行下个页面的渲染逻辑。

Framework 层的代码基本也不需要再重复执行。

Web 端如何低成本打造 Native 体验?

 

内存压力

对于这种不卸载页面的方案来说最容易引起担忧的可能就是内存泄露问题,其实按照上面的沙箱机制,只要我们确保 DOM、全局变量、定时器、时间监听等能够被正确清除,与之相关的闭包等就不会赖在内存中不走。

从我们本地多次频繁点击切换页面的反应看,内存随着页面的切换返回也会一次次回到初始状态,从理论上不存在直接导致内存泄露的缺陷。

Web 端如何低成本打造 Native 体验?

 

然而,由于我们允许在页面间保留一部分的公共区域(上面称之为 Service Layer),另外沙箱本身是一个约定沙箱而非安全沙箱(例如往 Element.prototype.xxx 属性写东西就无法被拦截),对于一些不规范的写法仍然存在内存泄露的风险。

这一点可能需要和 Native 端类似的内存压力监控等方式来长期观察。

分阶段打点

由于整个 HTML 渲染过程都是我们自己实现的,所以整个渲染的各个阶段可以自己打点记录一些时间,下面就是一个例子:API 从 JS 请求到拿到耗时 124ms ,而实际上整个取数据(提前并行取的)花了 350ms 。每一个 script 开始执行和执行耗时也可以通过这种方式打上来。

Web 端如何低成本打造 Native 体验?

 

这也可以为我们的页面优化提供一些指导,例如 JS 的执行时间是不是过晚,某段 JS 的执行时间是不是过长。

效果

最终的对比效果如下,左为同屏渲染,右为正常跳转,从线上的数据看性能提升大约从 2.8s => 1.8s 。

Web 端如何低成本打造 Native 体验?

 

除了异步渲染的页面外,我们针对一些原先是 SSR 的页面也做了非常低成本的接入(不需要改造页面,但是享受到的受益相对也更有限)。

Web 端如何低成本打造 Native 体验?

 

但仅仅是上面这种跳转体验和返回体验的改善,就让我们的 Just For U 模块的曝光屏数有稳定 3% 的增长。

总结

总结一下:

  • 类似 SPA 体验的客户端渲染可以让 Web 的体验更接近 Native。
  • 同屏渲染是一种让网站低成本渐进式实现 Native 化体验的方式。
  • 更加沉浸的体验确实会让用户有意愿进行更多地浏览。

局限

上面的方案仍然存在一些局限性,例如前面提到的需要开发者防范内存泄露的问题,同时因为 History API 的限制,页面必须是同域的,否则跳转的 URL 无法满足预期。

未来

关注 Chrome 动态的同学也会了解到 Chrome 最近也退出了一个新的提案:Portal API,就是旨在解决我们上面提到的 Web 体验割裂的问题。

能够提供一个类似 iframe 的沙箱,以较低的成本实现页面间的跳转过渡等。在未来 Protal 普及后(至少 Chrome 发布, Safari 跟进后),我们就可以在新版本的浏览器中抛弃现在使用 JS 实现的沙箱机制,使用更加安全(且炫酷)的 Portal API 来实现同屏渲染。

在 Protal API 的支持下,我们也可以克服无法跨域的问题,按照目前的草案,Portal 是支持跨域跳转的。

拓展阅读

[1]qiankun (https://github.com/umijs/qiankun)

[2]Hands-on with Portals: seamless navigation on the Web

(https://web.dev/hands-on-portals/)



Tags:Native   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
概述谷歌云使用先进的 Andromeda 网络来实现 VPC 内实例之间的相互访问,以及 Google Kubernetes Engine (GKE) 的 Pod 的跨节点互访,避免了配置静态路由或者 Overlay 网络带来...【详细内容】
2021-08-20  Tags: Native  点击:(103)  评论:(0)  加入收藏
Nativefier简介Nativefier 是一种命令行工具,可以用最少的配置轻松地为任何网站创建桌面应用程序。它是由 Electron 引擎生成的可执行文件(.app .exe 等),能够运行在Windows,Mac...【详细内容】
2020-12-23  Tags: Native  点击:(189)  评论:(0)  加入收藏
阿里妹导读:Web 应用在实际体验上和 Native 应用仍然存在非常明显的差距,那么如何低成本地把一个现有的网站改造成类 Native 的体验呢?本文分享一种让网站低成本渐进式实现 Na...【详细内容】
2020-06-08  Tags: Native  点击:(54)  评论:(0)  加入收藏
1、前言 2017 年 12 月,微信小程序向开发者开放了实时音视频能力,给业内带来广阔的想象空间。连麦互动视频直播技术在 2016 年直播风口中成为视频直播的标配,然而只有在原生的...【详细内容】
2020-03-25  Tags: Native  点击:(93)  评论:(0)  加入收藏
我们上一篇文件文章已经说明了Native VLAN的3个工作原理,今天主要是设计一个测试用例来进行验证。今天主要讨论的是一个Trunk端口,配置的Native VLAN,如果此时这个端口收到了没...【详细内容】
2019-09-27  Tags: Native  点击:(392)  评论:(0)  加入收藏
前言React Native 是 Facebook 2015年开源的 Javascript 框架,旨在使用 Javascript 高效开发手机端 App。配合着多个显而易见的优势和 Facebook 强大的宣传机器,它立刻成为国...【详细内容】
2019-08-12  Tags: Native  点击:(224)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条