聊一聊

前端性能与体验的优化


分享By 吴俊杰

时间 2020-12-30

目录

一、调试工具

二、WEB API

三、雅虎军规

四、资源压缩

五、webpack优化

六、骨架屏

七、窗口化

八、缓存

九、预/懒加载

十、体验优化


一、调试工具
磨刀不误砍柴工

1、Network

这里可以看到资源加载详情,初步评估影响页面性能的因素。鼠标右键可以自定义选项卡,页面底部是当前加载资源的一个概览。DOMContentLoadedDOM渲染完成的时间,Load:当前页面所有资源加载完成的时间

Down arrow

思考:如何判断哪些资源对当前页面加载?

shift + cmd + P 调出控制台的扩展工具,添加规则
Down arrow

瀑布流waterfall

资源加载的一个过程


  • Queueing 浏览器将资源放入队列时间
  • Stalled 因放入队列时间而发生的停滞时间
  • DNS Lookup DNS解析时间
  • Initial connection 建立HTTP连接的时间
  • SSL 浏览器与服务器建立安全性连接的时间
  • TTFB 等待服务端返回数据的时间
  • Content Download 浏览器下载资源的时间

2、Lighthouse

根据chrome的一些策略自动对网站做一个质量评估,并且会给出一些优化的建议。First Contentful Paint 首屏渲染时间,1s以内绿色Speed Index 速度指数,4s以内绿色Time to Interactive 到页面可交换的时间

Down arrow

3、Peformance

对网站最专业的分析~后面会多次讲到

Down arrow

4、webPageTest

可以模拟不同场景下访问的情况,比如模拟不同浏览器、不同国家等等,在线测试地址:webPageTest

Down arrow Down arrow

5、打包资源分析

使用 webpack-bundle-analyzer 或者 开启 source-map 使用source-map-explorer 'build/*.js

Down arrow Down arrow
二、WEB API
工欲善其事,必先利其器。浏览器提供的一些分析API 至关重要

大家应该刷过慕课,只要离开窗口视频就会暂停~

或者一些考试网站,提醒你不能离开当前窗口

再或者,这种效果~

1、窗口激活状态监听


							let vEvent = 'visibilitychange';
							if (document.webkitHidden != undefined) {
								vEvent = 'webkitvisibilitychange';
							}
							function visibilityChanged() {
								if (document.hidden || document.webkitHidden) {
									document.title = '客官,别走啊~'
								} else {
									document.title = '客官,你又回来了呢~'
								}
							}
							document.addEventListener(vEvent,visibilityChanged,false)
						

2、监听网络变化


              const connection = navigator.connection 
                              || navigator.mozConnection 
                              || navigator.webkitConnection;
              function changedStatus() {
                type = connection.effectiveType;
                console.log("changed from "+ type + " to " + type);
              }
              connection.addEventListener('change', changedStatus);
						

3、计算DOMContentLoaded


              window.addEventListener('DOMContentLoaded', 
              (event) => {
                let time = performance.getEntriesByType('navigation')[0]
                console.log(time.domInteractive);
                console.log(time.fetchStart);
                let diff = time.domInteractive - time.fetchStart;
                console.log("TTI: " + diff);
              })
						

4、更多计算规则:


              DNS 解析耗时: domainLookupEnd - domainLookupStart
              TCP 连接耗时: connectEnd - connectStart
              SSL 安全连接耗时: connectEnd - secureConnectionStart
              TTFB 网络请求耗时: responseStart - requestStart
              数据传输耗时: responseEnd - responseStart
              DOM 解析耗时: domInteractive - responseEnd
              资源加载耗时: loadEventStart - domContentLoadedEventEnd
              白屏时间: responseEnd - fetchStart
              首次可交互时间: domInteractive - fetchStart
              DOM Ready 时间: domContentLoadEventEnd - fetchStart
              Load 页面完全加载时间: loadEventStart - fetchStart
              http 头部大小: transferSize - encodedBodySize
              重定向次数:performance.navigation.redirectCount
              重定向耗时: redirectEnd - redirectStart
						
三、雅虎军规
老生常谈,但是又不得不谈

              let cards = document.getElementsByClassName("MuiPaper-rounded");
              const update = (timestamp) => {
                for (let i = 0; i < cards.length; i++) {
                  let top = cards[i].offsetTop;
                  cards[i].style.width = ((Math.sin(cards[i].offsetTop 
                  + timestamp / 100 + 1) * 500) + 'px')
                }
                window.requestAnimationFrame(update)
              }
              update(1000);
            

              let cards = document.getElementsByClassName("MuiPaper-rounded");
                const update = (timestamp) => {
                  for (let i = 0; i < cards.length; i++) {
                    fastdom.measure(() => {
                      let top = cards[i].offsetTop;
                      fastdom.mutate(() => {
                        cards[i].style.width = Math.sin(top 
                        + timestamp / 100 + 1) * 500 + "px";
                      });
                    });
                  }
                  window.requestAnimationFrame(update)
                }
                update(1000);
            

Performance 分析结果对比

四、资源压缩

1、GZIP

  • 修改Nginx配置
  • 借助 Compression-webpack-plugin来生成 .gz 文件
  • Node Compress

              var compression = require('compression')
              var app = express();
              //尽量在其他中间件前使用compression
              app.use(compression());
            

2、JavaScript、Css、Html压缩

压缩原理 简单的讲就是去除一些空格、换行、注释,借助es6模块化的功能,做了一些tree-shaking 的优化。同时做了一些代码混淆,一方面是为了更小的体积,另一方面也是为了源码的安全性。
  • UglifyJS
  • webpack-parallel-uglify-plugin
  • terser-webpack-plugin

3、http2首部压缩

http2除了首部压缩,还有一些其他的特性
  • 二进制分帧
  • 首部压缩
  • 流量控制
  • 多路复用
  • 请求优先级
  • 服务器推送http2_push: 'xxx.jpg'
五、webpack优化
上文中也提到了部分webpack插件,下面再举几个例子~

1、DllPlugin 提升构建速度

通过DllPlugin 插件,将一些比较大的,基本很少升级的包拆分出来,生成xx.dll.js文件,通过manifest.json引用

              const path = require("path");
              const webpack = require("webpack");
              module.exports = {
                  mode: "production",
                  entry: {
                      react: ["react", "react-dom"],
                  },
                  output: {
                      filename: "[name].dll.js",
                      path: path.resolve(__dirname, "dll"),
                      library: "[name]"
                  },
                  plugins: [
                      new webpack.DllPlugin({
                          name: "[name]",
                          path: path.resolve(__dirname, "dll/[name].manifest.json")
                      })
                  ]
              };
            

2、splitChunks 拆包


              optimization: {
                splitChunks: {
                    cacheGroups: {
                        vendor: {
                            name: 'vendor',
                            test: /[\\/]node_modules[\\/]/,
                            minSize: 0,
                            minChunks: 1,
                            priority: 10,
                            chunks: 'initial'
                        },
                        common: {
                            name: 'common',
                            test: /[\\/]src[\\/]/,
                            chunks: 'all',
                            minSize: 0,
                            minChunks: 2
                        }
                    }
                }
            },
            
六、骨架屏
用css提前占好位置,当资源加载完成即可填充,减少页面的回流与重绘,同时还能给用户最直接的反馈。
图中使用插件:react-placeholder
七、窗口化
原理:只加载当前窗口能显示的DOM元素。
图中使用插件:react-window
八、缓存

http缓存

利用好浏览器策略以及Http提供的相关参数,可以有效地减少带宽的浪费以及页面加载的时间

1
2
3
4
keep-alive

                  # 0 为关闭
                  # keepalive_timeout 0;
                  # 65s无连接 关闭
                  keepalive_timeout 65;
                  # 连接数,达到100断开
                  keepalive_requests 100
                
Cache-Control / Expires / Max-Age
Last-Modified / If-Modified-Since
Etag / If-None-Match

http缓存

Service Worker

借助webpack插件WorkboxWebpackPlugin和ManifestPlugin,加载serviceWorker.js,通过serviceWorker.register()注册

            new WorkboxWebpackPlugin.GenerateSW({
              clientsClaim: true,
              exclude: [/\.map$/, /asset-manifest\.json$/],
              importWorkboxFrom: 'cdn',
              navigateFallback: paths.publicUrlOrPath + 'index.html',
              navigateFallbackBlacklist: [
                  new RegExp('^/_'),
                  new RegExp('/[^/?]+\\.[^/]+$'),
              ],
            }),
            
            new ManifestPlugin({
                fileName: 'asset-manifest.json',
                publicPath: paths.publicUrlOrPath,
                generate: (seed, files, entrypoints) => {
                    const manifestFiles = files.reduce(
                      (manifest, file) => {
                        manifest[file.name] = file.path;
                        return manifest;
                    }, seed);
                    const entrypointFiles = entrypoints.app.filter(
                        fileName => !fileName.endsWith('.map')
                    );
            
                    return {
                        files: manifestFiles,
                        entrypoints: entrypointFiles,
                    };
                },
            }),
            
九、预/懒加载
正常加载
给对应资源添加preload

                <`link rel="preload" href="xxx" as="font" crossorigin="anonymous"/>
              
给对应资源添加prefetch

                <`link rel="prefetch" href="xxx" as="font" crossorigin="anonymous"/>
              
两种图片:机械图片 and 渐进式图片(类似高斯模糊)
响应式图片

              <`img src="index.jpg" sizes="100vw" srcset="./img/dog.jpg 800w, ./img/index.jpg 1200w"/>
            
十、体验优化
白屏加载Loading

THE END