专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

前端使用canvas压缩图片后再上传 js 图片压缩后上传

ins518 2024-09-25 22:49:34 技术文章 16 ℃ 0 评论

canvas 可以对图片进行压缩处理,原理及处理流程大致如下:

1、先将图片的 File 文件对象转成 base64 DataURL。

2、创建一个 Image 对象,src 接收文件的 base64 DataURL 后来获取图片的宽高和比例。

3、创建 canvas 画布,并依据 Image 对象的尺寸来设置画布的大小。

4、将图片绘制到 canvas 画布面。

5、使用 canvas 的 drawImage() 方法对图像进行压缩处理,并获取新的 base64 DataURL。

6、将 base64 DataURL 转换为文件对象。

7、如果服务端是接收 File 对象(文件流,二进制)来进行上传,前端可以通过 FormData.append("file", fileObject) 的方式来追加文件对象以进行上传。

将file文件对象转化为base64 DataURL

/**
 * 将文件对象转化为 base64 Data URL
 * @param {二进制文件流} file 
 * @param {回调函数,返回 base64 Data URL} callback 
 **/
readFileAsDataURL(file, callback) {
  // 如果file未定义,返回null
  if (!file) return callback(null);

  // 创建读取文件对象
  let fileReader = new FileReader();
  fileReader.readAsDataURL(file); // 读取file文件,得到的结果为base64位
  fileReader.onload = function() {
    let _this = this,
    imgBase64DataURL = _this.result; // fileReader 读取到的 base64 Data URL
    callback && callback(imgBase64DataURL);
  }
}

将base64 DataURL转换为文件对象

/**
 * 将 base64 Data URL 转换为文件对象
 * @param {baseURL} dataUrl 
 * @param {文件名称} filename 
 * @return {文件二进制流}
 */
unitedDataURLToFile(dataUrl, filename) {
  if (!dataUrl) return null;
  let arr = dataUrl.split(','),
  mime = arr[0].match(/:(.*?);/)[1],
  base64Str = atob(arr[1]),
  len = base64Str.length,
  unit8Arr = new Uint8Array(len);
  while (len--) {
    unit8Arr[len] = base64Str.charCodeAt(len);
  }
  return new File([unit8Arr], filename, {
    type: mime
  });
}

压缩方法(关键)

/**
 * 压缩方法
 * @param {参数obj} parms 
 * @param {文件二进制流} parms.file ,必传
 * @param {优化目标文件大小} parms.optimumSize ,不传初始赋值 0
 * @param {输出图片宽度} parms.width ,不传初始赋值 0,按比缩放无需传高度
 * @param {输出图片名称} parms.fileName ,不传初始赋值 image
 * @param {压缩图片程度} parms.quality ,值范围0~1,不传初始赋值 0.92。
 * @param {回调函数} parms.done ,压缩后的回调函数,压缩成功将返回 file 对象,不成功则返回错误信息
 **/
compress(parms) {
  //文件读取与图片加载等事件都是异步的,因此需要在回调函数中才能获取返回值
  if (parms && parms.done) {
    //如果file没定义,返回null
    if (parms.file == undefined) return parms.done(null);

    let _this = this;

    //给参数赋初始值
    parms.optimumSize = parms.hasOwnProperty("optimumSize") ? parms.optimumSize: 0;
    parms.width = parms.hasOwnProperty("width") ? parms.width: 100;
    parms.filename = parms.hasOwnProperty("filename") ? parms.filename: "image";
    parms.quality = parms.hasOwnProperty("quality") ? parms.quality: 0.92;

    // 得到文件类型
    let fileType = parms.file.type;
    // console.log(fileType) //image/jpeg
    if (fileType.indexOf("image") == -1) {
      console.log("请选择图片文件进行压缩理");
      return parms.done({
        code: -1,
        msg: "Only_Required_Image"
      });
    }

    // 读取 file 文件,得到的结果为 base64 字符串 Data URL
    _this.readFileAsDataURL(parms.file,
    function(base64DataURL) {
      if (base64DataURL) {
        //如果当前 fileSize 比优化目标小,直接输出
        let fileSize = parms.file.size;
        if (parms.optimumSize > fileSize) {
          console.log("无需压缩");
          //直接返回原文件信息,parms.file 是一个 JS File 对象,dataURL 是图片的 base64 DataURL
          return parms.done({
            code: 0,
            msg: "Need_Not_Compress",
            data: {
              file: parms.file,
              dataURL: base64DataURL
            }
          });
        }

        let image = new Image();
        image.src = base64DataURL;
        image.onload = function() {
          let _self = this,
          scale = _self.width / _self.height; // 获得长宽比例
          // console.log(scale);
          //创建一个canvas,并获取其上下文					
          let canvas = document.createElement('canvas'),
          context = canvas.getContext('2d');

          //获取压缩后的图片宽度,如果width为0,默认原图宽度
          canvas.width = parms.width == 0 ? _self.width: parms.width;

          //获取压缩后的图片高度,如果width为0,默认原图高度
          canvas.height = parms.width == 0 ? _self.height: parseInt(parms.width / scale);

          //把图片绘制到canvas上面
          context.drawImage(image, 0, 0, canvas.width, canvas.height);

          //压缩图片,获取到新的base64Url
          let newImageData = canvas.toDataURL(fileType, parms.quality);

          //将base64转化成文件流
          let resultFile = _this.unitedDataURLToFile(newImageData, parms.filename);

          //判断如果 optimumSize 有限制且压缩后的图片大小比优化目标大小大,就弹出错误
          if (parms.optimumSize > 0 && parms.optimumSize < resultFile.size) {
            console.log("未优化到目标压缩大小!上传图片尺寸太大,请重新选择合适图片上传");
            parms.done({
              code: -1,
              msg: "Image_Too_Large"
            });
          } else {
            //返回文件信息,resultFile 是一个 JS File 对象,dataURL 是压缩图的 base64 DataURL
            parms.done({
              code: 0,
              msg: "Complete",
              data: {
                file: resultFile,
                dataURL: newImageData
              }
            });
          }
        }
      }
    });
  }
}

当然,上述三个方法是写在一个 ES module 里的,完整的代码如下:

使用示例

新建一个示例页面,主要放置一个 <input type="file"> 控件,然后监听其 change 事件,在事件中调用压缩图片的方法。

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
	<meta name="renderer" content="webkit" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
	<title>前端压缩图片示例</title>
	<style>
		body {
			font-size: 16px; 
			font-family: 'PingFang SC','Microsoft YaHei','Helvetica Neue','Helvetica','Arial',sans-serif;
		}
		.form-group {
			display: flex;
			margin-bottom: .5rem;
		}
		.file-upload {
			line-height: 1.25rem;
			background-color: #fff;
			border: 1px solid #d4d4d4;
			border-radius: .125rem;
		}
		.quote {
			text-align:center; 
			color: lightgray;
		}
	</style>
</head>
<body>
	<section>
		<form name="form1">
			<div class="form-group">
				<div><label>上传图片:</label></div>
				<div>
					<input type="file" name="file" id="fileUpImg" accept="image/*" class="file-upload"/>
				</div>
			</div>
			<div class="form-group">
				<div><label style="color: transparent">图片预览:</label></div>
				<div>
					<div class="preview"><img id="compressionPreview" alt="预览图" src="./assets/img/default.jpg" width="256" /></div>
					<div class="quote"><label id="compressionTips">请选择图片上传,稍后将进行压缩</label></div>
				</div>
			</div>
		</form>	
	</section>
	
	<!--Recommended Scripts Position-->
	<script type="module">
		import {ImageCompressor} from './utils/image-compressor.js';
		var imgCompressor = new ImageCompressor(),
			fileUpImg = document.getElementById("fileUpImg"),
			compressionPreview = document.getElementById("compressionPreview"),
			compressionTips = document.getElementById("compressionTips");
		
		//监听上传控件事件
		fileUpImg.onchange = function(){			
			compressionTips.textContent = "压缩处理中,请等待完成…";
			let _this = this, fileObj = _this.files[0];
			
			//压缩图片,这里优化目标大小设为 100KB (可能优化不成功);如果优化目标大小设为0,表示只要压缩就行,不期望达到某一目标大小
			imgCompressor.compress({
				file: fileObj,
				optimumSize: 100*1024,
				width: 480,
				quality: 0.95,
				done: function(res){
					console.log(res);
					//如果压缩成功
					if(res && res.code == 0){
						//TODO
						let data = res.data;
						if(data.dataURL) {
							compressionPreview.src = data.dataURL;
							compressionTips.textContent = "压缩成功!";
						}
						
						//新建一个 FormData 对象
						let formData = new FormData();
						formData.append("file", data.file); //加入文件对象
						
						//可使用 Ajax 将 FormData 上传到服务端
						//$.ajax({
						//	url: "xxx",
						//	type: "POST",
						//	data: formData,
						//	sunccess: function(res){}
						//});
					} else {
						compressionTips.textContent = "压缩失败,未优化达到目标";
						console.log("Compress Failed. Image Too Large");
					}
				}
			});
		};		
	</script>
</body>
</html>

测试结果

本示例项目参考了网上的一些代码!本来想做一个压缩图片的递归算法,直到图片大小符合优化期望。后来发现,如果优化目标大小设置得比较小,图片如何进行压缩都无法满足条件时,会造成类似“死循环”而无法跳出,浪费资源。还有一种情况就是图片进行几次压缩之后,文件大小不会有多大改变了,有时还会增加。无法有效解决此类问题,遂放弃了递归压缩。

(完)

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表