总结篇

性能优化--1.缓存

性能优化--2.网络篇

性能优化--3.渲染篇

性能优化--4.应用篇

性能优化

  1. html css js img 最简单的压缩合并 YUI Compressor tinyPNG https://zh.recompressor.com/

  2. 雅虎军规

  3. 服务器端的:etag gzip

  4. 第三方库代码:资源压缩 强缓etag jq vue

  5. 渐进式图片加载 首先加载低质量或模糊的图像,然后在页面继续加载时,使用 Guy Podjarny 提出的 LQIP (Low Quality Image Placeholders) technique(低质量图像占位符)技术替换它们的清晰版本

  6. 业务代码:大容量的解决方案 -> 异步

    • html css js -> 数据库里 h5页面localstorage扩容(postmessage)
    • 将本地资源已key:value形式存入localstorage,请求文件前先对比请求的资源和相应的key是否匹配,匹配的话就读取激活对应的文件,否则再去请求资源
  7. web本地存储 -localStorage 5M内存,but超过2.5M某些机型会卡顿

    • localStorage扩容 5M的限制是针对同域的,可以利用iframe跨域扩容localStorage(postmessage),不过这种就是异步的了
    • webSQL关系型
    • indexedDB非关系型 是webSQL的继承者更加好用
    • localforage 为你解决一切,针对浏览器做降级处理本地存储
    • manifest 更新是个坑
    • service worker 拦截掉本地所有网络请求 //需要编写一定量的代码
  8. 移动端测试网速方案(根据网速判断是加载二倍图还是三杯图)

    • navigator.connection
    • 服务端放个1*1图片 客户端new Image() 测时差
      let img = new Image();
          const size = 1
              img.src = ''     // 假如是个1k的图片
          let start = Date.now()
              img.onLoad = function() {
                  let end = Date.now()
                  let speed = size / (end - start) / 1000   网速 
                  if( )
              }
      
  9. cdn 1.找到离你最近

  10. 缓存静态资源

  11. 不用带那么多cookie

  12. 并发数量有限制 不放一个cdn

  13. dns预解析 dns-prefetch

DNS 请求需要的带宽非常小,但是延迟却有点高,这点在手机网络上特别明显。预读取 DNS 能让延迟明显减少一些,例如用户点击链接时。在某些情况下,延迟能减少一秒钟。

在某些浏览器中这个预读取的行为将会与页面实际内容并行发生(而不是串行)。正因如此,某些高延迟的域名的解析过程才不会卡住资源的加载。

这样可以极大的加速(尤其是移动网络环境下)页面的加载。在某些图片较多的页面中,在发起图片加载请求之前预先把域名解析好将会有至少 5% 的图片加载速度提升 <meta http-equiv="x-dns-prefetch-control" content="on"> <link href='j1.58cdn.com' rel='dns-prefetch'>

  1. preconnect预链接 浏览器要建立一个连接,一般需要经过DNS查找,TCP三次握手和TLS协商(如果是https的话),这些过程都是需要相当的耗时的,所以preconnect,就是一项使浏览器能够预先建立一个连接,等真正需要加载资源的时候就能够直接请求了。

    <link rel="preconnect" href="//example.com">
    <link rel="preconnect" href="//cdn.example.com" crossorigin>
    
  2. /prefetch/preload/

  3. 测网速 判断 然后 2倍图3倍图 去掉无用功能 只留核心组件

webpack优化部分 构建优化部分

压缩JS(去掉无效代码、去掉日志输出代码、缩短变量名等优化。)

- **uglifyjs-webpack-plugin** or **new webpack.optimize.UglifyJsPlugin** 
- **webpack-parallel-uglify-plugin** 多进程压缩文件,类似happyPack

 ```
 const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
   new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
 ```

压缩CSS css-loader已经内置开启 minimize

```
const ExtractTextPlugin = require('extract-text-webpack-plugin');
rules: [
  {
    test: /\.css/,
    // 提取出 Chunk 中的 CSS 代码到单独的文件中
    use: ExtractTextPlugin.extract({
      // 通过 minimize 选项压缩 CSS 代码
      use: ['css-loader?minimize']
    }),
  },
]
```

缩小文件搜索范围 构建时优化

-  配置**loader 缓存** 以及**include exclude**
```
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
        use: ['babel-loader?cacheDirectory'],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
    ]
```
- 配置 **resolve.modules**(resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。)

```
module.exports = {
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    modules: [path.resolve(__dirname, 'node_modules')]
  },
};
```
- 配置**resolve.mainFields** 字段 (慎用 除非你知道你用的所有第三方模块的入口文件描述)

```
    第三方包可能有根据不同的运行环境有多个入口文件
    为了减少搜索步骤,在你明确第三方模块的入口文件描述字段时,你可以把它设置的尽量少。
  resolve: {
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['main'],
    },
```
- 配置 **resove.alias**

```
  resolve: {
    // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,
    // 减少耗时的递归解析操作
    alias: {
    'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
}

}, ``` - 配置 resolve.extensions (配置后缀默认 ['.js','.json']) 尽可能较少默认后缀 频繁使用的放前面 等

externals(外部扩展)从代码中拆公共模块,走cdn

```
  externals: {  
     'vue': 'Vue',  
     'vue-router': 'VueRouter',  
     'axios': 'axios'  
   }
```

cdn加速

业界比较成熟的做法:
- 针对HTML 不开启缓存,放到服务器上而不是CDN,同时关闭自己服务器上的缓存,服务器会提供HTML和接口数据
- 针对静态JS CSS IMAGE等,开启CDN和缓存,同时文件内容带上hash
- 同时HTML进入资源地址也要改成CDN的地址 带上hash 之前的相对路径都改成了指向CDN服务的结对路径
- 同一时刻针对同一个域名的资源并行请求是有限制的话,所以会把js css img 分别放到3个cdn下 js.cdn.com css.cdn.com img.cdn.com
- 当然多域名 又会增加域名解析时间。 可以通过 **CDN预解析** 较少解析带来的延迟
webpack 接入cdn

```
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {WebPlugin} = require('web-webpack-plugin');
module.exports = {

... output: { // 给输出的 JavaScript 文件名称加上 Hash 值 filename: '[name][chunkhash:8].js', path: path.resolve(__dirname, './dist'), // 指定存放 JavaScript 文件的 CDN 目录 URL publicPath: '//js.cdn.com/id/', }, module: { rules: [ { // 增加对 CSS 文件的支持 test: /.css/, // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ // 压缩 CSS 代码 use: ['css-loader?minimize'], // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL publicPath : '//img.cdn.com/id/' }), }, { // 增加对 PNG 文件的支持 test: /.png/, // 给输出的 PNG 文件名称加上 Hash 值 use: ['file-loader?name=[name][hash:8].[ext]'], }, ... ] }, plugins: [ new WebPlugin({ template: './template.html', filename: 'index.html', // 指定存放 CSS 文件的 CDN 目录 URL stylePublicPath: '//css.cdn.com/id/', }), new ExtractTextPlugin({ // 给输出的 CSS 文件名称加上 Hash 值 filename: [name]_[contenthash:8].css, }), //keep module.id stable when vendor modules does not change new webpack.HashedModuleIdsPlugin(), ], };

```
我一般是通过loader来配置图片的cdn

```
区分环境 生产环境下加载cdn图片,并且启用图片压缩,开发环境下加载本地图片
const ISPROD = process.env.NODE_ENV === 'production'
const IMGURL = '[name].[ext]'
const IMAGELOADER = ISPROD ? `image-webpack-loader?{mozjpeg: {progressive: true,quality: 65},pngquant:{quality: "55-80", speed: 4}}` : null
const IMG_PUBLICPATH = '//img.58cdn.com.cn/crop/baseteam/yunying/pc/gzpp/img/'
const IMG_QUERY = ISPROD ? `&name=${IMGURL}&outputPath=/img&publicPath=${IMG_PUBLICPATH}` : `&name=${DEVIMGURL}`
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    use: [
        `url-loader?limit=10000${IMG_QUERY}`,
        IMAGELOADER,
    ]
},
```

图片压缩 示例如上 image-webpack-plugin 或者 imagemin-webpack-plugin

cdn 预解析 加上 dns-prefetch

dll 动态链接库 DllPlugin和DllReferencePlugin 可以搭配 html-webpack-include-assets-plugin 来动态在模板上引入dll

- 区别 CommonsChunkPlugin:webpack每次打包实际还是会处理这些第三方库,但是打包之后能和第三方库和我们业务代码分开,DLLPlugin 可提前把第三方库完全分开,每次只打包自己业务代码
- 区别 externals:

提取公共代码CommonsChunkPlugin

一般策略是 
1. 第三方基本库放到一个js中(为了缓存)
2. 剔除第三方基本库公共部分放一个js
3. 每个页面自己的js

tree shaking

- 第三方采用的commonJS是无效的 因为 ES6 模块化语法是静态的(导入导出语句中的路径必须是静态的字符串,而且不能放入其它代码块中),这让 Webpack 可以简单的分析出哪些 export 的被 import 过了。 如果你采用 ES5 中的模块化,例如 module.export={...}、require(x+y)、if(x){require('./util')},Webpack 无法分析出哪些代码可以剔除。
- 一般第三方库会有两个入口文件

```
如redux package.json
{
    "main": "lib/index.js", // 指明采用 CommonJS 模块化的代码入口
    "jsnext:main": "es/index.js" // 指明采用 ES6 模块化的代码入口 (社区约定)
}
1. webpack约定优先入口文件
resolve: {
    mainFields: ['jsnext:main', 'browser', 'main']
},
2. 修改.babelrc 让其保留 ES6 模块化语句
{
    "presets": [
    [
        "env",
    {
        "modules": false //关闭babel模块转化功能
    }
    ]
  ]
}
3. webpack只是指出那些函数没用上,还要通过UglifyJS 去剔除掉无用的代码
```

启用 scope hoisting 作用域提升 new webpack.optimize.ModuleConcatenationPlugin()

好处
- 代码体积更小,因为函数申明语句会产生大量代码;
- 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。
- 前提: **tree shake**一样源码必须采用 ES6 模块化语句和 
- [scope hoisting原理](#scopehositing)
	- 分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。  
module.exports = {
  resolve: {
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(),
  ],
};

prepack(优化代码运行时的效率 目前技术不成熟,慎用)

- 在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的 JavaScript 代码。 实际上 Prepack 就是一个部分求值器,编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值。 
- 通过在编译阶段预先执行了源码得到执行结果 prepack-webpack-plugin
- [prepack运行原理](#prepack) 
	- 通过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码;
	- Prepack 实现了一个 JavaScript 解释器,用于执行源码。借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程中的结果返回到输出中。
- prepack目前技术不成熟 慎用

happypack

- 构建时优化,任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
- [hanppyPack原理]():把 Loader 对文件的转换操作部分任务分解到多个进程去并行处理,从而减少了总的构建时间。把 Loader 处理的文件都先交给了 happypack/loader 去处理,收集到了这些文件的处理权后 HappyPack 就好统一分配了。
	1. 每通过 new HappyPack() 实例化一个 HappyPack 其实就是告诉 HappyPack 核心调度器如何通过一系列 Loader 去转换一类文件,并且可以指定如何给这类转换操作分配子进程。

	2. 核心调度器的逻辑代码在主进程中,也就是运行着 Webpack 的进程中,核心调度器会把一个个任务分配给当前空闲的子进程,子进程处理完毕后把结果发送给核心调度器,它们之间的数据交换是通过进程间通信 API 实现的。

	3. 核心调度器收到来自子进程处理完毕的结果后会通知 Webpack 该文件处理完毕。

```

const HappyPack = require('happypack');
// 构造出共享进程池,进程池中包含5个子进程
const happyThreadPool = HappyPack.ThreadPool({ size: 5 });

module.exports = {
 module: {
    rules: [
      {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: ['happypack/loader?id=css'],
        }),
      },
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // 使用共享进程池中的子进程去处理任务
      threadPool: happyThreadPool,
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      loaders: ['css-loader'],
      // 使用共享进程池中的子进程去处理任务
      threadPool: happyThreadPool,
    }),
  ],
};
```

prefetch/preload

- [webopack4已支持](https://webpack.docschina.org/guides/code-splitting/#prefetching-preloading-modules) 但是4.6之后才支持

```
import(/* webpackPreload: true */ 'ChartingLibrary');
import(/* webpackPrefetch: true */ 'LoginModal');
```
- 或借用插件
	1.  [webpack插件resource-hints-webpack-plugin](https://github.com/jantimon/resource-hints-webpack-plugin) similarly no async chunk support
	2. [preload-webpack-plugin](https://github.com/vuejs/preload-webpack-plugin)
    3. [[译]Preload,Prefetch 和它们在 Chrome 之中的优先级](https://juejin.im/post/58e8acf10ce46300585a7a42?utm_source=gold-miner&utm_medium=readme&utm_campaign=github)

按需加载 webpack有强大的代码分割能力

```
import(/* webpackChunkName: "show" */ './show')
```

**webpack支持import语法的支持**,当执行到import时

- 以 ./show.js 为入口新生成一个 Chunk;
- 当代码执行到 import 所在语句时才会去加载由 Chunk 对应生成的文件。
- import 返回一个 Promise,当文件加载成功时可以在 Promise 的 then 方法中获取到 show.js 导出的内容。
- /* webpackChunkName: "show" */ 为动态生成的文件取名字, OR 默认[id].js,当然为了正确输出文件名字 需要配置出口
- babel-plugin-syntax-dynamic-import

```
module.exports = {
  output: {
    // 为动态加载的 Chunk 配置输出文件的名称
    chunkFilename: '[name].js',
  }
};
```

- vue按需加载: 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

```
vue按需加载比较简单

const index = () => import(/* webpackChunkName: "index" */ "@/components/index.vue");
```
- react按需加载Router 

```
import React, {PureComponent, createElement} from 'react';
import {render} from 'react-dom';
import {HashRouter, Route, Link} from 'react-router-dom';
import PageHome from './pages/home';

/**
 * 异步加载组件
 * @param load 组件加载函数,load 函数会返回一个 Promise,在文件加载完成时 resolve
 * @returns {AsyncComponent} 返回一个高阶组件用于封装需要异步加载的组件
 */
function getAsyncComponent(load) {
  return class AsyncComponent extends PureComponent {

    componentDidMount() {
      // 在高阶组件 DidMount 时才去执行网络加载步骤
      load().then(({default: component}) => {
        // 代码加载成功,获取到了代码导出的值,调用 setState 通知高阶组件重新渲染子组件
        this.setState({
          component,
        })
      });
    }

    render() {
      const {component} = this.state || {};
      // component 是 React.Component 类型,需要通过 React.createElement 生产一个组件实例
      return component ? createElement(component) : null;
    }
  }
}

// 根组件
function App() {
  return (
    <HashRouter>
      <div>
        <nav>
          <Link to='/'>Home</Link> | <Link to='/about'>About</Link> | <Link to='/login'>Login</Link>
        </nav>
        <hr/>
        <Route exact path='/' component={PageHome}/>
        <Route path='/about' component={getAsyncComponent(
          // 异步加载函数,异步地加载 PageAbout 组件
          () => import(/* webpackChunkName: 'page-about' */'./pages/about')
        )}
        />
        <Route path='/login' component={getAsyncComponent(
          // 异步加载函数,异步地加载 PageAbout 组件
          () => import(/* webpackChunkName: 'page-login' */'./pages/login')
        )}
        />
      </div>
	</HashRouter>
```

分析插件

- webpack-bundle-analyzer
- webpack --profile --json 记录构件耗时并输出json文件
- [Webpack Analyse](http://webpack.github.io/analyse/)官方可视化工具

HRM 模块热替换

   new webpack.HotModuleReplacementPlugin(), //当启动时带上 `--hot` 参数会自动调用
   new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.

接入serviceWorker workbox-webpack-plugin

- wokbox 是用于向web应用程序添加离线支持的JavaScript库。
- https://segmentfault.com/a/1190000019281388?utm_source=tag-newest

GPU加速原理

网页的渲染过程

1. 获取dom 分割成多个层
2. 对每个图层的节点计算样式结果 (Recalculate style)
3. 为每个节点生成图形和位置 layout 
4. 将每个节点绘制并填充到图形的位置中 paint 
5. 图层作为纹理上传到GPU
6. 符合条件的多图层生成屏幕的图像  Composite layer 合成层
7.浏览器渲染过程 layout -> paint -> composite layer (触发的阶段越高,代价越高)

独立的层 -->重排重绘 不会影响父层

哪些些会生成独立的层

1. 根元素 position transform 半透明 canvas video overflow 滤镜...

哪些属性可以开启GPU加速

 - 虽然生成独立的层,但是还是cpu一直在计算重绘重排,再传到GPU 进行合成
- 开启GPU加速后 就省去CPU重排重绘的过程 GPU(图形处理器)擅长图形处理   (gpu.js)
- css3d video webgl transfrom css滤镜 flash z-index大于某个相邻节点的layer元素... 

硬件加速(GPU加速)

- 术语:Texture 即CPU传到GPU的一个Bitmap
- GPU 能对 快速对Texture 进行 偏移 旋转,缩放,修改透明度等
- 节约了 
    1. CPU进行layout paint 的时间 
    2. CPU上传位图的时间

CPU VS GPU

- 相同:都有总线与外界联系,有自己的缓存体系,以及数字与逻辑运算单元,两者都是为了完成计算任务而设计的
- 不同:CPU 主要处理操作系统和应用程序 GPU主要处理跟显示相关的数据处理, GPU的活一般CPU都能干,但是效率低

layer模型

- 浏览器根据css属性生成layer
- 将layer作为Texture上传GPU
- 当改变layer的transfrom opacity 等属性,渲染会跳过 layout paint 直接通知GPU做出改变

重绘

- border-raduis box-shadow color ...

重排

- 跳过优化(浏览器对重排有一定的优化,合并多次重排)读取 offsetTop scrollTop width clientWidth getComputedStyle() getBoundingClientRect()
- 删除位置 大小 盒子
- 读写分离   **fastdom.js**

```
var h = document.getElementById('h').clientHeight
//写操作放到下一帧 (假如下一针有大量的操作,requestAnimationFrame会自动调整帧数从60FPS降到30FPS,不会导致丢帧)
    requestAnimationFrame(function(){
        xxx.style.height = h*2
    })
```
- display:none -> block 重绘重排  visibility:visible -> hidden 

##. 直出 - 假如多页的话,每次跳转都会重新加载你以来的vue react - 假如SPA单页面:访问html文件 -> 初始化路由 -> 找到对应跟路由 -> 找到对应组件 ->发请求 发给后端再渲染 导致白屏 - SSR + SPA 用户访问到网站直接吐html - 多页转单页 pushstate技术 a/b ->c/d 检测到路由变化向后台发请求 - 如果header是ajax ("X-Requested-With":"XMLHttpRequest")就是单页,不是render

  1. 利用缓存技术(localStorage indexedDB webSQL等)
    1. 缓存关键路径里的js资源。(微信) 第一次访问时将js放到缓存中,每次进来取出来执行
    • 关键点是设计一套缓存更新机制。对缓存的js增加后缀(时间戳或hash等)来设置独一无二的版本标识。
    • 每次后端同步传一份资源的配置文件,FE根据配置文件和缓存中的文件进行版本标识。从而决定是利用缓存还是重新请求。
    1. 利用缓存对关键的XHR异步请求进行缓存。(天猫超市)
    • 天猫超时首页对首屏中的轮播和10个分类入口的数据进行了缓存。提升首屏的渲染展示时间。
    1. 对非关键请求进行缓存(狗东pc)
    • 将非首屏资源的HTML/CSS等资源抽出来放在LS内,当页面滚动到可视区域时再去LS中获取数据,插入到dom中。 wx
wx
window.__moon_map = {"pages/iframe_communicate.js":"//res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/pages/iframe_communicate42f400.js","new_video/player.html.js":"//res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/new_video/player.html433882.js",}

58.com 下localstorage
clv
{j1.58cdn.com.cn/componentsLoader/dist/PopCollection.js: "20180427170320"
j1.58cdn.com.cn/componentsLoader/dist/PopupBind.js: "20181221193411"
j1.58cdn.com.cn/componentsLoader/dist/PopupLogin.js: "20181221193411"
j1.58cdn.com.cn/git/passport-popupwarn/BindBefore.js: "20181201104527"
j1.58cdn.com.cn/git/passport-popupwarn/MobileSure.js: "20181201104527"
j1.58cdn.com.cn/git/passport-popupwarn/Warn.js: "20181201104527"
j1.58cdn.com.cn/git/private-phone-call-pc/dist/js/bundle_pc_generate.js: "0"
updateTime: 1555060912448//现在的时间戳
}

TIP

请打开chrome工具对比 preference timeingline

对比 preference timeingline
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<style>
	.container {
		position: relative;
	}
	#ball,#ball2 {
		position: absolute;
		border-radius: 50%;
		box-shadow: 0 0 5px 0 red;
		width: 100px;
		height: 100px;
	}
	#ball2 {
		box-shadow: 0 0 5px 0 gray;
	}
	.run {
		animation: running-circle 3s infinite;
	}
	.run2 {
		animation: running-circle2 3s infinite;
	}
	@keyframes running-circle {
		0% {
			top: 0;
			left: 0;
		}
		25% {
			top: 0px;
			left: 200px;
		}
		50% {
			top: 200px;
			left: 200px;
		}
		75% {
			top: 200px;
			left: 0px;
		}
		100% {
			top: 0px;
			left: 0px;
		}
	}
	@keyframes running-circle2 {
		0% {
			transform: translate(0,0)
		}
		25% {
			transform: translate(0,200px)
		}
		50% {
			transform: translate(200px,200px)
		}
		75% {
			transform: translate(200px,0)
		}
		100% {
			transform: translate(0,0)

		}
	}
</style>
<body>
	<div class="container">
		<div id="ball"></div>
		<div id="ball2"></div>
	</div>
	<script>
		let ball = document.getElementById('ball')
		ball.classList.add('run')
		let ball2 = document.getElementById('ball2')
		ball2.classList.add('run2')
	</script>
</body>
</html>

TIP

ball分析

ball

TIP

ball2分析

ball

前端性能优化清单 前端性能优化清单 从Chrome源码看浏览器如何加载资源

Last Updated: 10/24/2019, 4:46:49 PM