kekobin/blog

H5获取相册图片进行编辑裁剪上传

kekobin opened this issue · 0 comments

基础知识

dataURL

一个完整的 dataURI 应该是这样的:

data:[<mediatype>][;base64],<data>

其中mediatype声明了文件类型,遵循MIME规则,如“image/png”、“text/plain”;之后是编码类型,这里我们只涉及 base64;紧接着就是文件编码后的内容了。我们常常在 HTML 里看到img标签的src会这样写:

src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"

这个img引用的就是以 base64 编码的 dataURL 了,只要浏览器支持,就可以被解码成声明格式的图片并渲染出来。

.toDataURL()

canvas.toDataURL([type, encoderOptions]);
  • type: 图片格式,默认为 image/png
  • encoderOptions: 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。(这可以用于图片压缩,即指定质量为<1的就可以将原图片压缩到一定的比例)

图片加载是异步的,在转换成 dataURL 前必须先确保图片成功加载到,否则让 canvas 即刻执行绘制可能失败,从而导致转换 dataURL 失败。于是.toDataURL()方法应该写在的onload事件中,以确保 canvas 的绘制工作在图片下载完成后开始。好在.drawImage()方法是同步的,只有在 canvas 绘制完成后才会执行后续操作,比如.toDataURL()。

function getBase64(url){
        //通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
        var Img = new Image(),
            dataURL='';
        Img.src=url;
        Img.onload=function(){ //要先确保图片完整获取到,这是个异步事件
            var canvas = document.createElement("canvas"), //创建canvas元素
                width=Img.width, //确保canvas的尺寸和图片一样
                height=Img.height;
            canvas.width=width;
            canvas.height=height;
            canvas.getContext("2d").drawImage(Img,0,0,width,height); //将图片绘制到canvas中
            dataURL=canvas.toDataURL('image/jpeg'); //转换图片为dataURL
        };
    }

调取手机相册和相机

使用的是类似input[type=file]文件上传的形式,只不过在手机端会加上一些配置,从而能默认识别到相册 :

<form id="uploadForm" enctype="multipart/form-data">
      <input class="upload-open-photo" accept="image/*" type="file" id="filechooser" @change="btnUploadFile($event)"/>
    </form>

form-enctype: 设置或返回表单用来编码内容的 MIME 类型,默认值是"application/x-www-form-urlencoded",当 input type 是 "file" 时,值是 "multipart/form-data"。

处理上传的图片为dataURL

btnUploadFile(e) {
  // 获取图片
  const { files } = e.target;
  const file = files[0];
  const self = this;

  // 接受 jpeg, jpg, png 类型的图片
  if (!/\/(?:jpeg|jpg|png)/i.test(file.type)) {
    return;
  }

  const reader = new FileReader();
  reader.onload = function onload() {
    // 经过reader.readAsDataURL读取,这里的result为base64格式
    const { result } = this;

    let img = new Image();
    // 进行图片的渲染
    img.onload = () => {
      // 对图片进行压缩,返回的是压缩后的图片的base64
      const ret = self.compress(img, file.type);
      self.filename = file.name;
      // self.imgSrc = ret;
      // 对压缩后的图片进行裁剪
      self.initCroppie(ret);
      img = null;
    };
    img.src = result;
  };
  // 将file读取成dataURL格式
  reader.readAsDataURL(file);
}

对读取成dataURL的图片进行压缩

压缩处理步骤:

  • 利用 canvas 的 drawImage 将目标图片画到画布上
  • 利用画布调整绘制尺寸,以及导出的 quality ,确定压缩的程度
  • 利用 canvas的 toDataURL 或者 toBlob 可以将画布上的内容导出成 base64 格式的数据。
compress(img, fileType) {
      const drawWidth = img.width;
      const drawHeight = img.height;

      let canvas = document.createElement('canvas');
      canvas.width = drawWidth;
      canvas.height = drawHeight;

      let context = canvas.getContext('2d');
      // 先画到画布上
      context.drawImage(img, 0, 0, drawWidth, drawHeight);
      // 然后利用画布的功能进行压缩,压缩0.5就是压缩百分之50
      const base64data = canvas.toDataURL(fileType, 0.5);
      canvas = null;
      context = null;

      return base64data;
}

如果要调整图片方向的,可使用canvas的rotate:

const degree = 180;
drawWidth = -drawWidth;
drawHeight = -drawHeight;
context.rotate(degree * Math.PI / 180);

对压缩后的图片进行裁剪

使用的是Croppie 这个库实现放缩、移动、裁剪功能,但是其只支持相对或者绝对地址的图片url,所以先需要把我们的dataURI格式图片上传到服务器生成url。

dataURI转换成blob

由于上传接口只支持blob(file)格式,所以需要先把dataURI转换成blob:

// dataURI is base64 format
dataURItoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0) {
  // 解码base64成为 binary string
    byteString = atob(dataURI.split(',')[1]);
  } else {
    byteString = unescape(dataURI.split(',')[1]);
  }
  // separate out the mime component
  const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // binary string没法转成blob的, 所以先转成ArrayBuffer形式,这里使用
  //Uint8Array形式,将binary string字符串存进去,然后就可以使用 new Blob转换了
  // write the bytes of the string to a typed array
  const ia = new Uint8Array(byteString.length);
  for (let i = 0; i < byteString.length; i += 1) {
    ia[i] = byteString.charCodeAt(i);
  }

  // Blob有兼容性问题,需要兼容处理
  let blob;
  try {
    blob = new Blob([ia], { type: mimeString });
  } catch (error) {
    const BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
    const bb = new BlobBuilder();
    bb.append([ia]);
    blob = bb.getBlob(mimeString);
  }
  return blob;
}

上传blob

async upload(base64) {
  const blob = this.dataURItoBlob(base64);
  const fd = new FormData();
  fd.append('filename', blob, this.filename);

  return uploadImg(fd);
}

实现裁剪

初始化

async initCroppie(base64) {
  // 现将dataURI转成blob进行上传(因为上传只支持blob格式),得到上传后的图片url
  // 因为裁剪使用的是Croppie,只支持相对或者绝对的图片url进行初始化
  const uploadRet = await this.upload(base64);
  const el = document.getElementById('croppie-container');

  this.croppieTarget = new window.Croppie(el, {
    viewport: { width: 100, height: 100 },
    boundary: { width: 300, height: 300 },
    showZoomer: false,
    enableOrientation: false // 这里注意,填false,否则默认图片会反过来
  });

  this.croppieTarget.bind({
    url: uploadRet.data, // 这里是图片的url
    orientation: 4
  });
}

裁剪可通过一个按钮触发

croppie() {
  this.croppieTarget.result('blob').then(async (blob) => {
    // do something with cropped blob
    console.log(blob);
    const fd = new FormData();
    // 注意,这里要把filename放在第三个参数带进去,因为blob里面是没有的
    fd.append('filename', blob, this.filename);

    const res = await uploadImg(fd);
    this.imgSrc = res.data;
  });
}

参考

利用 canvas 压缩图片
移动端 h5 照片的处理
HTML5调用照相机并自定义显示获取到的图片
用canvas的toDataURL()将图片转为dataURL(base64)
H5拍照上传填坑