整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

web前端开发常用上拉刷新demo,代码可直接运行

web前端开发常用上拉刷新demo,代码可直接运行

端开发过程中常用的上拉刷新demo,下拉刷新与此原理类似

界面如下(有点丑的demo哈):

代码如下如示:

<style>

*{

padding: 0;

margin: 0;

}

.box{

height: 100%;

/* overflow: hidden auto; */

}

#refreshContainer{

width: 90%;

margin: 0 auto;

}

ul li{

outline-style: none;

outline: none;

}

#refreshContainer li{

height: 60px;

line-height: 60px;

padding: 0 10px;

background: green;

margin: 10px auto;

list-style:none;

}

</style>

</head>

<body>

<div class="box">

<ul id="refreshContainer">

<li>111</li>

<li>222</li>

<li>333</li>

<li>444</li>

<li>555</li>

<li>111</li>

<li>222</li>

<li>333</li>

<li>444</li>

<li>555</li>

</ul>

<p class="refreshText"></p>

</div>

</body>

<script>

(function(window) {

// 获取当前滚动条的位置

function getScrollTop() {

var scrollTop=0;

if (document.documentElement && document.documentElement.scrollTop) {

scrollTop=document.documentElement.scrollTop;

} else if (document.body) {

scrollTop=document.body.scrollTop;

}

return scrollTop;

}

// 获取当前可视范围的高度

function getClientHeight() {

var clientHeight=0;

if (document.body.clientHeight && document.documentElement.clientHeight) {

clientHeight=Math.min(document.body.clientHeight, document.documentElement.clientHeight);

}

else {

clientHeight=Math.max(document.body.clientHeight, document.documentElement.clientHeight);

}

return clientHeight;

}

// 获取文档完整的高度

function getScrollHeight() {

return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);

}

var _text=document.querySelector('.refreshText'),

_container=document.getElementById('refreshContainer');

// 节流函数

var throttle=function(method, context){

clearTimeout(method.tId);

method.tId=setTimeout(function(){

method.call(context);

}, 300);

}

function fetchData() {

setTimeout(function() {

_container.insertAdjacentHTML('beforeend', '<li>new add...</li>');

}, 1000);

}

window.onscroll=function() {

if (getScrollTop() + getClientHeight()==getScrollHeight()) {

_text.innerText='加载中...';

throttle(fetchData);

}

};

})(window);

</script>

代码可直接复制

每日一分享,感谢所有关注的粉丝们

读一开始我们的H5页面秒开率只有30%左右,现在我们的H5页面秒开率达到了 75%。这中间巨大的差异究竟有哪些黑科技在里面?我们为什么要做H5页面的秒开优化?我们的秒开指标是如何统计的?客户端和H5是怎么配合做到 1+1>2的?监控是如何发现H5页面可优化项的?我们又通过监控发现了哪些可优化的问题呢?

1. 背景


H5秒开优化是一个老生常谈的问题,本文将逐步介绍如何通过客户端 + H5 的优化手段(1+1>2)把秒开从 30% 提升到 75% ?后续接口预请求、客户端预渲染以及预加载2.0上线后还会再次助力指标提升。


为什么要做优化?

Global Web Performance Matters for ecommerce 的报告中指出:

  • 47%的用户更在乎网页在2秒内是否完成加载。
  • 52%的在线用户认为网页打开速度影响到他们对网站的忠实度。
  • 每慢1秒造成页面 PV 降低11%,用户满意度也随之降低降低16%。
  • 近半数移动用户因为在10秒内仍未打开页面从而放弃。


整体系统架构图:


2. 指标选择


首先讲一下得物用来衡量秒开的指标 FMP,那为什么不选择 FCP 或者 LCP 呢?FCP 只有要渲染就会触发,LCP 兼容性不佳,得物希望站在用户的角度来衡量秒开这件事情,用户从点击打开一个WebView到首屏内容完整的呈现出来的时间点就是得物定义的FMP触发时机。


指标清楚了之后,再来看一下完整的 FMP 包含哪些耗时。



接下来将分为两大部分进行介绍,客户端优化部分和H5 优化部分。

3. 客户端优化


通过 HTML 预加载、HTML 预请求、离线包、接口预请求、链接保活、预渲染等手段提升页面首屏打开速度,其中预加载、预请求、离线包分别可提升10%左右的秒开。


3.1 HTML预加载

通过配置由客户端提前下载好HTML主文档,当用户访问时直接使用已经下载好的HTML文档,以此减少HTML网络请求时间,从而提升网页打开速度。




3.1.1 如何确定需要下载的页面


前人栽树后人乘凉,得物App有很多的资源位,banner、金刚位、中通位等,这些位置显示什么内容,早就已经是智能推荐算法产出的了,那么就可以直接指定这些资源位进行预加载。



3.1.2 页面缓存管理


页面被预加载之后,总不能一直不更新吧?那么什么时候更新页面的缓存呢?

  • 预加载的页面存放于内存中,关闭App缓存就会被清除
  • 通过配置过期时间人为控制最大缓存时间
  • 在页面进入后发起异步线程去更新HTML文档。


被现实打脸:


但是在后面的灰度过程中被现实狠狠的教育了一顿,发现有些SSR的页面会涉及到状态的变更,比如说:领劵场景。这些状态都是经过SSR服务渲染好的,用户在进入页面时还没有领劵,这个时候去更新HTML文档,实属更新了个寂寞,在用户领劵之后关闭页面再次进入,发现页面中的状态仍是让用户领劵,点击领劵又告诉人家你已经领过了。



改进措施

  1. H5 页面在打开时针对状态可能会发生变更的组件,再次请求接口获取最新的状态数据。
  2. 客户端由进入页面就更新HTML文档修改为:关闭WebView时更新HTML文档。


3.1.3 线上收益效果


至此问题也解决了,工程师的任务结束了吗?如果你认为功能做上去就算结束,那么此时此刻请你一定要转变思维,想一想我们的目标是什么?我们的目标是「提升秒开」,预加载只是一种提升秒开的手段,但是在功能做上去之后并不知道这个功能带来了多少秒开的收益,因此在把功能开发完成上线之后,就要开始关注上线之后的结果,来看看预加载的性能表现如何。从下图可以看到,预加载开启状态下可提升10%以上的秒开率。



3.1.4 遇到的挑战

  1. 预加载的页面基本上都是 SSR 服务的页面,预加载无形中造成了大量的请求,此时得物的SSR服务扛不住这么大的请求量;
  2. 即使SSR 服务扛得住,也会对后端整个服务链路造成压力。

(1)SSR服务扩容


要解决服务器压力问题,很自然就会想到增加机器,于是我们对SSR机器数量做了一次扩容,将机器数量提升了一倍,这个时候继续尝试扩大预加载的用户数量,但是仍然无法抗住这么大的QPS,而且此时还引发了第二个问题,算法部门的服务器发出了告警,于是放量计划又一次遇到了阻碍。


(2)破局者 CDN


利用CDN 服务器的缓存能力既可以减轻 SSR 服务器的压力又可以减少后端服务链路的压力,这么好的东西为什么不用呢?这里留个悬念,后面将H5优化部分会详细介绍。


(3)客户端配合改造

支持针对CDN域名进行全部开放预加载能力,针对非CDN域名保持原有放量比例。


3.1.5 开屏页预加载


在这个过程中还分析了页面的流量占比,发现开屏广告来源的页面流量占比也很高,那么是不是可以把开屏广告的HTML文档内容也给预加载下来呢?


开屏页面预加载策略


  1. 对预加载列表进行去重,开屏广告列表中可能会存在重复的页面,他们的背景图和生效时间是不同的;
  2. 增加了生效时间相关配置,开屏广告列表中存在于未来某段时间才会展示的页面;
  3. 添加黑白名单控制,开屏广告列表中可能会有第三方合作页面,他们不希望预加载统计会造成PV时不准确。


3.1.6 预加载展望


既然可以提前下载好HTML,那是不是可以更进一步,提前把页面内的资源加载好,这样在打开一个页面的时候可以减少大部分的网络请求从而更快速的把内容呈现给用户。这里还需要考虑如何跟下面讲到的离线包进行协作。



3.2 HTML预请求


在WebView初始化同时,去请求HTML主文档,等待HTML文档下载完成 且 WebView初始成功后渲染,减少用户等待时间,客户端请求成功后,WebView加载本地 HTML,并保存以供下次使用。预请求HTML开启状态下可提升8%左右的秒开。


预请求 VS 预加载


本质上HTML预加载和HTML预请求的区别就是下载HTML文档的时机不同, 预加载是在App启动后用户无任何操作的情况下就会去下载,但是预请求只会在用户单击打开H5页面的时候才会去下载。如果用户是第二次打开某个H5页面,此时发现本地有已经下载好的HTML且尚未过期就会直接使用,这个时候的行为表现就跟预加载的功能是一致的了。


3.2.1 遇到挑战

上线之后发现预请求只提升了2%左右的秒开,经过分析发现问题:

  1. 缓存有效时间太短,页面过期时间只配置了10分钟,也就是说在10分钟之后用户就要重新去下载一次,那能不能把缓存时间延长呢?
  2. H5页面是没有自更新能力,无法支持配置更长的缓存时间,跟预加载HTML问题一致。

3.2.2 深入挖掘


在本地用低端机对整个秒开耗时链路进行了分析,为什么要用低端机分析呢?低端机有个好处,天然的加上了慢放功能,可以最大程度发现问题。


(1)安卓h5 页面加载 与 原生布局填充并行执行



从图中可以看出h5 页面加载之前 耗时 分布在 activityStart() 函数,该函数 包含了 onCreateView ,其中耗时最长是 布局填充 inflate(),因为 WebView 对象是提前创建好的,直接从对象池中取出的,所以耗时主要在 初始化过程,WebView 自身的初始化 WebViewChromiumFactoryProvider. startYourEngines (耗时 87 us,不到 1 ms),耗时还有 WebView 的一些其他初始化,jockey 的初始化 等等。


而秒开的计算是包含了 View 初始化到 WebVIew url 加载 的耗时,从而发现了优化点,可以将Webview loadUrl 前置,h5 页面加载 与 原生布局填充并行执行。在 onCreateView 时,创建 FrameLayout 进行返回,执行 WebView loadUrl 之后,主线程开始 对布局进行 inflate,布局加载成功后,将其 addView 到 FrameLayout 中,减少了 loadUrl 的阻塞时长。中高端机型有 15ms 左右优化,低端机型有 30 ~ 50 ms 优化 效果。


(2)双端下载HTML的时机提前至路由阶段


预请求HTML时机是在进入到 native 页面中,这个时间点距离用户单击事件已经过去100ms,那么是否可以将下载HTML的时机提前呢?经过一番探索,最终选择在路由阶段进行拦截,既可以统一收口而且距离用户点击的时间间隔可以忽略不计。通过这种方式将下载HTML时机提前了平均80ms+。

此时的流程变成了下面这样。



可能有的同学会问了,为什么不在用户点击的时候去下载呢?从用户点击到路由肯定还是有耗时的。


  1. 代码层面不好维护,如果在点击时就需要侵入到业务层面,入口千千万,很难进行维护管理;
  2. 从点击到路由这部分耗时在线下进行了性能测试,几乎可以忽略不计。


3.2.3 最终线上收益效果


在上述问题解决后,将缓存时间修改为1天,发现预请求HTML开启状态下可提升8%左右的秒开,已经和预加载的效果相差不大了。


3.3 离线包


通过提前将H5页面内所需的css、js等资源聚合在一个压缩包内,由客户端在App启动后进行下载解压缩,在后续访问H5页面时,匹配是否有本地离线资源,从而加速页面访问速度。



3.3.1 安卓实现


资源拦截这块安卓这边实现比较简单,WebView支持 shouldInterceptRequest, 可以在该方法内检测是否需要进行资源拦截,需要的话返回 WebResourceResponse 对象,不需要直接返回 null。



3.3.2 iOS 实现


但是在iOS 这边遇到了一些困难,调研了以下方案:


方案一:NSURLProtocol 拦截方式

NSURLProtocol 拦截方式,使用WKBrowsingContextController和registerSchemeForCustomProtocol。通过反射的方式拿到了私有的 class/selector。通过把注册把 http 和 https 请求交给 NSURLProtocol 处理。通过这种方式确实可以拦截请求,但是发现post请求的body会出现丢失的问题。而且NSURLProtocol一经注册就是全局开启。我们希望他只会拦截接入了离线包的页面,但是没有办法控制他,他会拦截所有页面的请求,包括第三方合作页面,显然这是无法接受的。


方案二:通过CustomProtocol拦截请求


在iOS 11及以上系统中, 拥有了加载自定义资源的API:WKURLSchemeHandler。

可以修改当前页面url为自定义 scheme 协议,比如:https://fast.dewu.com 修改为 duapp://fast.dewu.com 然后在客户端内注册该 scheme,前端配合修改页面内所有的资源请求未自适应协议,如:src="//fast.dewu.com/xxx" 就可以实现拦截。但是在测试过程中发现,接口为了安全起见只允许白名单内的域名发起跨域请求,且无法配置多个域名,导致该方案无法继续进行。


方案三:hook handlesURLScheme



仍然是使用 WKURLSchemeHandler 然后通过 hook WKWebview 的 handlesURLScheme 方法来支持 http 和 https 请求的代理。通过这种方式虽然可以拦截请求了,但是遇到了以下问题:


(1)body丢失问题


不过在 iOS 11.3 之后对这种情况做了修复处理,只有 blob 类型的数据会丢失。需要由JS来代理 fetch 和 XMLHttpRequest 的行为,在请求发起时将 body 内容通过 JSBridge 告知 native,并将请求交给客户端进行发起,客户端在请求完成后 callback js 方法。


(2)Cookie 丢失、无法使用问题


通过代理 document.cookie 赋值和取值动作,交由客户端来进行管理,但是这里需要额外注意一点,需要做好跨域校验,防止恶意页面对cookie进行修改。


3.3.3 遇到挑战


至此功能开发完成上线,先来一组线上收益数据,安卓开启离线情况有有10%左右的收益,但是iOS开启离线的反而秒开率更低。经过修复处理后iOS也可提升10%以上的秒开。



安卓和iOS实现差异


经过分析对比发现,安卓的拦截动作比较轻,可以判断是否需要拦截,不需要拦截可以交给WebView自己去请求。


但是iOS这边一旦页面开启拦截后,页面中所有的http和https请求都会被拦截掉,由客户端发起请求进行响应,无法将请求交还给WebView自己去发起。


iOS 缓存问题修复


页面中的资源经过客户端请求代理后原本第二次打开WebView本身会使用缓存的内存,现在缓存也失效了,于是只能在客户端内实现了一套缓存机制。


    1. 根据 http 协议来判定哪些资源可以被缓存以及缓存的时长
    2. 添加自定义的控制策略,仅允许部分类型的资源进行缓存


3.3.4 离线包下载错误率治理


从下图可以看到离线包的下载错误率在 6% 左右浮动,这么高的下载错误率肯定是无法接受的, 经过一系列优化手段,把离线包下载错误率从6%左右浮动下降至 0.3%左右浮动。



先来看下优化前的流程图和问题点。



通过埋点发现大量 unknown host 、网络请求失败、网络连接断开的情况。分析代码发现下载未做队列控制,会同时并发下载多个离线包,从而导致多个下载任务争抢资源的情况。针对发现的问题点做出了以下优化:


    1. 下载失败添加重试机制,并可动态配置重试次数用于缓解网络请求失败、网络连接断开的问题。
    2. 添加下载任务队列管理功能,可动态配置并发下载数量,用于缓解不同下载任务争抢资源的问题。
    3. 针对弱网和无网络情况延迟到网络良好时下载。
    4. 离线包下载支持 httpdns,用于解决域名无法解析的情况。


下面是优化之后的流程图:



3.3.5 展望

针对离线资源是直接存储在磁盘上的,每次访问都会有磁盘IO耗时,经过在低端机器上做测试发现这个耗时会在 0 - 10ms 之间进行波动,后面会把内存合理的利用起来,通过设置内存上限,文件数量上限,甚至是文件类型,并通过 LRU 策略进行内存文件的淘汰更新。

3.4 接口预请求

通过客户端发起H5页面首屏接口请求,远比等待客户端页面初始化、下载HTML、JS下载执行的时机更提前,从而节省用户的首屏等待时间。在本地测试过程中发现接口预请求可提前100+ms,用户也就可以更快的看到内容。



3.4.1 功能介绍


客户端会在App启动后获取配置,保存支持预请求的页面地址及对应的接口信息,在用户打开WebView时,会并行发起对应预请求的接口,并保存结果。当JS执行开始获取首屏数据时,会先询问客户端是否已经存有对应的响应数据,如果此时已经拿到数据则无需发起请求,否则 js 也会发起接口请求并开启竞速模式。以下是整体流程图:



3.4.2 配置平台


那么客户端怎么知道这个页面需要请求什么接口呢?以及接口的参数是什么呢?那自然少不了配置平台,它支持以下功能:


    1. 配置需要预加载的页面 url并对应一个需要请求的 api url 以及参数
    2. 配置审核功能,避免错误配置发布上线


3.4.3 QA 既然有了SSR服务端渲染为什么还需要接口预请求的功能?

首先即使是在 SSR 的情况下,首屏内容中仍然可能有部分组件是骨架直出的,需要等待页面渲染执行时才会去请求数据,另外还有一部分页面是SPA的。针对这两种情况都能做到很好的补充。


3.5 预建连&链接保活

开启后DNS 90分位耗时从80ms降至0ms,TCP建连90分位耗时从65ms分位耗时降为0,DNS平均耗时从55ms降为4.3ms,TCP建连平均耗时从30ms降为2.5ms。


3.5.1 网络请求耗时分析


通过上图可以看到一个网络请求在经过DNS解析耗时、TCP建连耗时、SSL建连耗时阶段之后才能把请求发出去,那么是否可以节省这段时间的耗时呢?


客户端常用的网络请求框架如OkHttp等,都能完整支持http1.1与HTTP2的功能,也就支持连接复用。了解了这个连接复用机制优势,那就可以利用起来,比如在APP开屏等待的时候,就预先建立关键域名的连接,这样进入相应页面后可以更快的获取到网络请求结果,给予用户更好体验。在网络环境偏差的情况下,这种预连接理论上会有更好的效果。


3.5.2 实现方案


可以通过对域名链接提前发起一个HEAD请求从而建立链接,网络框架会自动将连接放入连接池。并在默认无操作5分钟后进行释放,在五分钟内重复执行上述动作即可一直保持链接。


另外这里需要注意下连接池的数量问题,如果连接池的数据太小,但是域名比较多的话,通过预建连保持的链接很容易就会被释放掉,这就需要通过域名收敛或者调大连接池的数量来进行优化。


3.5.3 线上收益


那预建连会不会增加服务器的压力呢?这个肯定是会的,首先会针对预建连功能本身就行灰度策略,在HTML页面通过CDN托管后,直接针对 cdn 域名进行全量开启,从而不用担心 cdn 域名扛不住压力。


来看一下线上效果,通过下图可以看到在开启后DNS 90分位耗时从80ms降至0ms,TCP建连90分位耗时从65ms分位耗时降为0,DNS平均耗时从55ms降为4.3ms,TCP建连平均耗时从30ms降为2.5ms。



3.6 预渲染


客户端提前通过WebView将页面渲染好,等待用户访问时,可直接展示。从而达到瞬开效果。但是这种功能肯定不能对所有的页面进行开放,而且存在一定的弊端。


    1. 会额外消耗客户端资源,需要在主线程空闲时执行,并需要控制预渲染的页面数量。
    2. 如果页面一进入就会下红包雨,这种页面是不适合做预渲染的,需要进行规避。


下图【开学季】是业务上已经进行预渲染的H5页面,可以看到在打开【开学季】页面时,页面已经渲染完毕,丝毫没有等待过程。



后面计划把这种能力放大到通用的webview上,针对大促以及PV量高的页面进行开放。

4. H5 优化

4.1 SSR服务端渲染


SSR服务端渲染(英语:server side render)一般情况下,一个H5页面的数据渲染完全由客户端来完成,先通过AJAX请求到页面数据并把相应的数据填充到模板,形成完整的页面来呈现给用户。而服务端渲染把数据的请求和模板的填充放在了服务端,并把渲染的完整的页面返回给客户端。



SSR对于秒开有平均15%的提升,既然是服务端渲染,就会对服务器造成压力,尤其是在预加载HTML功能开启后,那得物是如何解决的呢?


4.1.1 前期优化内容

    1. 接口缓存:node服务向ctx中注入redis实例,业务方在服务端渲染的逻辑中处理接口相关的缓存,这其中涉及:配置文件下发、ab接口。
    2. 静态页面缓存:由于页面完成是无接口交互,并且所有用户展示都是一样的,由renderToHtml生成静态html资源,写入缓存。
    3. 无用户状态页面缓存:此类页面大多数情况下展示内容都是一样的,服务端请求的数据也都是一致的,服务端处理的时候根据是否有用户登陆状态判断是否需要缓存。
    4. 千人千面接口内容由SSR修改为CSR,并显示骨架图,由于千人千面内容都是由算法接口提供,且算法接口本身响应较慢,因此通过这种方式可以减少服务端响应耗时,且能更快速的给用户展示内容。


4.1.2 破局者CDN


通过这么多优化手段仍然无法满足预加载的需求,并且通过分析发现网络阶段耗时较长,最终还是搬出了CDN这个大杀器,一直没上CDN的原因有很多,主要有以下几方面:


(1)得物的页面是千人千面的每个人看到的内容都不同

通过上述优化4即可解决,将原本SSR渲染的内容修改CSR,由于已经上了CDN了,后续计划将这部分内容再次修改回SSR,这样用户可以更快的看到商品而不是骨架,然后通过 CSR 的方式来更新内容。


(2)页面状态变更,无法及时更新缓存

这个问题在上述客户端预加载优化部分已经有解决方案了,可以通过在页面打开后针对有需要的组件再次请求接口刷新数据以确保数据的准确性,但是这部分工作量也是比较大的,梳理出来的需要刷新状态的组件就有30+,而且之后开发的组件都需要考虑状态更新的事情。


(3)HTML模板内容变更无法及时更新

引起模板内容变更的地方有两处,第一个场景就是在搭建器场景下,运营可以动态修改模板内容导致页面结构变更(低频次),第二个场景是项目发版后模板内容需要更新(高频)。


这个问题可以通过在感知到内容变更时自动调用CDN服务商的刷新缓存接口来更新CDN缓存内容。

4.2 预渲染HTML


通过puppeteer将SPA页面渲染出来并将HTML文档进行保存,配合上述页面刷新策略,并将HTML通过CDN进行托管,让你的 SPA 页面 像 SSR 一样丝滑。


主要实现方案是通过基于webpack的插件prerender-spa-plugin,并配置需要预渲染的路由,这样经过打包之后就会产出对应路由的页面。方案本身是通用的,但是每接入一个页面都需要人工check。


4.3 不起眼的css包大小优化


众所周知 css 加载会阻塞HTML渲染,最终将首屏公共css从118kb缩减至38kb,下图通过 chrome 模拟弱网环境下的SSR页面加载时序图。从图中可以看出 styles.fb201fce.chunk.css 下载耗时 18s,阻塞了页面的渲染,HTML 主文档耗时 2.38s 就已经下载完成了,但是实际渲染时间却是在 20s 之后。



优化思路也比较单间,将首屏所需要的css 文件通过内联方式内嵌到HTML中,由SSR服务一并返回,并对css文件进行拆分,按需加载。


思路有了,接下来就看怎么去实现了,最初尝试了MiniCssExtractPlugin 插件他可以把css分成单独的文件,并且每一个js会对应生成一个css文件,但是他需要建立在webpack5之上,然而项目中使用的next版本是9.5,于是就想着升级到最新版next12,升级后发现,在构建中其他包各种报错,发现有些包并不支持最新的next12,在尝试一天的修复之后,仍未解决,且升级到最新版不确定是否会引发其他稳定性问题,暂且搁置寻找其他方法。


经过不懈的努力,通过阅读 next 源码发现了端倪,发现在打包时将所有的公共css通过 splitChunks 进行分组,由于项目中组件都是动态引入的,这里直接在 next.config.js 中修改webpack打包参数,将 splitChunks.cacheGroups.styles 配置删除,使用默认的 chunks: async 配置,即可实现按需引入。


4.4 图片优化

(1)避免图片src为空

虽然src属性为空字符串,但浏览器仍然会向服务器发起一个HTTP请求,尤其是在SSR服务器压力扛不住的情况下,因此这里需要特别注意一下。


(2)图片压缩以及格式选择

WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。

通过向图片服务器传递参数选择合适的分辨率。


4.5 细节优化


(1)打包优化

  • 页面组件拆分,优先加载首屏内容所需资源
  • webpack splitchunks 有效拆分公共依赖,提高缓存利用率
  • 组件按需加载
  • Tree Shaking 缩减代码体积


(2)非关键js、css延迟加载

  • defer、async、动态加载js
  • iOS 设备延迟加载 js


(3)媒体资源加载优化

  • 图片、视频懒加载
  • 资源压缩、通过向图片服务器传递参数选择合适的分辨率


(4)其他资源优化

  • 数据埋点上报延迟发送,不阻塞 onload 事件触发
  • 自定义字体优化,使用 fontmin 生成精简的字体包


(5)页面渲染优化

  • 页面渲染时间优化
    • SSR页面首屏 css 内联(Critial CSS)
  • 合理使用 Layers
  • 布局抖动优化:提前定好宽高
  • 减少重排重绘操作


(6)代码层面优化

  • 耗时任务分割
    • 通过 Web Worker 减少主线程耗时
  • 通过 RAF 回调,在线程空闲时执行代码逻辑
  • 避免 css 嵌套过深

5. 监控


为了帮助开发者更好地衡量和改进前端页面性能,W3C性能小组引入了 Performance API ,其中Navigation Timing API 实现了自动、精准的页面性能打点。得物前端性能监控指标也都是从 Performance API 中获取数据进行上报统计分析的。



5.1 系统架构


SDK 数据采集完毕后,会上报到 阿里云 sls 日志平台,随后通过 flink 实时消费清洗数据后落库至 clickhouse 中,平台后端通过读取 clickhouse 数据并做各种聚合处理后使用。



5.2 指标大盘

做优化之前首先要建立监控指标,互联网称之为抓手,没有监控指标的情况下,任你怎么优化,都不知道优化的效果怎么样,更不知道下一步该做什么?以及还有哪些问题没解决。因此优化之前指标先行,当然一定要指标的准确性。


指标大盘主要包含以下功能:

    1. 可快速查看某段时间内的版本、设备厂商、设备名、设备系统版本以及网络占比情况,还可以根据这些字段进行筛选排查。
    2. 中间区域展示了比较关注的整体以及活动页面的客户端耗时和H5秒开耗时的情况。
    3. 底部区域展示了各业务域的秒开耗时情况。
    4. 这里同时展示了平均耗时90分位耗时,平均耗时有个弊端就是很容易被平均,大家应该都有被平均的经历。90分位耗时这里简单讲解一下:意思就是有90%的访问耗时是低于90分位耗时的,以此类推 50 分位就是有50%的访问耗时是低于50分位的,分位值是将所有的耗时数据从小到大排序后得出的。


5.3 白屏监控


在正常情况下,完成上述的优化措施后用户基本是可以秒开 H5 页面的了。但异常情况总是会有的,用户的网络环境和系统环境千差万别,甚至 WebView 也可能发生内部崩溃。当发生问题时,用户看到的可能就直接只是一个白屏页面了,所以进一步的优化手段就是需要去检测是否发生白屏以及相应的应对措施。


检测白屏最直观的方案就是对 WebView 进行截图,遍历截图的像素点的颜色值,如果非纯色的颜色点超过一定的阈值,就可以认为不是白屏。首先获取包含 WebView 视图的 Bitmap 对象,然后把截图缩小到指定的分辨率大小如:100*auto,遍历检测图片的像素点,当非纯色的像素点大于 5% 的时候就可以认为是非白屏的情况,但是还有很多列外的情况,我们通过图片识别技术对截图进行分析,可以很好的感知当前是否白屏、是不是在loading、是不是特殊页面等。


白屏是一个重要的指标,我们针对整体白屏率快速拉升、新增白屏页面发出告警通知,便于开发人员及时介入开始排查问题。


5.4 性能问题发现


主要通过 CDN 未覆盖监控、http请求监控、网络监控(加载失败、耗时异常、传输大小异常)、图片监控(未压缩、分辨率异常)等监控手段发现页面中的潜在问题,同时还提供了问题分析能力,在问题分析页面输入页面url地址即可帮助您发现问题并给出修改建议。


5.4.1 CDN未覆盖监控


CDN的重要性不言而喻,它可以加速资源访问速度,从而提升用户体验,我们通过对线上埋点数据分析,找出CDN未覆盖的资源列表,从而推动各业务同学优化。


5.4.2 HTTP请求监控

为什么要监控HTTP请求呢?我们先来看一下HTTPS相对于HTTP新增的特点:


  1. 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  2. 验证身份:通过证书认证客户端访问的是自己的服务器
  3. 保护数据完整性:防止传输的内容被中间人冒充或者篡改


那么HTTP就容易被中间人查看到内容,甚至被篡改,既然如此为了我们服务的安全性就需要对现有的http协议统一进行升级改造,那就需要监控去发现。


5.4.3 网络监控


某些页面秒开率低,那就要分析一下原因,是不是这个页面的接口响应比较慢呢,还是说页面本身有请求比较大的资源?如果发生网络请求失败的情况也要第一时间感知,不能被动等待用户反馈。


5.4.4 图片监控


包含图片未压缩、图片分辨率异常、图片传输大小大于 300kb 异常、动图资源传输大小大于 1M 异常功能。


5.4.5 页面问题分析


上面列出来一堆功能,对于业务的同学可能比较烦恼,我一个页面具体有哪些问题呢?你不能让我去上面的功能里面一个个看,哪个异常是我负责的页面的吧?这个功能本身就行将现有的功能利用起来,通过一个页面path进行聚合分析。



5.5 异常监控


H5异常一直是使用 sentry 进行监控的,但是sentry系统因缺乏同PV、DAU数据的关联性,因此无法衡量产品异常发生后所带来的严重程度。在业务域关联上的缺失导致异常问题无法根据业务域进行划分。用户行为日志也尚未与Native 端侧打通,在问题分析时容易遇到上下文不全的瓶颈。还有一个问题是sentry会有限流措施,当qps较高时会丢弃一部分异常数据。



由于sentry已经可以帮助我们进行一定的问题排查分析能力,我们不打算做sentry同样的功能,而是做sentry不支持的部分,针对上述问题我们设计了以下功能:


  • 异常问题指标衡量
    • 增加异常率、页面异常率、影响用户率趋势
    • 增加问题多维度(系统版本、APP版本、H5发布版本、网络等)下的分布占比、业务域划分
  • 异常问题聚合能力增强(聚合问题能力增强)
    • 异常列表支持最新新增、Top PV、异常次数、影响用户数排序
    • 区分三方sdk异常、接口异常等不同异常类型划分


6. 未来展望&总结


虽然目前秒开率已经做到了75%以上,但是同时我们还有一个重要的指标,90分位耗时,致力于提升末尾用户H5页面使用体验,在90分位优化完成后,可能会考虑继续深入优化95分位耗时。

最后感谢那些为得物H5页面秒开做出贡献的同学,感谢H5团队,同学们都很棒,各种优化手段和想法层出不穷。


至此我们系统的讲解了背景以及从指标建立到秒开优化上线的全过程,全文分成了三个部分,客户端、H5、以及监控。如果阅读本文对您有所收获,麻烦您动一动发财的小手点个赞吧!如果阅读完还意犹未尽或者有什么问题和想法欢迎留言区评论交流。


最后奉上整体优化脑图:


*文/徐铭

欢迎关注「得物技术」微信公众号,每周一三五晚18:30更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~

端开发

软件开发是一个高度专业化的职业分工,根据所使用的编程语言的不同,会细分出多种岗位:前端开发、后端开发、客户端开发、iOS开发、Android开发、数据库开发等等,具体到每一个岗位,工作中常用的工具软件也存在着差别。

就前端开发或前端工程师来说,前端技术的发展可以说是日新月异,新工具和框架层出不穷,以应对不断变化的市场需求。在这篇文章中,我们将介绍前端开发中常用的8大类工具软件,以及每个类别下面最具代表性的产品,总计17款工具软件,帮助各位在选择前端工具时减少纠结,提高工作效率和开发质量。

  • 代码编辑工具:WebStorm、VS Code
  • 代码版本控制工具:Git、SVN
  • 代码包管理工具:npm、Yarn
  • 笔记工具:boardmix、Evernote、Typora
  • 前端构建工具:Webpack、Vite
  • 调试工具:浏览器开发工具
  • 响应式设计工具:Bootstrap、Material-UI
  • 浏览器插件:easy auto refresh、FeHelper(前端助手)、Vue Devtools

前端开发常用的17款工具软件

01 代码编辑工具

选用哪个编辑器来写代码,与 IT 界由来已久的一个话题「XX语言是最好的编程语言」有点像,每个人都有自己的想法。工具本身本身并不是最重要的,能否写出简洁、优雅的代码,主要是取决于自己的个人经验和逻辑能力,代码思路没梳理清楚,代码编辑器雕出花来也没用哇。

① WebStorm

WebStorm,最智能的 JavaScript IDE 工具,是一个用于 JavaScript 和相关技术的集成开发环境。与 JetBrains 推出的其他 IDE 一样,它使你的开发体验更加愉快,使日常工作自动化,并帮助你轻松处理复杂的任务。

② VS Code

VS Code,微软推出的开源代码编辑器,支持 Windows、macOS 和 Linux 系统。单凭免费 + 轻量 + 大厂背景(光环)这些点,想必就吸引了不少人。

VS Code 虽然不像 WebStorm 定位为 IDE 工具,但它可以内置的扩展功能、可以安装不同的插件,也让 VS Code 在保证灵活性的同时,也能变得足够强大——软件本身没有的功能,可以用插件来弥补。

除了这里介绍的两个工具,其他可选的代码编辑器有 Sublime Text、Atom、Vim、CodePen(在线代码编辑工具) 等,这里就不展开介绍了。

02 代码版本控制工具

① Git

Git 是目前最流行的分布式版本控制系统,它具有高效的分支管理和合并功能,可以支持大型项目的开发和维护。Git 使用简单,有强大的命令行工具和图形界面工具,例如 Git Bash 和 Sourcetree。程序员可以通过 Git 进行代码的提交、拉取、推送等操作,同时还可以方便地查看代码的历史记录和比较不同版本之间的差异。

② SVN

SVN 是一种集中式版本控制系统,与 Git 不同,它使用集中式的服务器来存储代码,并且需要通过网络连接才能进行代码的提交和更新。SVN 具有较为简单的操作和较好的稳定性,适合小型项目和对分支管理要求不高的团队使用。程序员可以通过 TortoiseSVN 等图形界面工具来进行 SVN 的操作,也可以通过命令行工具来进行更高级的操作。

03 代码包管理工具

当涉及前端开发时,npm(Node Package Manager)和Yarn都扮演着至关重要的角色,它们是包管理工具,用于管理JavaScript库和框架的依赖关系。

① npm

npm是JavaScript世界中最常见的包管理工具之一,它是Node.js的官方包管理器,囊括了各种用于前端和后端开发的包。npm提供了功能强大且灵活的命令行界面,用于执行各种操作,包括安装依赖项、更新包、运行脚本等。这使得前端开发者可以在命令行中快速而高效地完成各种任务。

② Yarn

Yarn是JavaScript世界中的另一款重要的包管理工具,尤其受到前端开发者的欢迎。它在某些方面与npm相似,但也有自己的独特特点:

出色的性能:Yarn被设计为更快的包管理工具。它支持并行下载和安装依赖项,从而提高了性能。这对于大型项目和具有多个依赖项的应用程序特别有帮助,能够显著加快构建和安装速度。

版本锁定:Yarn使用yarn.lock文件来确保依赖关系的一致性。这意味着在不同开发环境中的版本冲突问题将被大大减少,确保您的项目在不同的计算机上以相同的方式运行。

易于使用:Yarn的命令和操作更加直观,使新手可以更快地上手。它提供了更友好的命令行交互和更清晰的日志输出,帮助开发者更好地理解正在发生的事情。

自定义包源:Yarn允许配置自定义包源,这对于需要使用特定镜像或私有存储库的项目非常有帮助。这为开发者提供了更大的灵活性,以适应不同的项目要求。

Yarn提供了一种高性能、可靠且易于使用的包管理工具,适用于前端开发项目。它的版本锁定和离线模式等功能使其在复杂的项目环境中尤为有用。选择使用npm还是Yarn取决于您的项目需求和个人偏好,但无论您选择哪一个,都可以提高包管理的效率。

04 笔记工具

① boardmix

boardmix是一款跨平台的笔记工具,有点接近微软推出的OneNote,但功能比OneNote更强大。

boardmix可以帮助程序员将各种信息和想法整理成文档、文档卡片、思维导图、便签等形式,支持文字、代码块、图片、表格、链接等多种内容的插入,还可以通过标签和容器来组织和分类笔记。此外,boardmix还支持手写和音视频通话功能,方便程序员在会议或讨论中进行记录。

*跨平台笔记工具boardmix

跨平台笔记工具boardmix的特点:

  1. 实时保存,跨平台自动同步
  2. 融合了代码块、思维导图、流程图、文档、看板、表格、PPT等功能,支持在笔记中添加多种形式的内容,满足多样化的记录或协作需求
  3. AIGC创作功能,可用AI快速生成生产级别的代码
  4. 兼容多种第三方应用,如Figma、Pixso、腾讯会议、腾讯文档、Notion、高德地图、亿图图示、金数据等,在一个无限画布上同时使用或浏览多个应用
  5. 文档支持富文本编辑和简单的Markdown编辑两种模式
  6. 支持导出多种文件,包含Markdown、图片、PDF、CSV、JSON、TXT等

② Evernote

Evernote是一款功能强大的云端笔记应用,它可以帮助程序员随时随地记录和整理自己的想法、代码片段、技术文档等。Evernote支持多种格式的笔记,包括文字、图片、音频和视频等,还可以通过标签和笔记本来组织和分类笔记。

③ Typora

Markdown笔记软件是程序员常用的一种笔记工具,它使用简洁的标记语法来编写和格式化文本,可以帮助程序员记录和整理代码片段、技术文档、项目文档等信息。

市面上可选择的Markdown笔记软件非常多,其中最具代表性的是Typora。Typora是一款跨平台的Markdown编辑器,它提供了实时预览功能,可以让程序员在编写的同时实时查看渲染后的效果。Typora支持多种导出格式,如PDF、HTML等,方便用户分享和发布笔记。

05 前端构建工具

① Webpack

说到前端构建工具,不得不提许多前端工程师都逃不过的 Webpack,我们先来看一下 Webpack 中文文档对 Webpack 的介绍:

Webpack,是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 Webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

如果是第一接触 Webpack 的朋友,看完这段介绍应该还是一脸懵逼,不知道 Webpack 到底能用来干什么。

随着前端变得越来越复杂,我们在使用框架编写项目时,可能会有下面这些行为:

  • 引入各种各样的模块来实现模块化开发
  • 使用一些高级特性来加快开发效率或提升安全性,例如使用 Sass、Less 等方式来编写 CSS 样式代码
  • 开发完成后需要对代码进行压缩、合并等,优化页面的打开速度

以其中的 Sass 样式文件为例,浏览器默认情况下只能渲染 CSS 文件,而不认识这里的 Sass 文件,要想将 Sass 文件转换为浏览器可读取的 CSS 文件,就需要用到 Webpack,实现 Sass 到 CSS 的自动转换。

除了 Sass 文件,Webpack 还可以将 ES6 语法的 JS 代码转换为 ES5,将 TypeScript 代码转换为 JavaScript,也可以对图片、字体等进行打包优化,减少文件占用的空间,可以这么说,有了 Webpack,原先我们要处理的一系列问题,都可以让它自动帮我们完成。

② Vite

Vite是一个快速、现代的前端构建工具,专为Vue.js框架设计,但也支持其他现代JavaScript框架和库。

快速开发:Vite是为了提高前端开发的速度而创建的。它利用ES模块的特性,将开发服务器保持在本地开发环境中,实现了快速的冷启动和热模块替换(HMR),这意味着您可以在几乎没有构建等待时间的情况下迅速查看更改。

Vue 3支持:Vite是Vue 3的官方开发工具,它紧密集成了Vue 3的特性和生态系统。这使得使用Vite来构建Vue 3应用程序变得非常高效。

支持其他框架和库:尽管最初是为Vue.js设计的,但Vite也支持React、Preact、Svelte等现代JavaScript框架和库。这意味着开发者可以在不同项目中使用Vite,而不仅仅限于Vue.js。

插件系统:Vite具有灵活的插件系统,可以通过插件扩展其功能。这使得开发者可以根据项目需求轻松自定义构建过程。

构建优化:尽管Vite在开发环境下快速运行,但它也提供了用于生产构建的工具,以优化和压缩代码,减小文件大小。

开发者体验:Vite注重开发者体验,提供清晰的错误消息和有用的调试工具,以帮助开发者更轻松地诊断和解决问题。

Vite是一个面向现代前端开发的快速工具,它通过利用ES模块、本地开发服务器和HMR等特性,提供了出色的开发体验。它适用于Vue.js项目,同时也能够支持其他流行的JavaScript框架和库,使其成为前端开发者的强大工具。

06 调试工具

浏览器开发工具

对于前端Web开发,浏览器开发工具是非常有用的调试工具。浏览器开发工具可以帮助程序员查看网页的HTML结构、CSS样式和JavaScript代码,同时还可以监控网络请求和调试JavaScript代码。常见的浏览器开发工具有Chrome开发者工具、Edge开发者工具、Firefox开发者工具等。

07 响应式设计工具

① Bootstrap

Bootstrap是由Twitter开发的开源前端框架,它提供了一组CSS和JavaScript工具,用于创建现代、响应式的Web页面和Web应用程序。

② Material-UI

Material-UI是一个基于Google的Material Design理念的React组件库。它旨在提供Material Design的外观和感觉,以及与React的深度集成。

08 浏览器插件

① easy auto refresh

easy auto refresh,一个自动刷新浏览器页面的插件,比较适合刚接触前端的新人。

在未使用前端框架或 Webpack 打包项目的情况下,每当我们想预览代码在浏览器中的运行效果,都需要手动刷新页面,操作起来比较繁琐。

对于这个问题,可以使用这里介绍的浏览器插件 ,提前设置一个刷新的频率,就能实现页面的自动刷新。

② FeHelper(前端助手)

FeHelper,一个可以格式化 json 数据的浏览器插件。

在浏览器中查看服务器返回的 json 数据时,由于它们被压缩过,去除了数据中原有的换行,会呈现为「乱成一团」的状态,不便于查看我们想要的数据。

而 FeHelper 插件,就可以让被压缩后的 json 数据还原为压缩前的状态,即对数据进行格式化,美化压缩后的数据文件,让我们可以更方便地查看服务器返回的数据。

③ Vue Devtools

Vue Devtools(Vue.js开发者工具)是专门为Vue.js开发者设计的浏览器扩展,它提供了强大的调试和开发工具,有助于更好地理解、分析和调试Vue.js应用程序。