# 图像优化
在Web应用中，图片占据了很大一部分流量，所以对图像的优化必不可少。

## 图片格式
图片格式有许多，不同的格式在是否支持透明，是否支持动画，压缩方式，兼容性等方面存在差别。先放一张来自谷歌开发者文档的图：

![https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/format-tree.png?hl=zh-cn](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/format-tree.png?hl=zh-cn)

### 矢量图像
* 矢量图像适用于包含几何形状的图像
* 矢量图像与缩放和分辨率无关
* SVG 是一种基于 XML 的图片格式
* SVG 文件应进行缩减和使用 GZIP 进行压缩(参照资源压缩小节)

### 光栅图像

光栅图像是一个 2 维“像素”栅格，例如，100x100 像素的图像是 10,000 个像素的序列，而每个像素又存储有“RGBA”值，即4个通道，每个通道256个值，需要用8位（即一个字节）表示，相当于一个像素有4个字节。

针对光栅图像的一种优化方案是改变像素中每个通道的位数。

## 替代方案
* **CSS效果和动画:**  用CSS实现的效果代替图片，可以得到类似SVG的效果，即与缩放和像素无关，且可以进行压缩，以减少字节数
* **网页字体:** 针对色彩单一的图像可以用iconfont代替，不适合色彩复杂的图像
* **canvas:** 高性能的动画支持，适合画光栅类图像
* **base64编码:** 图片小于2KB的情况下采用

以gulp为例，自动化地将css中引用的url格式的图片，转换为base64格式：
```js
const base64 = require('gulp-base64');
gulp.task('base64',()=>{
    gulp.src('css/animateTop.css')
        .pipe(base64({
            baseDir: './',
            extensions: ['png'],
            debug: true,
            maxImageSize: 20 * 1024
        }))
        .pipe(gulp.dest('dist/css'))
});
```

## 图像压缩

---
* 由于人眼的工作方式的缘故，对图像进行有损压缩是不错的选择
* 图像优化依赖有损和无损压缩来实现
* 图片格式上的差异是由于优化图像时使用的有损和无损算法的差异和使用方式的差异所致
并不存在任何适用于所有图像的最佳格式或“质量设置”：每个特定压缩程序与图像内容的组合都会产生独特的输出

参见图像压缩维基百科：[https://zh.wikipedia.org/wiki/%E5%9B%BE%E5%83%8F%E5%8E%8B%E7%BC%A9](https://zh.wikipedia.org/wiki/%E5%9B%BE%E5%83%8F%E5%8E%8B%E7%BC%A9)


### gulp-imagemin
创建gulp自动化压缩图像任务：
```js
const imagemin = require('gulp-imagemin');

gulp.task('minifyImages',()=>{
    gulp.src('images/*.*')
        .pipe(imagemin({
            progressive: true
        }))
        .pipe(gulp.dest('dist/images'))
})
```
这里的`progressive`表面要生成渐进式图片，即下面要讲到的渐进式图片。
## 优化

### 渐进式图片
`baseline`替代为`progressive`(渐进式)，对于大尺寸图片，可以一定程度上加快下载呈现速度，改善用户体验。

一般线性加载：

![http://codinghorror.typepad.com/.a/6a0120a85dcdae970b0128776fcab6970c-pi](http://codinghorror.typepad.com/.a/6a0120a85dcdae970b0128776fcab6970c-pi)

渐进式加载：

![http://codinghorror.typepad.com/.a/6a0120a85dcdae970b0128776fcadb970c-pi](http://codinghorror.typepad.com/.a/6a0120a85dcdae970b0128776fcadb970c-pi)

图片来源：张鑫旭的博客

#### 生成渐进式图片
* photoshop等软件生成，将图像保存为web格式，勾选连续选项
* Nodejs生成：

```js
const gm = require('gm');
const path = require('path');

gm(path.join(__dirname,'./images/img1.png'))
    .interlace('Line')
    .write(path.join(__dirname,'./image1-line.jpeg'),err=>{
        if(err){
            console.error(err);
        }
    })
```


### 响应式图片
试想这么一个情况：网页实际需要呈现的大小是400x400，但是服务提供了一张410x410的资源，在1x分辨率情况下，就多余了8100像素。

实际应用中，我们的网站会针对不同的设备大小显现不同尺寸的图像，如果我们提供的资源尺寸固定，在移动设备上就会造成带宽浪费。所以，响应式图片就显得很重要。

响应式图片创建主要用到三个新特性：`srcset/sizes/<picture>`

来自MDN的例子：
```html
<img srcset="elva-fairy-320w.jpg 320w,
             elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy">
```
`srcset`包含多个不同真实大小的图像资源，`sizes`指定多个媒体查询条件，每一个条件后面跟上该条件为真时，显示到网页的尺寸，当所有条件都不成立时，使用默认的尺寸，即这里的800px,`src`指定了默认加载的资源，当浏览器不支持新特性时有用。

比如浏览器此时视窗宽度为480px，就会显示440px的图像，加载`elva-fairy-480w.jpg`，因为480最接近440，这样就避免下载更大的资源。

另外一个场景：一张照片，周围是风景，中间一个人像，在小屏幕上会显示缩放的尺寸，使得人像太小，这个时候应该截取中间的人物部分，这样显得人像更大：
```html
<picture>
  <source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg">
  <source media="(min-width: 800px)" srcset="elva-800w.jpg">
  <img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva">
</picture>
```

#### 资源预加载的影响
在之前的网页渲染章节中，我们讲到了浏览器的资源预加载优化策略，该策略会导致我们无法使用css和js来实现响应式图片，必须借助上面的新特性。

### 图片懒加载
对于图片资源过多的页面，比如淘宝网站，商品列表是可以不断的往下滚动加载的，这就使得一张网页上会有很多图片，如果一次性加载会使得网站加载很慢，这个时候我们可以采取懒加载的方式。

#### 实现
将页面中的`img`标签`src`指向一张小图片或者src为空，然后定义`data-src`（其他都可以）用于存放真实图片资源地址，用于后面需要加载时替换`src`值，如：
```html
<img src="default.jpg" data-src="/img1.jpg" />
```
滚动到可见区域再加载：
```js
<script>
    var num = document.getElementsByTagName('img').length;
    var img = document.getElementsByTagName("img");
    var n = 0; //存储图片加载到的位置，避免每次都从第一张图片开始遍历
    lazyload(); //页面载入完毕加载可是区域内的图片
    window.onscroll = lazyload;
    function lazyload() { //监听页面滚动事件
        var seeHeight = document.documentElement.clientHeight; //可见区域高度
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
        for (var i = n; i < num; i++) {
            if (img[i].offsetTop < seeHeight + scrollTop) {
                if (img[i].getAttribute("src") == "default.jpg") {
                    img[i].src = img[i].getAttribute("data-src");
                }
                n = i + 1;
            }
        }
    }
</script>
```
代码参照：[https://zhuanlan.zhihu.com/p/24057749?refer=dreawer](https://zhuanlan.zhihu.com/p/24057749?refer=dreawer)

*注意：滚动事件处理函数会高频触发调用，需要进行函数节流。*

#### 与预加载结合
这一点灵感来源于张鑫旭的博客。考虑这么一个情况：我们使用懒加载来避免不必要的加载，但是我们又失去了预加载的好处：加快加载速度。

张鑫旭博客的一个例子：
![](/assets/screenhot14.png)

在加载第一张选项卡时，后面两张图片不用加载，可以采用懒加载处理，但是当我们点击第二个选项卡后再去加载图片时，会有一个加载的过程，更好的处理方式时，在点击之前就加载完毕，即基于用户行为的资源预加载：

即我们不是在`click`事件时加载图片，而是在用户鼠标进入时就去加载图片：
```js
elAs.on({
    mouseenter: function() {
        var target;    
        if (!this.preloaded) {
            target = elImgs.eq($(this).data('index'));
            // 图片地址换成真实地址
            target.attr('src', target.attr('data-src')).removeAttr('data-src');
            // 标记已加载
            this.preloaded = true;
        }
    }    
});
```

参考：[http://www.zhangxinxu.com/wordpress/2016/06/image-preload-based-on-user-behavior/](http://www.zhangxinxu.com/wordpress/2016/06/image-preload-based-on-user-behavior/)

### 合并图片sprite(雪碧图)
对于有许多小图标的网页，如果每个都是一个独立的资源，就要发起多个请求，这样不论是对服务器还是对用户体验来说都是不好的实践。

举个例子，下面是淘宝首页一个地方的截图：
![/assets/screenhot15.png](/assets/screenhot15.png)

淘宝就采用了雪碧图的处理方式：
![](/assets/screenhot16.png)

#### gulp雪碧图
使用`gulp.spritesmith`自动处理雪碧图：

![https://github.com/twolfson/gulp.spritesmith/raw/master/docs/example.png](https://github.com/twolfson/gulp.spritesmith/raw/master/docs/example.png)

具体请查看文档：[https://github.com/twolfson/gulp.spritesmith](https://github.com/twolfson/gulp.spritesmith)
### 自动优化

#### gulp
关于gulp自动优化图片，已经穿插在上面的内容当中，如自动化base64编码，图片压缩等。

#### CDN

有些CDN服务提供了自动图片处理服务，如原图片对象链接：
`http://7xsi10.com1.z0.glb.clouddn.com/60-footer.png`，在链接后面加上查询查询字符串：
`http://7xsi10.com1.z0.glb.clouddn.com/60-footer.png?imageView2/1/w/500/h/400`，就会将原图片剪裁到指定大小。

详细参考七牛官方开发者文档：
[https://developer.qiniu.com/dora/manual/3683/img-directions-for-use](https://developer.qiniu.com/dora/manual/3683/img-directions-for-use)

#### 其他
Google发布了了[Google PageSpeed](https://developers.google.com/speed/pagespeed/module/)这个服务器模块，可以在apache或ngnix中加载，通过在服务器配置文件中进行设置来进行自动化的优化