Canvas 实现图片上传压缩

作者: changchao | 来源:发表于2018-01-02 22:49 被阅读43次

    背景

    收集用户上传的图片是一件很常见的需求,然而随着现在设备像素越来越高,照片尺寸通常也越来越大。因此,在前端对用户上传的图片进行压缩变为一个刚需,尤其是在移动设备上,用户通常希望直接选择拍摄图片上传,由此有了本文。原理很简单,主要就是通过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,只是兼容性较差,可以酌情考虑使用。
    下面上几个使用的例子:

    <input type="file" id="test">
    
    //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 实现图片上传压缩

        本文链接:https://www.haomeiwen.com/subject/umhcnxtx.html