# 浅谈前端性能优化 --- 图片篇

作者:Horace
博客:最近简单的搭了一个👉博客~
ps:新人小白一枚,如有错误,欢迎指出~

# 写在前面

前端是一个范围很大、涉猎甚广的领域,用户的需求越来越多,随着前端的发展,对于性能优化的考虑和需求越来越多。性能优化的实现可以更好的提升用户体验和产品质量,对于留住你的用户有很大的帮助。今天,我们就来聊一聊前端对于图片到底能做出什么样的优化。

# 为什么要进行图片优化?

图片的优化在前端领域中是很常见的,PC端、手机端都对图片进行了一定的优化处理。图片的优化可以让用户的感受更加亲切,更加的符合视觉要求。从另一个角度来说,图片也要去适应不同的设备。

首先,我们要来聊聊 http 请求。我们都知道,http 请求一般来说是并发执行的,而浏览器的并发数量是有限的,那 chorme 来说,http 请求的最大并发数量是 6。这也是有一定的考虑的,http 请求过多会对性能有一定的影响。其次,如果图片文件比较大的话,加速的速度会明显的下降,下面从 PC 端和手机端端两个方面来说说。

# PC 端

PC 端主要是通过 wifi 来访问互联网的,如果一个网站的图片比较多(尤其是电商购物网站),那么同一时间就会发起多个 http 并发请求,多个并发请求之间就会处于竞争的状态,页面打开的速度会大幅度下降。

# 手机端

手机端很多时候都是通过自己的移动流量进行互联网访问,这个时候我们就需要为用户考虑到流量的问题了,并且如果用户的网络情况不是很好,就需要在节省流量的前提下更快的把图片展示给用户。

# 图片的类型

在说图片优化之前,我们必须要知道常见的图片类型到底有哪些。
平时比较常见的图片类型有:jpg、jpeg、png、svg、bmp、gif、webp
前几种对于大部分人来说可能就是比较熟悉的了,到了后面的 webp 格式,有些人可能会觉得比较陌生,没关系,我后面会讲到。

# 优化一:延迟加载(懒加载)

首先我们不动图片本身,从图片外部来考虑,那就很容易想到延迟加载 --- 也叫懒加载。顾名思义,就是图片过多的时候,把后续的图片延迟一会儿,等到需要的时候再进行加载显示。现在一张图片超过几兆已经是很经常的事了,如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以,我们需要懒加载,进入页面的时候,只请求可视区域的图片资源。

# 实现方式

延迟加载的方式大概可以总结成以下几点:

  1. 设置图片 src 属性为同一张图片(一般放置加载图片,也就是所谓的菊花图),同时自定义一个 data-src 属性来存储图片的真实地址
  2. 页面初始化显示的时候或者浏览器发生滚动的时候判断图片是否在视野中
  3. 当图片在视野中时,通过 js 自动改变该区域的图片的 src 属性为 data-src 中的真实地址
    放张图就可以懂了:

延迟加载示意图

代码可以直接看我的github

# Vue 的实现

Vue 中实现图片的异步加载有更方便的方式,直接使用 vue-lazyload 库就可以轻松实现。

Vue.use(VueLazyload, {
  // CommonJS
  error: require('./assets/loading.svg'), // error 时候展示的图
  loading: require('./assets/loading.svg'), // 加载中
  attempt: 1 // viewport 视窗
})
1
2
3
4
5
6

一次加载两个视窗范围的图片,图片上 v-lazy

<template>
  <div id="app">
    <div v-for="(img, index) in images" :key="index">
      <img v-lazy="img">
    </div>
    <!-- <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/> -->
  </div>
</template>
1
2
3
4
5
6
7
8
9

# 优化二:图片压缩

上一步的延迟加载只是延迟了图片的加载,让浏览器同一时间的并发数减少,如果是在面试中,面试官还会让你再进一步优化,这个时候就要从图片本身去考虑了。

在电商网站中经常可以看到 webp 格式的图片,比如:

那现在就来聊聊之前讲到的 webp 格式的图片,我们先来看看它是什么:

WebP 是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式VP8。
WebP最初在2010年发布,目标是减少文件大小,但达到和JPEG格式相同的图片质量,希望能够减少图片档在网络上的发送时间。根据Google较早的测试,WebP的无损压缩比网络上找到的PNG档少了45%的文件大小,即使这些PNG档在使用pngcrush和PNGOUT处理过,WebP还是可以减少28%的文件大小。

从这里我们可以看到,在同样的图片效果的前提下,webp 格式的图片可以剩下 28%-45% 的大小, 这就是变相的在图片方面提升了网页的性能,所以它得到了很多网站的青睐。

jpg与webp对比

我们可以通过 node 使用 webp-converter 的 npm 包来实现把图片压缩成 webp 格式:

// 转换 webp 格式示例(在图片比较大的情况下)
const webp = require('webp-converter')
webp.cwebp('test.jpg', 'test.webp', '-q 80', 
  function(status, error) {
    console.log(status, error)
  }
)
1
2
3
4
5
6
7

# 兼容

webp 格式的图片虽然好用,但是它和其他的一些好东西一样,有兼容性问题,低版本的浏览器不支持 webp 格式。这个时候,我们为了兼容性考虑,使用 node 进行图片处理,提供两套图片,如果不支持还是显示 jpg,根据实际情况来确定到底选用哪个图片~

# 优化三:去菊花图 + base64 化

当网速过慢的时候,我们在一些网站图片加载的时候就会显示一个死亡加载圈圈 --- 也就是所谓的菊花图,如下所示:

用户的需求是尽可能快的看到图片,而菊花图的圈圈会让用户的体验很不好,不符合用户的心理。所以我们对这个也可以进行一定的优化,而这个优化在一些网站中就可以看的到,那就是先加载图片的轮廓让用户看到一个模糊的图片,等加载完成显示原图。多说无益,直接看图:

我们也有相应的 npm 包可以实现 — lqip 使用方法如下:

// 先显示原图 10% 左右的轮廓,lazyload 加载完成后替换原来的 src 放上去
const lqip = require('lqip') // 生成轮廓图
const file = './in.png'
lqip
  .base64(file) // 生成 base64(轮廓的 base64)
  .then(res => {
    console.log(res)
  })
1
2
3
4
5
6
7
8
<body>
  <img 
  width="446.5"
  height="195.5"
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAECAYAAAC3OK7NAAAAAklEQVR4AewaftIAAACUSURBVAXB0QrBUBzA4d9/52zLthK1kjTl3hvYE4hn8FZehSTJQ3CluJhiM9QS2Tm+T7LN3Cpl8Js3vPCN1ke+z4KfHtFo9ykzl/L0QNciiPiIsZjrlspLWa4N+/OB2bgFToSIoI2FGvj8Emw9QWRA0g1Y7FZcijsuFZ7robVSIBYDOGGPV54Td2Km6RDfdQjCCOUIf1XhMMa8r80zAAAAAElFTkSuQmCC" 
  alt="" 
  data-src="./in.png" 
  class="lazyload">

  <script>
    window.onload = function () {
      const img = document.querySelector('.lazyload')
      const real_src = img.getAttribute('data-src') // 真实图片地址
      const oImg = document.createElement('img') // 用一个假节点渲染真实图片
      oImg.src = real_src // 真实图片
      oImg.onload = function () { // 真实图片加载完成
        setTimeout(() => {
          img.src = real_src // 替换页面图片
        }, 3000);
      }
    }
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 其他优化:

当然,对于图片还有一些其他的优化方案,这里简单的介绍一下。

# 图片适配

我们在看别人的源代码的时候经常会发现其中同样的一张图片会有 @2x 和 @3x 的形式,这是前端在准备图片的时候考虑的对手机型号的适配,网上资料比较多,这里就不多说这方面了。

# Webpack

  • 图片压缩
    webpack也可以对图片进行压缩操作,通过image-webpack-loader可以对输出的图片进行指定质量的压缩

    查看代码
    
    rules: [{
    test: /.(gif|png|jpe?g|svg)$/i,
    use: [
    'file-loader',
    {
    loader: 'image-webpack-loader',
    options: {
    mozjpeg: { // 压缩 jpeg 格式图片
    progressive: true,
    quality: 65
    },
    optipng: { // 压缩 png 格式图片
    enabled: false,
    },
    pngquant: { // 压缩 png 格式图片
    quality: [0.65, 0.90],
    speed: 4
    },
    gifsicle: { // 压缩 gif 格式图片
    interlaced: false,
    },
    webp: { // 把 jpg 和 png 格式的图片压缩成 webpag 格式
    quality: 75
    }
    }
    },
    ],
    }]
    
    

  • 合成雪碧图
    有时为了美观,我们会使用图片来代替一些小图标,但是一个网页可能有很多的小图标,浏览器在显示页面的时候,就需要像服务器发送很多次 http 请求,这样一来,一是造成资源浪费,二是会导致访问速度变慢。这时候,把很多小图片(需要使用的小图标)放在一张图片上,按照一定的距离隔开,合成了所谓的雪碧图,配合上 css3 的 background-position 就解决了上述的两个问题。 雪碧图实例 webpack 同样也提供了合成雪碧图的方法 --- webpack-spritesmith

    查看代码
    
    const SpritesmithPlugin = require('webpack-spritesmith')
    new SpritesmithPlugin({
    src: {
    cwd: path.resolve(__dirname, 'src/asserts'),
    glob: '*.png'
    },
    target: {
    image: path.resolve(__dirname, 'src/spritesmith-generated/sprite.png'),
    css: path.resolve(__dirname, 'src/spritesmith-generated/sprite.css')
    },
    apiOptions: {
    cssImageRef: "src/sprite.png"
    }
    })
    

    通过配置就能将 asserts 目录下的所有 png 文件合成雪碧图,并且输出到指定的目录,同时还可以生成对应的样式文件,样式文件的语法会根据你配置的样式文件的后缀动态生成,但如果你图片过多的情况下,放到一张图片中找起来就会比较麻烦,还是要看实际情况~

# base64 化

在上面的去菊花图的优化方法中,生成图片轮廓的时候就是使用了 base64 格式。将图片 base64 化会生成一串字符串,可以直接放到 img 标签的 src 属性上,浏览器是可以直接识别这一串字符的,不需要发送网络请求直接解析,这样也就大大降低了 http 请求的次数。

base64 化虽然可以减少 http 请求的次数,但是要注意的是:base64 化之后的图片体积会增大。所以严格来说,base64 化不代表性能优化,它在减少 http 请求的同时,付出了 css 文件体积增大进而导致 CRP 阻塞的代价

base64 化最常见的就是在 url-loader 中使用。

# 写在最后

笔者是在看到最近的一个面试题目之后才有感而发写了这篇文章,前端对于图片的优化肯定不止我写到的这些。由此可见前端对于用户体验的考虑还是很多的,当然不排除用户需求太多(手动狗头)。笔者现在大三,最近看了很多面经之后发现自己水平和别人相比还是差很多,希望能在文章输出的同时有一定的进步吧~

我把我的学习记录都记录在了我的 github 并且会持续的更新下去,有兴趣的小伙伴可以看看~
github

Last Updated: 5/6/2020, 11:48:16 AM