前端性能优化(二):图片

1,799 阅读8分钟

  图片的优化可以说是性能优化里经常涉及的一个问题了,图片在网站中体量的平均占比已经超过了 50%,而网络不好的时候图片加载太慢极大的影响用户体验。所以我自己结合自己的项目经历和学习总结出了这篇文章。这篇文章很多地方我参考了一篇大佬写的文章,这篇文章涉及的面很多,虽然没有深挖,但是很适合作为入门了解自己对哪些方面有兴趣可以自己再去深挖实践。(前端性能优化之旅)

图片优化总结起来有几个主要的点:

  • 选择正确的图片格式(在图片质量上肉眼上往往很难看出差距,所以更重要的是体积大小);
    • JPG/JPEG:压缩比高的情况下色彩表现还很优秀。比较适合一些首页开屏的大图使用,但是缺点就是高压缩比的情况下还是会有一些锯齿感,对一些需要精细的图片如logo不建议使用。
    • PNG:常用来做透明背景的图片,色彩保存的也很完整,保留了很多细节的东西(比如消除了锯齿)所以往往体积比较大,更适合做图标或者logo这种小图片使用。
    • WEBP:能在和PNG有同等质量的情况下比PNG压缩比例更高,但是有些浏览器对其兼容并不好。
  • 选择合适的图片大小去适配不同屏幕的尺寸;
  • 根据不同的需求做适当的图片压缩,不能一刀切,比如需要高质量的摄影类的图片压缩成小图,文件大小是小了但是需求上就出问题了;
  • 图片加载的优先级;
  • 延迟加载;

根据网络情况来改变图片尺寸大小

  通过判断用户的网络来动态设置要显示的图片的尺寸或者清晰度。WIFI或者4G5G可以显示高清大图,而2G的时候可以显示低质量的模糊图片这样不会让页面空白太多影响体验。

  • 微信小程序

    获取用户的网络状态

  • Web端

  web端暂时只能检测是否网络在线,无法获取用户更多的信息,此时我们可以换一种思路,通过接口请求的时间来判断网络好坏,比如设置1s的时间内接口还未返回结果或者img标签的onload事件还未触发,这个时候可以把图片动态改为低质量的图片。 而检测网络是否在线有几种方法:

  • navigator.onLine
    • 问题在于navigator.onLine只会在没有连接到路由器或者路由器时返回false,就算路由器只是单纯的连上了路由器但是没网络也会显示true,所以这种方法不靠谱。
  • 网络请求
    • 根据接口请求的返回值来判断,这个就没什么好说的了。
  • 绑定事件
    • 设置一个变量保存网络状态即可,判断变量来决定用哪种图片
window.addEventListener("online", ()=>{
   this.isOnline = true //在线
});  
window.addEventListener("offline", ()=>{
   this.isOnline = false //离线
});  

响应式图片

  根据某些条件比如窗口大小动态展示不同的图片。尽量根据需要来返回不同尺寸大小的图片而不是返回一个大图然后去改变图片的大小,这样可以避免不必要的网络损耗。

  • 获取窗口大小

	let windowsWidth = 0; //窗口宽度
	let windowsHeight = 0; //窗口高度
	// 获取窗口宽度
	if (window.innerWidth) {
	   windowsWidth = window.innerWidth;
	} else if (document.body && document.body.clientWidth) {
	   windowsWidth = document.body.clientWidth;
	}
    
	// 获取窗口高度
	if (window.innerHeight){
	   windowsHeight = window.innerHeight;
	} else if (document.body && document.body.clientHeight) {
	   windowsHeight = document.body.clientHeight;
	}
	// 通过深入Document内部对body进行检测,获取窗口大小
	if (document.documentElement  && document.documentElement.clientHeight && document.documentElement.clientWidth) {
	   windowsHeight = document.documentElement.clientHeight;  
	   windowsWidth = document.documentElement.clientWidth;
	}
  • css媒体查询

  使用@media查询,你可以针对不同的媒体类型定义不同的样式。@media可以针对不同的屏幕尺寸设置不同的样式,特别是如果你需要设置设计响应式的页面,@media是非常有用的。当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。相关文档

@media screen and(max-width:640px){  
   my_image{width:640px;}
}
  • img标签属性srcset

  srcset是根据你配置的大小按需加载图片,即使你配置了很多张一次也只会根据屏幕尺寸请求一张图片。 具体的介绍可以看这篇知乎的帖子

<img sizes="100vw" srcset="img_100.png 100w,img_200.png 200w,img_400.jpg 400w" />
  • picture标签

  根据屏幕匹配的不同尺寸显示不同图片,如果没有匹配到或浏览器不支持picture属性则使用img元素。

<picture>
  <!-- 屏幕尺寸大于等于800px -->
  <source media="(min-width: 800px)" srcset="xxxx.jpg">
  <!-- 屏幕尺寸小于等于400px下 -->
  <source media="(max-width: 400px)" srcset="xxxx.jpg">
  <!-- 没有匹配到或浏览器不支持picture -->
  <img src="xxxx.jpg">
</picture>

逐步加载图片

  通过监听页面滚动,判断图片是否进入视野,在还没有下滑到屏幕可见区域时可以选择不加载图片,或者加载模糊图片,当然也可以先加载模糊图等到模糊图加载完毕以后就加载原图,这样等用户拉到下面以后看到的已经是原图了。比较常见的网站里,知乎就是采用的先加载模糊图片然后加载原图的方法。

mounted() {
   //监听页面滚动
   window.addEventListener("scroll", util.throttle(() => {
      console.log(document.documentElement.scrollTop);
   },20));
   
   
   //监听页面内某容器滚动
   document.getElementById(`元素id`).addEventListener("scroll",util.throttle(() => {
      console.log(document.getElementById(`元素id`).scrollTop);
   },20));
},


//util.js 
//函数节流:规定一个单位时间,在该时间内,只能有一次函数回调,即使多次请求也只有一次生效
function throttle(fn, time) {
    let timer = true
    return () => {
        if (!timer) {
            return
        }
        timer = false
        setTimeout(() => {
            fn.call(this, arguments)
            timer = true
        }, time)
    }
}

module.exports = {
   throttle
}
  • vue-lazyload(推荐)

  因为笔者是用的Vue框架所以这里推荐的就是Vue的插件,git的官方地址。它的可扩展性和优化都要比原生的loading="lazy"属性好。如下图所示,在同样的高度下,原生会多加载更多的还没有进入界面的图,而vue-lazyload加载的更少。

image.png

//main.js

import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload)

Vue.use(VueLazyload, {
    error: "xxxxx.png",//加载失败时显示的图片
    loading: "xxxxx.png",//还在加载时显示的图片
})

new Vue({
  el: 'body',
  components: {
    App
  }
})
//template.vue

<img v-lazy="img.src" >
  • 使用统一占位符

    • 在未加载出图片或者加载失败时,可以使用一个本地的默认图片或者loading动画占位。加载完毕以后再替换即可。
    • 使用方法:
      • 使用伪元素 使用绝对定位,让伪元素的背景图覆盖掉原图片。要注意的点是,伪元素的图片不能是透明图,不然就会出现以下情况,加载失败的图没有被挡住。

//HTML
<img class="img_s" src="xxxxxxx"/>

//CSS
.img_s {
  height: 200px;
  width: 200px;
  position: relative;
}

.img_s::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-size: 100% 100%; //让图片和父元素同比例宽高,否则会只显示一部分图片
  background-image: url("xxxxxxx");
}
  • LQIP(低质量图像占位符):Low Quality Image Placeholders

    • 安装 npm install lqip
    • LQIP的GitHub地址
    • 使用方法:
      • 在 index.js中写入以下内容,然后在本地终端调用命令node index.js运行该文件。
      // node环境
      const lqip = require('lqip');
      //文件路径
      const fileUrl = 'xxxxxxxxxx';
      
      //将输入的图片转为base64
      lqip.base64(fileUrl).then(res => {
         console.log("将输入的图片转为base64:",res); 
      });
      
        直接将这段base64的代码放到img标签的src中即可,左边是经过处理的模糊图片,右边是原图,演示效果如下图所示。而且从右下角的图片大小可知,原图有10.8K,模糊处理以后只有903B,缩小到了只有原来的十分之一不到。当然你要是这种情况下你还加载不出来图片。。。。那这种情况下使用默认图片占位符就好了。
  • SQIP(基于SVG的图像占位符):SVG Quality Image Placeholders

    • 安装 npm install sqip
    • SQIP的GitHub地址
    • 使用方法和LQIP一样:
      • 在 index.js中写入以下内容,然后在本地终端调用命令node index.js运行该文件。
      // node环境
      const sqip = require('sqip');
      const result = sqip({
         filename: 'xxxxxxxxx',//文件路径
         numberOfPrimitives: 10 //可根据不同应用场景设置大小
      });
      
      console.log("将输入的图片转为SVG:", result.final_svg);
      
        将生成的字段直接放在HTML文件中的DOM结构即可,用法和普通标签一样。
  • 原生loading="lazy"属性

  这个是img标签自带的属性值,但是自定义和扩展性比较差,好处就是写起来简单。

<div v-for="(item, index) in arr" :key="index">
    <img loading="lazy" :src="item" />
</div>

其他

  • Web Font

    • 字体图标的好处就在于占用空间小加载快,导入相应的字体库即可。
  • image spriting雪碧图

    • 将大量小图标合成为一张图片,设置成背景图片,然后通过设置background-position的值来显示不同位置的图片。就像是你用一个带孔的纸板盖在一张画满了图标的纸上。孔露出的位置就是显示的图片,而孔的位置就是你设置的background-position值。CSS sprites在线合成工具
      • 优点:可以减少HTTP请求,一般浏览器的HTTP并发请求在6到10次之间,图片太多请求的HTTP自然就多,而现在一次就行。
      • 缺点:在要增删改图的时候可能会导致大面积的修改,因为整个大图可能都会被影响,极其不方便频繁调整。
      • 使用:
      //设置为背景图而不是img的src属性
      background-image: url('../../static/images/css_sprites.png');
      
      //调整要显示的图片
      background-position: 0px 0px;
      
      //设置背景图的大小也就是改变图标的大小保持原比例即可(x和y轴)
      background-size: 100px 100px;
      

总结

  作为要经常使用的图片加载的优化最好做成组件。优化的方法要根据自己项目的实际情况来,不同的模块可能使用的优化方法也不一样,而不是一股脑的全用上,过分优化会矫枉过正没有意义。