首页 > 技术文章 > 利用canvas对上传图片进行上传前压缩

zhensg123 2018-07-19 21:54 原文

利用谷歌调式工具发现,图片大小直接影响着首屏加载时间。

且考虑到后期服务端压力,图片压缩特别必要。

本文是前端利用canvas实现图片。参考文章:https://www.cnblogs.com/007sx/p/7583202.html

本文将其改为插件形式,适合单文件压缩,多文件可以采用生成多个二进制文件的方法,然后一并上传。具体后面研究。

说说原理,压缩涉及三个关键点:

1,一个图片前端可被加载,基于file:协议的路径是不能产生onload事件,所以需要借助浏览器的接口将图片转为可加载文件,一种是通过FileReader,另一种是

通过URL.createObjectURL。

2,利用canvas,获取图片的高度和宽度之后,利用drawImage输出图片,再利用canvas的toDataURL输出base64的图片。

3,将base64转为二进制文件。

案例代码:

  1 <!DOCTYPE html>
  2 <html>
  3 
  4 <head>
  5     <meta charset="UTF-8">
  6     <title>XMLHttpRequest上传文件压缩上传图片文件</title>
  7 </head>
  8 
  9 <body>
 10     <input type="file" id="file" name="myfile" accept="image/x-png, image/jpg, image/jpeg, image/gif" />
 11     <input type="button" onclick="UpladFile()" value="上传" />
 12     <script type="text/javascript">
 13     (function() {
 14         function compress(opts, fileObj) {
 15             this.defaults = {
 16                 id: "#file",
 17                 quality: 0.4,
 18                 url: "",
 19                 callback: function(bl) {
 20                     console.log(bl);
 21                 }
 22             };
 23 
 24             if (typeof opts === "object") {
 25                 this.options = Object.assign({}, this.defaults, opts)
 26             } else {
 27                 this.options = this.defaults;
 28             }
 29             fileObj = fileObj || document.querySelector(this.options.id).files[0];
 30             this.init(fileObj);
 31         }
 32         compress.prototype = {
 33             version: "1.0.1",
 34             init: function(fileObj) {
 35                 _this = this;
 36                 //promise处理异步
 37                 this.photoCompress(fileObj).then(function(res) {
 38                     return _this.canvasDataUrl.call(_this, res);
 39                 }).catch(function(err) {
 40                     console.log(err);
 41                 }).then(function(res) {
 42                     var bl = _this.convertBase64UrlToBlob(res);
 43                     _this.options.callback(bl);
 44                 }).catch(function(err) {
 45                     console.log(err);
 46                 })
 47 
 48             },
 49             photoCompress: function(file) {
 50                 var ready = new FileReader();
 51                 ready.readAsDataURL(file);
 52                 return new Promise(function(resolve, reject) {
 53                     ready.onload = function() {
 54                         var re = this.result;
 55                         resolve(re);
 56                     }
 57                     ready.onerror = function(err) {
 58                         reject(err)
 59                     }
 60                 })
 61 
 62             },
 63             canvasDataUrl: function(res) {
 64 
 65                 var img = new Image();
 66                 img.src = res;
 67                 _this = this;
 68                 return new Promise(function(resolve, reject) {
 69                     img.onload = function() {
 70                         // 默认按比例压缩
 71                         var w = this.width,
 72                             h = this.height;
 73                         //生成canvas
 74                         var canvas = document.createElement('canvas');
 75                         var ctx = canvas.getContext('2d');
 76                         // 创建属性节点
 77                         var anw = document.createAttribute("width");
 78                         anw.nodeValue = w;
 79                         var anh = document.createAttribute("height");
 80                         anh.nodeValue = h;
 81                         canvas.setAttributeNode(anw);
 82                         canvas.setAttributeNode(anh);
 83                         ctx.drawImage(this, 0, 0, w, h);
 84 
 85                         // quality值越小,所绘制出的图像越模糊
 86                         var base64 = canvas.toDataURL('image/jpeg', _this.options.quality);
 87                         // 回调函数返回base64的值
 88                         resolve(base64)
 89                     }
 90                     img.onerror = function(err) {
 91                         reject(err)
 92                     }
 93                 })
 94             },
 95 
 96             //base64转为二进制数据,后端可直接利用
 97             convertBase64UrlToBlob: function(urlData) {
 98                 var arr = urlData.split(','),
 99                     mime = arr[0].match(/:(.*?);/)[1],
100                     bstr = atob(arr[1]),
101                     n = bstr.length,
102                     u8arr = new Uint8Array(n);
103                 while (n--) {
104                     u8arr[n] = bstr.charCodeAt(n);
105                 }
106                 return new Blob([u8arr], { type: mime });
107             }
108         }
109         window.compress = compress;
110 
111     })();
112 
113     //上传文件方法
114     function UpladFile() {
115 
116         var url = "./upload"; // 接收上传文件的后台地址 
117         new compress({
118             quality: 0.4,
119             url: url,
120             callback: function(bl) {
121                 var form = new FormData();
122                 form.append("file", bl, "file_" + Date.parse(new Date()) + ".jpg"); // 文件对象
123                 var xhr = new XMLHttpRequest();
124                 xhr.open("post", url, true);
125                 xhr.onreadystatechange = function(evt) {
126                     if (xhr.readyState === 4 && xhr.status === 200) {
127                          uploadComplete(evt);
128                     }
129                 }
130 
131                 xhr.send(form); //开始上传,发送form数据
132             }
133         })
134     }
135 
136     //上传成功响应
137     function uploadComplete(evt) {
138         //服务断接收完文件返回的结果
139         var data = JSON.parse(evt.target.responseText);
140         if (data.success) {
141             alert("上传成功!");
142         } else {
143             alert("上传失败!");
144         }
145 
146     }
147     </script>
148 </body>
149 
150 </html>
View Code

后端采用thinkphp5,代码index控制器下,方法

 1    public function upload(){
 2        
 3          // 获取表单上传文件 例如上传了001.jpg
 4      $file = request()->file('file');
 5      $arr=array("code"=>1,"success"=>true);
 6      // 移动到框架应用根目录/public/uploads/ 目录下
 7      if($file){
 8         $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
 9         if($info){
10             
11             echo json_encode($arr);
12 
13         }else{
14 
15           return "error";
16             // 上传失败获取错误信息
17             echo $file->getError();
18         }
19       };
20     }

多张图片上传前压缩

  1 <!DOCTYPE html>
  2 <html>
  3 
  4 <head>
  5     <meta charset="UTF-8">
  6     <title>XMLHttpRequest上传文件压缩上传图片文件多</title>
  7 </head>
  8 
  9 <body>
 10     <input type="file" id="files" name="myfile" multiple accept="image/x-png, image/jpg, image/jpeg, image/gif" />
 11     <input type="button" onclick="UpladFile()" value="上传" />
 12     <script type="text/javascript">
 13     /*
 14             三个参数
 15             file:一个是文件(类型是图片格式),
 16             w:一个是文件压缩的后宽度,宽度越小,字节越小
 17             objDiv:一个是容器或者回调函数
 18             photoCompress()
 19              */
 20     function photoCompress(file, w, objDiv) {
 21         var ready = new FileReader();
 22         /*开始读取指定的Blob对象或File对象中的内容. 当读取操作完成时,readyState属性的值会成为DONE,如果设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容.*/
 23         ready.readAsDataURL(file);
 24         ready.onload = function() {
 25             var re = this.result;
 26             canvasDataURL(re, w, objDiv)
 27         }
 28     }
 29 
 30     function canvasDataURL(path, obj, callback) {
 31         var img = new Image();
 32         img.src = path;
 33         img.onload = function() {
 34             var that = this;
 35             // 默认按比例压缩
 36             var w = that.width,
 37                 h = that.height,
 38                 scale = w / h;
 39             w = obj.width || w;
 40             h = obj.height || (w / scale);
 41             var quality = 0.7; // 默认图片质量为0.7
 42             //生成canvas
 43             var canvas = document.createElement('canvas');
 44             var ctx = canvas.getContext('2d');
 45             // 创建属性节点
 46             var anw = document.createAttribute("width");
 47             anw.nodeValue = w;
 48             var anh = document.createAttribute("height");
 49             anh.nodeValue = h;
 50             canvas.setAttributeNode(anw);
 51             canvas.setAttributeNode(anh);
 52             ctx.drawImage(that, 0, 0, w, h);
 53             // 图像质量
 54             if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
 55                 quality = obj.quality;
 56             }
 57             // quality值越小,所绘制出的图像越模糊
 58             var base64 = canvas.toDataURL('image/jpeg', quality);
 59             // 回调函数返回base64的值
 60             callback(base64);
 61         }
 62     }
 63     /**
 64      * 将以base64的图片url数据转换为Blob
 65      * @param urlData
 66      *            用url方式表示的base64图片数据
 67      */
 68     function convertBase64UrlToBlob(urlData) {
 69         var arr = urlData.split(','),
 70             mime = arr[0].match(/:(.*?);/)[1],
 71             bstr = atob(arr[1]),
 72             n = bstr.length,
 73             u8arr = new Uint8Array(n);
 74         while (n--) {
 75             u8arr[n] = bstr.charCodeAt(n);
 76         }
 77         return new Blob([u8arr], { type: mime });
 78     }
 79 
 80     //上传文件方法
 81     function UpladFile() {
 82 
 83         var url = "./upload"; // 接收上传文件的后台地址 
 84         var form = new FormData();
 85         var files = document.querySelector("#files").files;
 86         files = Array.prototype.slice.call(files);
 87         files.forEach(function(val, index) {
 88             photoCompress(val, {
 89                 quality: 0.2
 90             }, function(base64Codes) {
 91                 console.log(index);
 92                 //console.log("压缩后:" + base.length / 1024 + " " + base);
 93                 var bl = convertBase64UrlToBlob(base64Codes);
 94                 form.append("file" + index, bl, "file_" + Date.parse(new Date()) + ".jpg"); // 文件对象
 95                 if (index + 1 === files.length) {
 96                     xhr = new XMLHttpRequest(); // XMLHttpRequest 对象
 97                     xhr.open("post", url, true); //post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
 98                     xhr.onload = uploadComplete; //请求完成
 99                     xhr.send(form); //开始上传,发送form数据
100                 }
101             });
102         })
103     }
104 
105     //上传成功响应
106     function uploadComplete(evt) {
107         //服务断接收完文件返回的结果
108         var data = JSON.parse(evt.target.responseText);
109         if (data.success) {
110             alert("上传成功!");
111         } else {
112             alert("上传失败!");
113         }
114 
115     }
116     </script>
117 </body>
118 
119 </html>
View Code

 

推荐阅读