前端图片解决方案总结

402 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

本篇文章是我针对自己项目中图片相关问题整理的解决方案, 图片在我项目中是耗费最多的,主要有两个限制, 一个是量大、使用频繁; 另一个是为了保证清晰度, 图片必须保留一定的大小。

这里我概括成了以下几个方面【注: 图片是用阿里云的OSS存储的】

  • 上传时压缩+缩略图
  • 图片懒加载+预加载
  • 渐进式图片加载

图片上传时压缩

这里就用到了canvas进行压缩, 我把它分为了以下几个步骤

  1. 文件转成DataURL
  2. DataURL 转成 Image
  3. Image 转成 Canvas (控制大小)
  4. Canvas 导出成 base64 或者 file 类型(控制质量)

代码我封装好后放在我的代码仓库里了, 大家可以直接拿下来用

1. 文件转成DataURL

/**
 * 将file类型文件转成DataURL
 * @param file 文件
 * @param cb 参数是转成的url
 */
function fileToDataURL(file, cb) {
  let reader = new FileReader();
  reader.onloadend = function (e) {
    cb(e.target.result);
  };
  reader.readAsDataURL(file);
}

2. url 转成 image 对象

/**
 * 将dataurl 转成image对象
 * @param dataurl
 * @param cb
 */
function dataUrlToImage(dataurl, cb) {
  let img = new Image();
  img.onload = function () {
    cb(img);
  };
  img.src = dataurl;
}

3. 图片转成canvas

/**
 * 将图片转成canvas, 此时可设置图片宽高
 * @param image
 */
function imageToCanvas(image, width, height) {
  let cvs = document.createElement("canvas");
  let ctx = cvs.getContext("2d");
  if (width === 0 && height === 0) {
    width = image.width;
    height = image.height;
  } else if (width === 0) {
    width = image.width * (height / image.height);
  } else if (height === 0) {
    height = image.height * (width / image.width);
  }
  ctx.width = width;
  ctx.height = height;
  ctx.drawImage(image, 0, 0, width, height);
  return cvs;
}

4. canvas 转成 base64 或 file

/**
 * 将canvas 转成一个dataurl 字符串
 * @param canvas
 * @param quality
 */
function canvasResizeToDataURL(canvas, quality) {
  return canvas.toDataURL("image/jpeg", quality);
}

/**
 * 将canvas压缩转变为一个Blob对象
 * @param canvas
 * @param quality
 * @param cb
 */
export function canvasResizeToFile(canvas, quality, cb) {
  canvas.toBlob(
    function (blob) {
      cb(blob);
    },
    "image/jpeg",
    quality
  );
}

5. 封装

最后大家可以根据自己的需求进行组合封装, 下面的是压缩+改大小最后转成file类型上传的封装。

若要转成缩略图, 只用把质量调地点, 然后宽度根据实际情况设置, 最后再转成url即可

/**
 * 图片调整大小和质量, 即压缩图片, 最终返回file类型
 * @param file 获取图片时的file
 * @param quality 图片质量 0 ~ 1
 * @param cb 回调函数,参数为最终的file
 * @param width 图片宽, 若宽高只定义一个, 则按比例缩放
 * @param height 图片压缩后的高
 */
function fileResizeToFile(file, quality, cb, width = 0, height = 0) {
  fileToDataURL(file, function (dataurl) {
    dataUrlToImage(dataurl, function (image) {
      canvasResizeToFile(imageToCanvas(image, width, height), quality, cb);
    });
  });
}

图片懒加载+预加载

这个我之前写过一篇文章, 这里就直接引用原链接了哈。

这里代码实现不复杂, 就是逻辑要适合自己的, 比如我自己的项目中是一个作业批改页面。

  • 那么我首先肯定把要批改的第一个学生的第一章图片设置较高的优先级
  • 其余在图片列表里的图片, 就用上传时存储的base64 展示
  • 然后其他图片和前后两个学生的图片设置较低的优先级,让它们自己在后台加载去

渐进式图片加载

这里简单概括下就是 先用base64形式加载, 等高清图加载完成后, 把src替换过来即可。

这里要注意的就是, 高清图的加载需要自己去new 一个Image, 加载, 等加载完成后,把原DOM树上的模糊图的 Img 替换掉即可。

这里的思路是借鉴这个仓库的代码我自己的代码, 没怎么改动, 这里写下思路, 代码我就补贴了, 占位置了。

  1. 设置监听事件, 针对多图和响应式图, 需要给scroll, wheel, mousewheel,resize,touchmove 设置监听事件
  2. 工具函数, 可以把防抖,监听和移除监听放进去
  3. 主函数, 主函数里获取所有图片的节点,并判断是否在可视区域内, 在的话就加载图片。
  4. 加载图片, 新建一个Image对象, 监听器onload事件, 然后再做替换即可。 如果要做动画, 可以给新的image 写样式
  5. 挂载新节点, 通过旧节点获取旧节点后就可以挂载了

总结

以上就是我项目中目前在用到的图片相关的解决方案, 还有像压缩、图片格式、响应式等这些, 涉及到的不多, 基本上是围绕webpack、src-set这类来的, 今后遇到其他需求后再补充。

这是我的个人博客, 欢迎访问。