Skip to content

图片优化

  • 避免空 src 的图片,因为空 src 也是会发送请求的

  • 减小图片尺寸

    • 不同环境,加载不同尺寸和像素的图片

      srcset 表示候选 URL,指定在某个宽度下使用特定图像候选代表的图像资源或在什么样的显示 器像素密度下应用相应的图像资源,可以混用,还可以指定一个额外的备用图像候选,没有任何 条件,可用于任意宽度。

      sizes 表示不同媒体条件下使用不同尺寸的图像,当视口宽度小于等于 500px 时,图片宽度为 50px,视口宽度为 500-800px 时,图片宽度为 100px, 视口宽度为 800-1500px 时,图片宽度为 200px

      html
      <img 
        src="foo2x.png" 
        srcset="foo.png 1x, foo.png 2x, foo.png 3x"
        sizes="
          (max-width: 500px) 50px,
          ((min-width: 500px) and (max-width: 800px)) 100px, 
          ((min-width: 801px) and (max-width: 1500px)) 200px
        "
      >
    • 较大的图片采用渐进式图片(图片会从模糊到清晰)

    • 小图片(8 KB 以下)编码为 Base64 减少网络请求

  • 图片懒加载

    html
    <img loading="lazy" src="foo" width="100" height="100">
  • 采用雪碧图合并图标。

  • 图片格式

    • jpg

      适合色彩丰富的图片、banner 图;不适合图形问题、图标(纹理边缘有锯齿),不支持 透明度。

    • png

      适合纯色、透明、图标,支持半透明;不适合色彩丰富的图片,因为无损存储会导致存储 体积大。

    • gif

      适合动画,可以动的图标;不支持半透明,不适合存储彩色图片。

    • webp

      适合半透明图片,可以保证图片质量和较小的体积。

    • svg

      相比于 jpg 和 png 体积更小,渲染成本过高,适合小且色彩单一的图标。

HTML 优化

  • 语义化,利于开发及 SEO

  • 提前声明字符编码,让浏览器快速确定如何渲染网页内容。将 <meta charset="UTF-8"> 放在 <head> 的最前面,有助于提升页面的首屏渲染速度和兼容性。

    html
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
    </head>
  • 减少不不要的 HTML 嵌套从而减少 DOM 数量。

  • 打包时删除多余空格、空行、注释、无用属性,压缩 HTML。

  • 不直接写 iframes 标签,会在父页面的 onload 事件触发前等待 iframe 内的内容加载完成,会阻塞父页面的 onload,通过JS 动态加载 iframe。

    js
    const iframe = document.createElement('iframe')
    iframe.src = 'https://www.example.com'
    document.body.appendChild(iframe)

CSS 优化

  • 减少伪类选择器、减少选择器层级、减少使用通配符,因为是从右往左解析的。

  • 打包时删除空行、注释,压缩 CSS

  • 使用外部样式表(link 标签),便于浏览器缓存。

  • 添加媒体字段,只加载需要的 CSS 文件

    html
    <link
      href="index.css"
      rel="stylesheet"
      media="screen and (min-width: 1024px)" 
    />
  • 利用 contain 属性将元素进行渲染隔离,减少重排重绘影响范围,提高性能

    css
    .card {
      /* 
      ayout:元素的布局不会影响外部,也不会被外部影响。浏览器在计算布局时,可以把该元素当作一个独立的区域。
      paint:元素的绘制(渲染)不会影响外部,也不会被外部影响。重绘时只需重绘自身和子元素。
      */
      contain: layout paint;
    }
  • 避免使用 @import 引入 CSS,因为其加载是串行的,会阻塞渲染,影响性能。

JS 优化

  • async、defer 异步加载 JS 文件

    • defer:脚本异步加载,等 HTML 解析完成后,按照在页面中出现的顺序执行,适合依赖 DOM 的脚本。

      <script> 放在 </body> 前,脚本会在 HTML 元素解析完后才加载和执行,但此时脚本是同步加载和执行的,会阻塞后续资源的加载。defer 更优雅且不会阻塞页面解析

    • async:脚本会异步加载,加载完成后立即执行,不保证执行顺序,适合不依赖 DOM 和其他脚本的独立脚本。

  • 缓存元素的布局信息

    js
    // bad
    div.style.left = div.offsetLeft + 1 + 'px'
    console.log(div.offsetLeft)
    
    // good
    const offsetLeft = div.offsetLeft
    div.style.left = offsetLeft + 1 + 'px' 
    console.log(offsetLeft)
  • 读写分离,代浏览器有渲染队列的机制,发现某一行要修改元素样式,不立即渲染,而是看看下一行,如果下一行也会改变样式,则把修改样式的操作也放到渲染队列中,...,直到不再是修改样式的操作后再整体渲染一次,引发一次回流。

    js
    // bad
    box.style.width = '100px'
    console.log('do something...')
    box.style.height = '100px'
    console.log('do something...')
    box.style.background = '100px'
    box.style.margin = '20px auto'
    
    // good
    box.style.width = '100px'
    box.style.height = '100px'
    box.style.background = '100px'
    box.style.margin = '20px auto'
    console.log('do something...')
    console.log('do something...')
  • 频繁的 DOM 操作可先在内存中处理(如使用虚拟 DOM 或 Fragement),最后一次性更新到真实 DOM,减少重排和重绘。

    js
    // bad
    for (let i = 0; i < 10; i++) {
      const span = document.createElement('span')
      span.innerText = i
      document.body.appendChild(span)
    }
    
    // good
    const fragment = document.createDocumentFragment()
    for (let i = 0; i < 10; i++) {
      const span = document.createElement('span')
      span.innerText = i
      fragment.appendChild(span)
    }
    document.body.appendChild(fragment)
    fragment = null
    
    // good
    let htmlStr = ``
    for (let i = 0; i < 10; i++) {
      htmlStr += `<span>${i}</span>`
    }
    document.body.innerHTML = htmlStr
  • 利用 Web Worker 在后台线程处理复杂计算,避免阻塞主线程,提高页面响应速度。

  • 滚动优化 IntersectionObserver 替代 window.onscroll

  • 虚拟滚动,只渲染可视区域内的列表项,提升长列表渲染性能,减少 DOM 节点数量。

  • requestAnimationFrame、RequestIdleCallback

    • requestAnimationFrame:在浏览器下一次重绘前执行动画相关操作,保证动画流畅。

    • requestIdleCallback:在浏览器空闲时执行非紧急任务,避免阻塞主线程。

  • 避免使用 eval 解析和执行字符串代码,性能差且有安全风险

  • 事件委托,利用事件冒泡,将事件绑定在父元素上,减少事件监听器数量,提升性能。

  • 尽量使用 Canvas 动画、CSS 动画,它们的性能通常比 JS 动画性能更好,能充分利用浏览器优化。

  • tree-shaking 去除无用代码,减少代码体积。

  • lazy-loading 代码懒加载

  • 高频任务进行函数的防抖和节流

字体优化

  • 使用字体子集

    只保留实际页面用到的字符,减少字体文件体积。

  • 选择合适的字体格式

    使用现代浏览器支持的高效字体格式,如 WOFF2(优先)、WOFF,兼容性好且压缩率高。

  • 字体懒加载

    对于不影响首屏渲染的字体,采用懒加载或按需加载,减少首屏阻塞。

缓存优化

  • 静态资源实现强缓存、协商缓存

  • 不经常更新的接口的数据采用本地存储并做过期时间限制

  • 常用的库部署到 CDN 上

网络请求优化

  • 减少数据传输大小

  • 采用 gzip 压缩优化对静态资源进行压缩

  • 大批量数据分批次请求(下拉刷新、分页、游标分页)

  • 控制 cookie 大小,同源下的请求头中会携带所有 cookie

    • 根据不同子域划分 cookie,减少传输大小。

    • 静态资源域名和 cookie 域名采用不同域名,避免访问静态资源时携带 cookie

  • 减少 HTTP 请求次数

    • localStorage 缓存 JS 文件,极大优化首屏时间 => m.baidu.com
  • HTTP 开启长连接:Connection: keep-alive

  • 采用域名分片技术,将资源放到不同域名下,解决同一个域名最多处理 6 个 TCP 连接的问题。(域名分片适用于 HTTP/1.x,在 HTTP/2 下,多路复用已解决此问题,不推荐再使用域名分片,过多的域名分片会增加 DNS 解析开销)

  • 加载数据优先级

    • preload(预先请求当前页面需要的资源,首页内容)

    • prefetch(页面空闲时请求未来页面会使用的资源并缓存到 HTTP 缓存中)

    html
    <link rel="preload" href="style.css" as="style">
    <link rel="prefetch" href="next-page.js">
  • DNS 预解析,预计用户会跳转到其它不同域名的页面,提前 dns 解析

    html
    <link rel="dns-prefetch" href="//example.com">

LightHouse

sh
npm i lighthouse

lighthouse --view http://foo.com