Canvas 实现图片上传压缩

背景

收集用户上传的图片是一件很常见的需求,然而随着现在设备像素越来越高,照片尺寸通常也越来越大。因此,在前端对用户上传的图片进行压缩变为一个刚需,尤其是在移动设备上,用户通常希望直接选择拍摄图片上传,由此有了本文。原理很简单,主要就是通过canvas读取图片重绘,所以以贴代码为主。
在线demo戳这儿

上代码

/**
* 图片压缩
* @param source {String|File}     传入待压缩图片来源,支持两种方式:
*                                   1直接传入一个网络图片url 
*                                   2传入img file对象
* @param options {Object}   压缩配置,示例如下:
*                           {
*                               width: 100, 设置width固定值,高度随比例缩放
*                               height: 100, 设置height固定值,宽度随比例缩放,同时设置则固定宽高
*                               maxWidth: 100, 设置最大宽度不大于100,高度随比例缩放
*                               maxHeight: 100, 设置最大高度不大于100,宽度随比例缩放
*                               maxSide: 100, 设置最大边不大于100,另外边随比例缩放
*                               ratio: 0.5, 设置尺寸宽高同时缩放到指定比例,取值范围 0-1
*                               //上述参数如果同时设置,优先级由上到下生效
*                               quality: 0.5, 图片输出质量,取值范围0-1
*                               type: 'image/jpeg' 图片输出格式
*                           }
* @param cb {Function}    处理结果的回调,参数为(err, file),第一个参数为出错时的error对象,第二个为处理后的图片file对象
* @return void
*/
function compressImg(source, options, cb) {
  var img = new Image();
  var finalFileName = 'newImage';

  img.onerror = function(err) {
    cb(err);
  };

  img.onload = function() {
    try {
      options = options ? options: {};

      // default width: origin width
      var finalWidth = this.width || 100;
      // default height: origin height
      var finalHeight = this.height || 100;

      // defalut quality: 0.6
      var finalQuality = options.quality || 0.6;

      // default type: 'image/jpeg'
      var finalType = options.type || 'image/jpeg';

      // calculate finalWidth & finalHeight
      if (options.width && options.height) {
        finalWidth = options.width;
        finalHeight = options.height;
      } else if (options.width && !options.height) {
        finalHeight = parseInt(finalHeight * (options.width / finalWidth), 10);
        finalWidth = options.width;
      } else if (options.height && !options.width) {
        finalWidth = parseInt(finalWidth * (options.height / finalHeight), 10);
        finalHeight = options.height;
      } else if (options.maxWidth) {
        if (finalWidth > options.maxWidth) {
          finalHeight = parseInt(finalHeight * (options.maxWidth / finalWidth), 10);
          finalWidth = options.maxWidth;
        }
      } else if (options.maxHeight) {
        if (finalHeight > options.maxHeight) {
          finalWidth = parseInt(finalWidth * (options.maxHeight / finalHeight), 10);
          finalHeight = options.maxHeight;
        }
      } else if (options.maxSide) {
        if (finalHeight >= finalWidth && finalHeight > options.maxSide) {
          finalWidth = parseInt(finalWidth * (options.maxSide / finalHeight), 10);
          finalHeight = options.maxSide;
        } else if (finalWidth > finalHeight && finalWidth > options.maxSide) {
          finalHeight = parseInt(finalHeight * (options.maxSide / finalWidth), 10);
          finalWidth = options.maxSide;
        }
      } else if (options.ratio) {
        finalWidth = parseInt(finalWidth * options.ratio, 10);
        finalHeight = parseInt(finalHeight * options.ratio, 10);
      }

      var canvas = document.createElement('canvas');
      canvas.width = finalWidth;
      canvas.height = finalHeight;
      var context = canvas.getContext('2d');
      context.clearRect(0, 0, finalWidth, finalHeight);
      context.drawImage(this, 0, 0, finalWidth, finalHeight);
      // 可以直接用canvas.toBlob,但是移动端兼容性一般
      var newBlob = dataURItoBlob(canvas.toDataURL(finalType, finalQuality), finalType);
      // 这一行ios设备可能会有兼容性问题(安卓OK),建议直接返回blob对象,不要调用new file生成新的file 对象
      var newFile = new File([newBlob], finalFileName, {
        type: finalType
      });
      console.log('After compress:' + newFile.size / 1024 + 'kb');
      cb(null, newFile);
    } catch(err) {
      cb(err);
    }

  };

  if (typeof source === 'string') {
    img.setAttribute('crossOrigin', 'anonymous');
    img.src = source;
    finalFileName = source;
  } else if (typeof source === 'object' && source.toString().indexOf('File') >= 0) {
    console.log('Before compress:' + source.size / 1024 + 'kb');
    var reader = new FileReader();
    reader.readAsDataURL(source);
    reader.onload = function(e) {
      img.src = e.target && e.target.result;
      if (source.name) {
        finalFileName = source.name;
      }
    };
  } else {
    cb(new Error('Error image source context'));
  }
};

function dataURItoBlob(dataURI, type) {
  var img = dataURI.split(',')[1];
  var decode = window.atob(img);
  /* eslint-disable */
  var ab = new ArrayBuffer(decode.length);
  var ib = new Uint8Array(ab);
  /* eslint-enable */
  for (var i = 0; i < decode.length; i++) {
    ib[i] = decode.charCodeAt(i);
  }
  return new Blob([ib], {
    type: type
  });
}

使用方法无比详细,就不多解释了。这里多说一点兼容性相关的问题。这个工具方法的callback返回的是一个新new的file对象,然而由于ios上存在的一些兼容性问题,方法最后返回的file对象在ios上大概率为空或者有各种问题,因此如果你是在移动端ios上使用此工具方法,建议修改一下,直接返回blob即可。另外,dataURLtoBlob方法其实有canvas的原生支持canvas.toBlob,只是兼容性较差,可以酌情考虑使用。
下面上几个使用的例子:


//Test upload
var input = document.querySelector('#test');
input.addEventListener('change', function(e) {
  var file = e.target.files[0];
  compressImg(file, {
    quality: 0.6,
    type: 'image/jpeg'
  },
  function(err, blob) {
    if (err) {
      console.log(err);
    } else {
      var url = URL.createObjectURL(blob);
      console.log(url);
    }
  });
});

//Test web url
compressImg("https://img.yzcdn.cn/upload_files/2017/11/27/FuBfCauOKWsx6aWKCEqRkB3RKXu7.png", {
    quality: 0.6,
    type: 'image/jpeg'
  },
  function(err, blob) {
    if (err) {
      console.log(err);
    } else {
      var url = URL.createObjectURL(blob);
      console.log(url);
    }
  });

The end

你可能感兴趣的:(Canvas 实现图片上传压缩)