验证码前端性能分析及优化实践

  • 模块化发展的中期,require.js和sea.js占据了模块化解决方案的半壁江山,这两个方案都是对模块化很好的实践。验证码将采用Node.js模块系统的CommonJS规范,借助webpack4将模块打包到一起。最终验证码工程中模块引用及导出相关的代码非常简单:
  • const module1 = require('./module1');
  • // ...
  • function Captcha() {}
  • // ...
  • module.exports = Captcha;
  • 模块化是资源合并的基础,这里的模块化不仅是将业务逻辑分模块加载,也包含了CSS、Icon等样式和图标。只有把所有资源均纳入到模块化管理中,配合webpack不同的loader,才能更细粒度地控制合并资源。
  • 3.2 DOM操作优化
  • DOM操作是整个JavaScript执行周期中最耗时的操作之一,不合理的DOM操作不仅会触发页面大量的重排和重绘,还有可能导致页面假死。良好的DOM组织结构应该遵循不需要大量重排(Reflow),DOM修改尽量用重绘(Repaint)代替的原则。
  • 验证码前端性能分析及优化实践


    • 图3.3 重绘&重排
    • 重绘是指一个元素的外观被改变,但布局没有发生变化,包括改变visibility、outline、background等属性;而重排是DOM的变化影响了元素的几何属性,浏览器会重新计算元素的几何属性,使渲染树中受到影响的部分失效,会验证DOM树上的所有其它节点的visibility属性,十分低效,这类操作包括改变窗口大小、文字大小、内容,style属性等。
    • 本次重构针对旧版工程大量不合理的DOM操作进行优化,采取的解决方案包括:
    • 最小化重绘和重排
    • 重新合理组织了DOM结构,将DOM的多样化用CSS类的方式表示,尽量控制DOM的显示或隐藏,避免添加或删除,对于一些要修改的DOM结构,采用修改className的方式,而不是多次读写style,避免使用js修复布局,减小DOM操作对页面渲染造成的影响。
    • 缓存DOM元素
    • 如果某个节点将在后续进行多次操作,则将其利用变量存储起来,而不是每次进行操作时都去查找一遍该节点。
    • 消灭定时器
    • 对于一些动画效果的实现,避免使用setTimeout或setInterval,而是用requestAnimationFrame代替
    • 3.3 资源合并打包
    • js脚本的加载和执行会阻塞HTML Parser,本次优化本着"all in one"的原则,运用组件化思想,将复杂的逻辑抽象成多个独立的模块,把前端代码拆分成多个小文件,利用webpack灵活地处理模块依赖、提取公共模块最终合成业务所需代码。通过webpack-bundle-analyzer插件将打包后的内容束展示为方便交互的直观树状图,如图3.4所示:
    验证码前端性能分析及优化实践


    • 图3.4 验证码项目结构
    • 滑动验证码支持20多个国家的23种语言,文案数多达30余条,因此语言包的体积占了整个工程的很大比例。Zepto有很多模块,通常为了使引用的zepto库尽可能小,会根据项目的实际需要打包所需的模块进去,验证码项目只需用到事件处理、网络请求和一些简单的动画效果,因此我们对zepto、event、ajax和fx(动画模块)四个模块进行了打包处理,生成本项目特有的zepto库。整个项目结构中占用体积较大的js来自多语言包和zepto库,其余都是体积可忽略不计的小组件。
    • 通过webpack对js的模块化管理,整个项目的模块结构变得十分清晰,方便维护和重构。Js资源文件的数量由10余个减小为1个,体积也得到了很好的控制,减少了HTTP请求数量并且没有增加带宽消耗。
    • 3.4 图片、样式内联
    • 验证码中的图片均为Icon,特点是体积小但数量较多,合并成雪碧图后还是需要占用几十KB的空间,并且加载雪碧图需要一次额外的HTTP请求。针对验证码小图标众多这一特性,我们将这些小图标base64编码到css文件中,减少HTTP请求数量,并且并不会占用很大的空间。
    • test: /\.(png|jpg|gif)$/,
    • use: [{
    • loader: 'url-loader',
    • options: {
    • limit: 8192
    • }
    • }]
    • 在工程中添加上述webpack配置,采用url-loader插件自动对小于8KB的小图标进行DataURL处理,直接把图片资源内嵌到页面中提供给浏览器解析,这样就可以不用发送HTTP请求,提升资源文件加载速度的同时也节省了带宽资源。
    • 验证码的DOM结构并不算太复杂,并且经过对SCSS的模块化处理,使用PostCSS生态和cssnext来支持自定义变量、样式内嵌等特性,最终编译生成的CSS文件已经很简洁,体积只有十几KB,因此我们考虑不再将其作为一个单独的资源文件引用,而是直接将其inline到html中,在html的head标签中添加如下style配置:
    • webpack的compilation对象继承于compiler,能获取一切编译后的内容,因此可以直接把打包生成的css样式插入html,减少了额外的HTTP请求,并且能避免偶发的CDN资源加载失败导致的页面显示异常。
    • 4. 移动端适配
    • 4.1 引入flexible.js
    • flexible.js是一个开源的用于终端设备的适配解决方案,主要用于解决各种不同尺寸移动设备的大小自适应问题,其原理是通过移动设备的dpr(设备像素比=物理像素/设备独立像素)和屏幕宽度来动态改变html的font-size大小。因此我们选择flexible.js用于验证码的页面适配,在页面引入flexible.js后,首先获取设备型号,然后根据不同设备在标签上添加一个data-dpr和font-size样式,并结合我们的项目对其进行改进,获得更加完美的兼容效果。
    • 4.2 rem自动换算
    • 下面是验证码页面的缩放配置,其中,baseDpr表示基准的dpr值为1,rem单位以375宽度的屏幕为基准,即1rem==37.5px,并提供'!px'和'!no'两种特殊的单位转换方式。
    • // px2rem
    • const px2remConfigs = {
    • baseDpr: 1,
    • remUnit: 37.5,
    • forcePxComment: '!px',
    • keepComment: '!no'
    • };
    • 通过postcss-loader的px2rem插件将原生scss文件中所有用px单位表示的大小自动编译转换成能够自适应于各屏幕大小的rem值。同时,会对px后面带'!px'和'!no'后缀的情况做特殊处理,在px后面添加'!px'表示根据dpr设置成不同的rem值,一般字体用该单位修饰;在px后面添加'!no'表示不转化px直接原样输出,一般boder边框用该单位修饰。
    • loader: "postcss-loader",
    • options: {
    • plugins: [
    • autoprefixer, // 自动添加前缀
    • px2rem(px2remConfigs)
    • ]
    • }
    • 针对一些元素尺寸、位置等需要动态变化的情况,比如横竖屏切换时iframe整体大小需要自适应,再比如每刷新一次,小拼图的宽、高、top值都要重新计算,此时只需要注册相应的回调函数,在回调函数内进行相应的逻辑处理即可。flexible检测到resize、pageshow或其它调用refreshRem方法的时候,会回调在验证码内注册的回调数组(resizeCb)中的所有方法。
    • listen: function(order, cb) {
    • if(flexible.resizeCb) {
    • flexible.resizeCb.splice(order, 0, cb);
    • }
    • }
    • flexible.listen(0, function() {...};
    • flexible.listen(1, function() {...};
    • flexible.listen(2, function() {...};
    • ...
    • if(flexible.resizeCb) {
    • for(var i=0; i < flexible.resizeCb.length; i++) {
    • try{
    • flexible.resizeCb[i] && flexible.resizeCb[i]();
    • } catch(e) {}
    • }
    • }
    • 4.3 iframe内缩放问题
    • 验证码作为一个web组件提供给业务使用,在iframe内部默认不设置视口(viewport),在dpr大于1的时候整个iframe会被压缩成1/dpr,如图4.1左侧所示。因此需要根据业务页面设置的viewport来设置缩放比例,通过获取父页面的scale值并将其透传到iframe内部的方式来设置正确的缩放比例,最终得到如图4.1右侧所示的效果。
    验证码前端性能分析及优化实践


    • 图4.1 iframe内缩放问题
    • 4.4 webview内适配问题
    • 虽然flexible能比较完美地适配移动端页面,然而在一些特殊的安卓机器中仍然会存在很诡异的适配问题,如图4.2所示:
    验证码前端性能分析及优化实践


    • 图4.2 webview内适配问题
    • 产生这种情况的原因是安卓部分webview修改了默认字体,使得最终显示的1rem的px值和设置的值不一致,导致页面显示异常。正常情况下,rem是根据html的最终font-size进行响应的,并且对于大部分机型,设置的值和最终的响应值是相等的,即:
    • 1rem == finalDocElementFontSize == docElementFontSize
    • 然而在小米MAX和荣耀8等机型中,最终的响应值要大于设置的值,导致以rem为单位的DOM元素都显示过大,就会出现图4.2中小拼图缺口大小不匹配、图片超出屏幕区域的异常情况。
    验证码前端性能分析及优化实践


    • 表4.1 优化前后采集数据对比
    • 如表4.1所示,其中,优化前所在行是我们采集上来的异常数据,可以明显看到,真实的1rem对应的px值要远大于设置的值,此时设定一个阈值,当设置值和最终响应值差值大于0.5时,重新计算fontsize大小:
    • try {
    • var finalDocElementFontSize = parseFloat(getComputedStyle(docEl).fontSize);
    • if(Math.abs(finalDocElementFontSize - docElementFontSize) > 0.5) {
    • var delta = finalDocElementFontSize / docElementFontSize;
    • docEl.style.fontSize = (docElementFontSize / delta) + 'px';
    • }
    • } catch(e) {}
    • 得到矫正后的优化后数据,使得最终的响应值接近我们最初想要设置的值,页面就能达到如图4.3所示完美的适配效果:
    验证码前端性能分析及优化实践


    • 图4.3 优化后适配效果
    • 5. 提升用户预期
    • 图5.1是旧版验证码的加载流程,白屏时间接近2s,并且整个过程衔接得并不自然。
    验证码前端性能分析及优化实践


    • 图5.1 旧验证码加载流程
    • 因此我们在重构中引入了Skeleton Screen(加载占位图),在验证码加载预期填充灰色的占位图,实现界面加载过程中的平滑过渡效果。这个概念来源于iOS设计规范中的Lanuch Screen(启动屏幕),主要目的是为了解决等待加载过程中出现白屏或界面闪烁造成的割裂感。
    验证码前端性能分析及优化实践


    • 图5.2 骨架屏
    • 实现的效果如图5.2所示,用占位图展示验证码的大致骨架,大大提升用户对验证码加载的预期。最终加载流程如图5.3所示。
    验证码前端性能分析及优化实践


    • 图5.3 新验证码加载流程
    • 加载占位图的显示不依赖任何页面外部资源,在验证码的HTML加载完成之前就可以显示出验证码的大致轮廓,增加用户的等待预期并减少长时间白屏带来的焦躁情绪,用户体验得以提升。
    • 6. 优化效果及总结
    • 经过本轮重构以后,验证码整体性能得到了大幅提升。其中,验证码体积减少了38%,http资源请求数由10余个减少为2个。引入骨架屏有效缩短了白屏时间,最耗时的DOM渲染和验证码主逻辑执行时间也分别减少了50%和60%。安卓下全流程加载平均耗时从3.9s减少为1.9s,降低了51%;ios下从3s减少为1.7s,降低了43%。
    • 本次移动端验证码重构是对前端性能优化的一次完整实践,模块化、资源合并打包、按需缓存、代码压缩等前端的优化思路,在基础前端产品中应用后效果尤为显著,优化后的验证码很大程度上减少了网络链路的开销。并且重构过程中梳理了验证码的加载流程,删减了流程中不合理的异步等待,最终的主逻辑代码中已经没有定时器的影子,这再一次证明了前端通用优化思路的可行性。
    • 一个项目存在历史包袱在所难免,随着各种新技术层出不穷,一些老旧的思想和方式势必被更好的实践替代,性能优化是值得前端从事者深入研究的领域,只有不断实践通用优化方法和探索最佳实践标准,才能提升用户体验从而为业务创造价值。为了用户体验,我们一直在努力。
    • 我们将对各类技术进行持续的探索与研究,为业务提供优质的安全解决方案以及威胁情报感知服务。如果对相关技术及业务安全感兴趣,欢迎访问007.qq.com,或者关注微信公众号了解更多。


    分享到:


    相關文章: