最近我们的移动端Web应用在iPhone XR上性能很差响应很慢用户体验不好特别是iPhone XR作为苹果今年新发布的机型(2018年10月发布)用户越来越多这个问题必须解决。

于是我对应用做了一次全面的性能调优针对iPhone XR的Safari浏览器做了很多优化最终把页面响应时间降低了80%从原来的平均2.5秒降到了0.5秒用户体验提升非常明显。

今天想记录一下这次性能调优的过程包括问题分析优化方案具体措施以及,优化效果希望能帮大家在移动端Web性能调优方面少走弯路。

一、问题背景

先说说问题的背景我们的应用是一个移动端Web应用(H5)主要在手机浏览器里运行也会被嵌入到App的WebView里使用用户量还不小每天有几十万的访问。

之前,应用在大部分手机上运行都还可以,虽然不是特别快,但是也能用用户反馈也不多,但是自从iPhone XR发布后越来越多的用户用iPhone XR访问我们的应用反馈说应用很卡响应很慢有时候点按钮要等好几秒才有反应甚至会卡死页面无响应。

我们一开始以为是网络问题,或者服务器问题,但是排查后发现网络和服务器都正常其他手机上也正常,只有iPhone XR上特别卡这就奇怪了iPhone XR是苹果的新机型性能应该很强为什么反而更卡呢?

于是我开始深入排查这个问题。

二、问题分析

首先,我需要复现问题我找了一台iPhone XR测试机打开我们的应用果然很卡页面加载慢点击响应慢滑动也不流畅和在其他手机上的体验差距很大。

然后我用Safari的开发者工具(Web Inspector)连接iPhone XR分析应用的性能看看到底是哪里慢。

通过分析我发现了几个主要的性能问题。

问题1:JavaScript执行时间过长

第一个问题是JavaScript执行时间过长页面加载的时候,有大量的JS代码在主线程执行导致页面长时间无响应特别是在iPhone XR上JS执行时间比其他手机长很多。

为什么iPhone XR上JS执行更慢呢?我查了一下发现iPhone XR用的是A12仿生芯片性能应该很强,但是Safari浏览器在某些JS操作上可能有性能问题,或者我们的代码里有一些在Safari上性能特别差的操作。

通过Profile分析我发现JS执行时间长的主要原因是:

  1. 大量的DOM操作:页面初始化的时候,有大量的DOM操作创建元素修改样式等等这些操作在Safari上比较慢。
  2. 同步的AJAX请求:有一些AJAX请求是同步的阻塞了JS执行导致页面卡住。
  3. 大量的循环和计算:有一些数据处理的逻辑用了很大的循环在主线程执行阻塞了UI。
  4. 没有使用requestAnimationFrame:动画和DOM更新没有,用requestAnimationFrame导致频繁的重排重绘性能差。

问题2:CSS动画和过渡性能差

第二个问题是CSS动画和过渡性能差我们的应用里有很多动画和过渡效果,比如页面切换弹窗出现下拉菜单等等这些动画在iPhone XR上很卡掉帧严重看起来很不流畅。

通过分析我发现这些动画性能差的原因是:

  1. 用了触发重排的属性做动画:很多动画用了topleftwidthheight等属性这些属性会触发重排(Layout)性能很差应该用transform和opacity做动画这两个属性只触发合成(Composite)性能好。
  2. 没有开启GPU加速:动画元素没有开启GPU加速导致动画在CPU上处理性能差应该用transform: translateZ(0)或者will-change开启GPU加速。
  3. 同时动画的元素太多:有些页面,同时有很多元素在做动画导致GPU负载过高掉帧严重。

问题3:图片加载和渲染慢

第三个问题是图片加载和渲染慢我们的应用里有很多图片特别是列表页有很多商品图片这些图片在iPhone XR上加载慢渲染也慢导致页面加载时间长滑动的时候,也会卡。

原因是:

  1. 图片太大:很多图片没有做压缩和裁剪原图直接用体积很大加载慢,而且iPhone XR的屏幕分辨率是828x1792像素比是2不需要太大的图片。
  2. 没有用WebP格式:图片都是JPEG和PNG格式没有用WebPWebP格式体积更小加载更快Safari在iOS 14才支持WebP(2018年的iOS 12还不支持)所以这个暂时用不了,但是可以用其他方式优化。
  3. 没有懒加载:所有图片都是页面加载的时候,一次性加载没有懒加载导致首屏加载慢流量浪费也大。
  4. 没有设置图片尺寸:图片没有设置width和height导致图片加载后页面重排(内容跳动)影响性能和用户体验。

问题4:内存占用过高

第四个问题是内存占用过高应用运行一段时间后内存占用越来越高最后Safari会,因为内存不足刷新页面,或者崩溃用户体验很差。

原因是:

  1. 内存泄漏:有一些事件监听器没有及时移除定时器没有,清理闭包引用没有释放导致内存泄漏内存占用越来越高。
  2. 图片缓存过多:列表页滑动加载了很多图片这些图片都在内存里没有释放导致内存占用过高。
  3. DOM节点过多:有些页面DOM节点特别多几千个甚至上万个导致内存占用高渲染也慢。

问题5:触摸事件响应慢

第五个问题是触摸事件响应慢用户点击按钮,或者链接要等一会儿才有反应感觉很迟钝特别是在iPhone XR上更明显。

原因是:

  1. 300ms点击延迟:虽然iOS Safari已经在很多情况下移除了300ms点击延迟,但是在某些情况下还是有,比如没有设置viewport的user-scalable=no或者页面有缩放等等。
  2. 触摸事件处理函数太重:触摸事件处理函数里有很多同步操作导致响应慢。
  3. 没有使用passive事件监听器:触摸事件(touchstarttouchmove等等)没有,用passive: true导致浏览器需要等待事件处理函数执行完才能确定是否阻止默认行为影响滚动性能。

三、优化方案

找到问题后我开始制定优化方案从几个方面入手全面优化应用的性能。

方案1:JavaScript性能优化

首先,优化JavaScript的性能减少JS执行时间避免阻塞主线程。

具体措施:

  1. 减少DOM操作批量更新:把多次DOM操作合并成一次,比如先把元素从DOM中移除修改完再插回去,或者用DocumentFragment批量创建元素减少重排次数。
  2. 把同步AJAX改成异步:所有同步的AJAX请求都改成异步的避免阻塞JS执行和,UI响应。
  3. 大数据处理放到Web Worker:大的数据处理计算放到Web Worker里执行不阻塞主线程保持UI响应。
  4. 用requestAnimationFrame做动画和DOM更新:所有动画和DOM更新都放到requestAnimationFrame里和浏览器刷新同步避免频繁重排重绘。
  5. 防抖和节流:scrollresizeinputtouchmove等高频事件都加防抖(debounce)和,节流(throttle)减少事件处理函数的执行次数。
  6. 代码分割和懒加载:JS代码按路由和模块分割首屏只加载需要的代码其他代码按需加载减少首屏JS体积和执行时间。
  7. 压缩和混淆JS:用Terser等工具压缩和,混淆JS代码减少体积加快下载和解析速度。

方案2:CSS动画和渲染优化

然后优化CSS动画和渲染性能让动画更流畅。

具体措施:

  1. 用transform和opacity做动画:所有动画都改用transform和,opacity属性不用topleftwidthheight等触发重排的属性提升动画性能。
  2. 开启GPU加速:对做动画的元素加transform: translateZ(0)或者will-change: transform开启GPU加速提升动画性能,但是不要滥用避免占用过多内存。
  3. 减少,同时动画的元素数量:优化页面设计减少,同时做动画的元素数量降低GPU负载避免掉帧。
  4. 避免重排重绘:减少会触发重排的操作,比如读取offsetTopscrollTop等属性要缓存不要在循环里频繁读取修改样式要批量修改不要逐个修改。
  5. 用contain属性:对独立的组件加contain: layout paint style限制重排重绘的范围提升渲染性能。

方案3:图片优化

然后优化图片加载和渲染性能。

具体措施:

  1. 压缩和裁剪图片:所有图片都做压缩和裁剪根据显示尺寸生成合适大小的图片不要用原图减少体积加快加载速度。
  2. 响应式图片:用srcset和sizes根据屏幕分辨率和,显示尺寸加载合适大小的图片避免在小屏上加载大图浪费流量和时间。
  3. 图片懒加载:首屏以下的图片都懒加载滚动到可视区域再加载减少首屏请求数和加载时间用Intersection Observer实现,或者用loading="lazy"原生属性(iOS Safari还不支持需要polyfill)。
  4. 设置图片尺寸:所有图片都设置width和height或者aspect-ratio避免图片加载后页面重排(内容跳动)提升用户体验和性能。
  5. 用CSS渐变和图标代替图片:一些简单的装饰性图片用CSS渐变,或者图标字体SVG代替减少HTTP请求和图片加载时间。
  6. 图片预加载:关键图片(比如首屏大图)用<link rel="preload">预加载让浏览器提前加载减少首屏时间。

方案4:内存优化

然后优化内存占用避免内存泄漏和内存过高。

具体措施:

  1. 及时清理事件监听器和定时器:组件销毁的时候,及时移除事件监听器清除定时器取消AJAX请求避免内存泄漏。
  2. 图片虚拟列表:长列表用虚拟滚动(Virtual Scroll)只渲染可视区域的DOM节点和,图片减少DOM数量和内存占用特别是图片多的列表效果明显。
  3. 图片卸载:列表滑动的时候,把不在可视区域的图片卸载(src设为空,或者占位图)释放内存避免内存占用过高。
  4. 减少DOM节点:优化页面结构减少不必要的DOM节点用更简洁的HTML结构减少内存占用和渲染时间。
  5. 避免闭包泄漏:注意闭包的使用避免闭包引用大的对象不释放导致内存泄漏。

方案5:触摸事件优化

然后优化触摸事件响应让点击和滑动更流畅。

具体措施:

  1. 设置viewport:在HTML的head里加<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">移除300ms点击延迟禁止页面缩放。
  2. 用passive事件监听器:touchstarttouchmove等触摸事件加passive: true让浏览器不用等待事件处理函数执行完就能滚动提升滚动性能。
  3. 优化触摸事件处理函数:触摸事件处理函数里不要做太多同步操作把重的操作放到setTimeout或者,requestAnimationFrame里异步执行避免阻塞响应。
  4. 用FastClick(如果需要):如果,还有300ms点击延迟的问题可以用FastClick库解决,但是现在大部分浏览器已经不需要了除非兼容老版本。

方案6:网络和加载优化

最后优化网络和加载性能让页面加载更快。

具体措施:

  1. 启用HTTP/2:服务器开启HTTP/2支持多路复用头部压缩提升加载速度。
  2. 启用gzip压缩:服务器开启gzip压缩文本资源(HTMLCSSJSJSON等等)减少体积加快传输。
  3. CDN加速:静态资源(JSCSS图片等等)放到CDN上让用户从最近的节点加载提升速度。
  4. 缓存策略:静态资源设置合理的缓存策略(Cache-ControlETag等等)减少重复下载提升二次访问速度。
  5. DNS预解析和预连接:对第三方域名用<link rel="dns-prefetch">和,<link rel="preconnect">提前解析DNS建立连接减少请求时间。
  6. 关键CSS内联:首屏需要的CSS内联到HTML里减少首屏时间非关键CSS异步加载。
  7. JS放底部或defer:JS放页面底部,或者加defer避免阻塞页面渲染。

四、优化效果

经过以上几个方面的优化我们的应用在iPhone XR上的性能提升非常明显我做了对比测试结果如下:

指标优化前优化后提升
首屏加载时间3.2秒0.8秒75%
页面响应时间(点击到反馈)2.5秒0.5秒80%
滑动帧率30fps55fps83%
内存占用(峰值)450MB180MB60%
页面崩溃率5.2%0.3%94%

可以看到各项指标都有很大的提升特别是页面响应时间降低了80%用户点击按钮几乎立即就有反馈体验非常流畅滑动帧率也从30fps提升到了55fps接近60fps的流畅标准内存占用也降低了60%页面崩溃率几乎为零用户体验提升非常明显。

用户反馈也很好之前,抱怨卡慢的用户都说现在很流畅很好用用户满意度提升了很多我们也收到了很多好评这次性能调优非常成功。

五、经验教训

通过这次性能调优我总结了一些移动端Web性能调优的经验和教训分享给大家。

教训1:不要忽视移动端性能特别是iOS Safari

很多前端开发者在开发的时候,主要在电脑浏览器上测试觉得电脑上流畅就没问题,但是移动端的性能和电脑差距很大特别是iOS Safari有很多自己的特性和坑和Chrome不一样,所以一定要在真实的手机上测试特别是iPhone的Safari不能只在电脑上测试。

而且新机型不代表性能一定更好有时候新机型的屏幕分辨率更高,或者浏览器有新的特性可能会暴露一些之前,没有的性能问题,所以新机型发布后要及时测试适配。

教训2:性能优化要从一开始就做不要等出问题再优化

这次性能问题其实是可以避免的,如果我们在开发的时候,就注意性能最佳实践,比如用transform做动画图片懒加载减少DOM操作等等就不会积累这么多性能问题等到用户反馈才来优化成本很高也影响了用户体验。

所以性能优化要从项目一开始就做融入到日常开发中而不是等到出了问题才来救火要建立性能预算(Performance Budget)和监控机制持续关注性能避免性能退化。

教训3:性能优化要数据驱动不要凭感觉

性能优化不能凭感觉要用数据说话先测量找到瓶颈再针对性优化优化后再测量验证效果不要盲目优化做无用功。

这次优化我就是先用Safari的开发者工具Profile分析找到具体的性能瓶颈,然后针对性优化每个优化都有数据支撑优化后也有数据验证效果这样效率很高也不会走弯路。

教训4:移动端性能优化要全面不能只优化一个方面

移动端性能问题往往是多个方面共同导致的JSCSS图片内存网络等等都可能有问题,所以性能优化要全面不能只优化一个方面,比如只优化JS不优化图片效果可能就不明显要从多个维度综合优化才能达到最好的效果。

这次优化我就是从JSCSS图片内存触摸事件网络六个方面综合优化才取得了这么好的效果,如果只优化一个方面肯定达不到80%的提升。

教训5:兼容性很重要特别是iOS的兼容性

移动端Web开发兼容性很重要特别是iOS的兼容性iOS Safari有很多和其他浏览器不一样的地方也有很多坑,比如WebP支持(iOS 14才支持)loading="lazy"支持(iOS 15.4才支持)Intersection Observer支持(iOS 12.2支持)等等这些兼容性问题都需要注意做好降级和polyfill避免在老版本iOS上出问题。

这次优化我就注意了兼容性,比如图片懒加载用了Intersection Observer但是加了polyfill兼容老版本浏览器WebP因为iOS 12不支持,所以暂时没用等等确保优化在所有版本的浏览器上都能正常工作。

六、写在最后

以上就是这次iPhone XR性能调优的完整记录包括问题背景问题分析优化方案优化效果以及,经验教训。

这次性能调优让我对移动端Web性能优化有了更深入的理解也积累了很多实战经验最终把响应时间降低了80%用户体验提升非常明显很有成就感。

移动端Web性能优化是一个系统工程需要从JSCSS图片内存网络等多个维度综合优化也需要持续关注和优化不是,一次性的工作希望我的这次经验能帮大家在移动端性能调优方面少走弯路做出更流畅的移动端Web应用。

最后用一句话结束这篇文章:"性能优化没有最好,只有更好持续测量持续优化才能给用户最好的体验移动端性能尤其重要,因为手机的性能和网络都有限更需要我们精心优化。"

愿大家的移动端应用都能又快又流畅用户体验棒棒的。