Skip to content

图片

web 优化专家一定是图片优化专家

显示优化

  • 缩略图到原图过渡

web 前端图片加载优化,从图片模糊到清晰的实现过程

图片格式

不同格式适用场景

无损,压缩级别只是编码不同?

格式使用场景劣势
JPG/JPEG1. 大的背景图; 2. 轮播图; 3. Banner 图 4. 照片
PNG1. 小 Logo; 2. 透明背景
GIF动态图片
SVG能适应不同设备且画质不能损坏的图片
Base64大小不超过 2KB,且更新率低的图片无法缓存,不适合大图片
WebP现代浏览器

PNG - 维基百科,自由的百科全书

响应式展示

srcset/sizes

Use Imagemin to compress images  |  web.dev

占位图工具

iph

h2 lazy

问题:一般期望首屏图片尽快下载完,但使用了 H2,优先级低的图片可能拖慢优先级高的图片下载,

  • h2 并行,对下行带宽来说,相当于在请求一个合并文件
  • 开发工具,切换到 image 标签,可以看当图片请求数,总请求大小
  • 图片总体积 9Mb,网速 500KB/s,那 9 * 1000 / 500 = 18,理论要 18s 全部下载完
  • 并行请求,充分利用带宽,但导致单个资源可用带宽爱限
  • 假定 50 张图片,每张图片分配的下行 500 / 50 = 10 kb/s,实际考虑有的图片较小,下载完会释放
  • 带来的问题,优先级高的图片下载也慢了
  • 用户带宽限制,服务器带宽一般远大于用户带宽

第 2、3 张走了不同的域名,所以快。第一张因为首页 h2 并发下载图片太多了(56 个请求,9Mb 大),4Mb 带宽下 较大图片并行请求 20 张每张分 配大概是 50kb/s,一张 200kb 的图至少需要 4s (实测,swiper 第一张 5s 多)

结论:

  • 所以用 h2,如果图片比较多的话,需要给图片排优先级(优先级低的使用懒加载),优先保障当前视口内图片加载,限制一定时间内请求数据量
  • 图片上单独 cdn

lazy load

chrome 原生已支持

默认已经按优先级加载。使用 lazy 可以节省资源

传统方法 scroll 性能不好

IntersectionObserver 交叉观察者接口,异步,空闲时执行

Chrome 51+,不支持 IE,兼容性很好

触发加载规则:图片元素出现在视口内,即检查元素与父元素或视口(root)是否交叉,交叉比例 threshold

应用场景:lazy load,无限滚动 IntersectionObserver - Web APIs | MDN

缺点:依赖 JS 才能正确设置 src,Chrome 原生的好处

Tips for rolling your own lazy loading | CSS-Tricks

示例 Interaction observer example

webp

  • 支持检测原理,src base64 onload
  • polyfill 支持原理,打包了libwebp解码库,webp -> image data -> canvas -> done
  • 兼容性:IE11/Safari
  • 文件 Giziped 95KB

chase-moskal/webp-hero: browser polyfill for the webp image formatEssential Image Optimization

高清图

Sketch用一倍图做设计稿还是二倍图?|UI|教程|Olaf_Chou - 原创文章 - 站酷 (ZCOOL)

二倍图设计默认导出的图片是 2x 高清的,可直接使用。 二倍图导出时省去了点击+号 再选 2x ,操作方便。

图片压缩

表单图片压缩

  • 图片 → canvas 压缩 → 图片
  • canvas 设置宽度
  • toBlob(),转换成二进制,后端友好

imageConversion 大文件 png 测试

图片预览,可以用 URL.creatObjectUrl(blob) 或 FileReader.readAsDataURL(blob)

在线压缩

Squoosh

本地手动压缩

imageoptim-cli 不支持 svg,因为已经有 svgo

注意参数需要有引号

imageoptim './*'

gui 更全面,且支持异步同步调用

自动优化图像  |  Web Fundamentals  |  Google Developers

工具压缩 gulp

  • 首先原图往往尺寸很大,先缩小
  • 把大文件挑出来,用智图的在线服务,压缩效果好,它的 gulp 工具依赖在线 api,不支持多层目录,有待观察
  • gulp-imagemin 支持多层目录
js
gulp.task('imagemin', function() {
  gulp
    .src('./h5/group4/M00/**/**/*.{png,jpg,gif,ico}')
    .pipe(imagemin())
    .pipe(gulp.dest('./h5/group4/M00-dist/'))
})

图片预览

  • filereader onload

处理上传

  • blob 必须设置 name
js
  blobToFile (theBlob, fileName) {
      theBlob.lastModifiedDate = new Date()
      theBlob.name = fileName
      return theBlob
  },

  const newFile = this.blobToFile(blob, 'upload.png')
  const formData = new FormData()
  formData.append('file', newFile, newFile.name)

原理:Vue 实现图片预览、裁剪并获取被裁剪区域的 base64(无组件) - hhzzcc_的博客 - CSDN 博客

Vue+element-ui 图片上传剪裁组件 - 掘金

  • element-ui
pug
  el-upload.avatar-uploader(action='https://jsonplaceholder.typicode.com/posts/', :show-file-list='false', :on-success='handleAvatarSuccess', :before-upload='beforeAvatarUpload')
      el-button(type='primary') 上传

AVIF

AV1 video keyframe

All we need to do is repack AVIF as a single-frame AV1 video and decode it using native decoder. polyfill 原理:将图片重打包成 AV! 视频,再复用浏览器原生支持能力解码 AVIF -> MP4 -> <video> -> canvas -> output.bmp喂给浏览器,不必再压缩 SW 不能创建<video>,需要主线程配合

保持比例

object-fit: contain;

background-size: contain;

图片压缩原理

TODO

优化思路

  1. 选择合适格式 png jpg webp avif
  2. 减少请求 base64 http 1.1 雪碧图

同步处理阻塞

onload 中进行像素处理,可能产生阻塞

示例:getImageData 是处理像素的耗时任务

js
var img = new Image();
img.src = 'yourImage.jpg';

img.onload = function() {
  var canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  var ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);

  // 在图像加载完成后,可以使用 getImageData 了
  var imageData = ctx.getImageData(x, y, width, height);
  var data = imageData.data;

  // 处理像素数据
};

优化:将耗时任务放到 Promise 中

js
var img = new Image();
img.src = 'yourImage.jpg';

img.onload = function() {
  // 使用 createImageBitmap 异步处理图像
  createImageBitmap(img).then(function(bitmap) {
    var canvas = document.createElement('canvas');
    canvas.width = bitmap.width;
    canvas.height = bitmap.height;
    var ctx = canvas.getContext('2d');
    ctx.drawImage(bitmap, 0, 0);

    // 现在可以使用 getImageData 了
    var imageData = ctx.getImageData(x, y, width, height);
    var data = imageData.data;

    // 处理像素数据
  });
};