接到需求,问前端是否可以压缩图片?因为有的图片太大,传到服务器上再压缩太慢了。意识里没有这么玩过,早上老大丢来一个知乎链接,一看,原来前辈们已经用 canvas 实现了 (为自己的见识羞愧 3 秒钟,再马上开干)!。
canvas 压缩
使用了 github 上的一个现成库:https://github.com/stomita/ios-imagefile-megapixel,不得不膜拜下 stomita 这位大神。大体的思路是将图片抽样显示在 canvas 上,然后用通过 canvas.toDataURL 方法得到 base64 字符串来实现压缩。比如在 input 元素触发 change 事件之后,读取里面的文件进行操作:
- var fileInput = document.getElementById('fileInput');
- fileInput.onchange = function() {
- var file = fileInput.files[0];
- // 创建一个压缩对象,该构造函数接收file或者blob。
- var mpImg = new MegaPixImage(file);
- // render方法的maxWith,maxHeight,以及quality都决定了压缩图片的质量
- var resImg = document.getElementById('resultImage');
- mpImg.render(resImg, {
- maxWidth: 300,
- maxHeight: 300,
- quality: 0.5
- });
- };压缩完成会得到
类似这样的图片:
data:image/jpeg 这样的格式已经应用的很多了,很多样式里面的背景图片直接就是这样。
需要说明的是有两点,这里的 resImg 是一个预览图片,是已经存在于文档中的,如果你不需要预览,而只是创建一个 img 用来压缩 (document.createElement("img")),这会少一个 tagName 属性。你可以修改源码或者自己加上这个属性。源码中会根据 tagName 进行判断,不存在的话会报错:
- MegaPixImage.prototype.render = function (target, options, callback) {
- //....
- target.tagName = target.tagName || "IMG"; //加上这一句
- var tagName = target.tagName.toLowerCase();
- if (tagName === 'img') {
- target.src = renderImageToDataURL(this.srcImage, opt, doSquash);
- } else if (tagName === 'canvas') {
- renderImageToCanvas(this.srcImage, target, opt, doSquash);
- }
- if (typeof this.onrender === 'function') {
- this.onrender(target);
- }
- if (callback) {
- callback();
- }
- if (this.blob) {
- this.blob = null;
- URL.revokeObjectURL(this.srcImage.src);
- }
- };
另外这是一个耗时的操作,如果是多张图片进行压缩,不能直接调用,需要稍微变换一下,不然会导致前面的图片没有压缩完成就进入到了后面的图片。
- fileSelected: function() {
- var files = $("#fileImage")[0].files;
- var count = files.length;
- console.log("共有" + count + "个文件");
- for (var i = 0; i < count; i++) {
- var item = files[i];
- console.log("原图片大小", item.size);
- if (item.size > 1024 * 1024 * 2) {
- console.log("图片大于2M,开始进行压缩..."); (function(img) {
- var mpImg = new MegaPixImage(img);
- var resImg = document.createElement("img");
- resImg.file = img;
- mpImg.render(resImg, {
- maxWidth: 500,
- maxHeight: 500,
- quality: 1
- },
- function() {
- //do some thing
- });
- })(item);
- }
- core.previewImage(item);
- }
- },
上传处理
1. 直接 post base64 字符串
- uploadBase64str: function(base64Str) {
- var formdata = new FormData();
- formdata.append("base64str", base64Str);
- var xhr = new XMLHttpRequest();
- xhr.upload.addEventListener("progress",
- function(e) {
- var percentComplete = Math.round(e.loaded * 100 / e.total);
- para.onProgress(percentComplete.toString() + '%');
- });
- xhr.addEventListener("load",
- function(e) {
- para.uploadComplete(xhr.responseText);
- });
- xhr.addEventListener("error",
- function(e) {
- para.uploadError(e);
- });
- xhr.open("post", para.base64strUrl, true);
- xhr.send(formdata);
- },
比如这里 base64strUrl 是 / home/MUploadImgBase64Str,MVC 控制器方法如下:
- [HttpPost]
- public ActionResult MUploadImgBase64Str(string base64str)
- {
- try
- {
- var imgData = base64str.Split(',')[1];
- //过滤特殊字符即可
- string dummyData = imgData.Trim().Replace("%", "").Replace(",", "").Replace(" ", "+");
- if (dummyData.Length % 4 > 0)
- {
- dummyData = dummyData.PadRight(dummyData.Length + 4 - dummyData.Length % 4, '=');
- }
- byte[] byteArray = Convert.FromBase64String(dummyData);
- using (System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray))
- {
- var img = System.Drawing.Image.FromStream(ms);
- var path = "~/Content/UploadFiles/mobile/";
- var uploadpath = Server.MapPath(path);
- if (!Directory.Exists(uploadpath))
- {
- Directory.CreateDirectory(uploadpath);
- }
- var saveName = uploadpath + "stoneniqiu" + ".jpg";
- img.Save(saveName);
- return Json(saveName);
- }
- }
- catch (Exception e)
- {
- return Json(e.Message);
- }
- }
几 M 的图片能压缩到几十 k 或者几百 k,当然,如果宽度、高度和质量设置的太小,图片就会很失真了。这个字符串怎么获取呢?有两个方法,一个是直接读取 src:
var base641 = resImg.src;
一个是用 canvas 转换:
- function getBase64Image(img) {
- var canvas = document.createElement("canvas");
- canvas.width = img.width;
- canvas.height = img.height;
- var ctx = canvas.getContext("2d");
- ctx.drawImage(img, 0, 0, img.width, img.height);
- var dataURL = canvas.toDataURL("image/jpeg");
- return dataURL;
- // return dataURL.replace("data:image/png;base64,", "");
- }
- var base64 = getBase64Image(resImg);
同一张图片,这两者获取到的字符串大小不一样,但图片质量我是分辨不出什么差别。
比如一个 2M 的图片,通过 getBase64Image 方法读到的字符串大小才 64k,而 src 直接读取到的却是 270k,各自生成的图片更小。一下分别是原图 (2.2M),base64(48k),src(202k) 对应的图片。
getBase64Image 通过 canvas 的 toDataURL 获取到更小的 base64 字符串。
2. 也可以在前端转换 blob 对象,再 post 到后端
- function dataURItoBlob(dataUrl) {
- var byteString = atob(dataUrl.split(',')[1]);
- var ab = new ArrayBuffer(byteString.length);
- var ia = new Uint8Array(ab);
- for (var i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
- return new Blob([ab], {
- type: 'image/jpeg'
- });
- }
3. 不压缩的就直接装到 formdata 中,send 到后台。
- uploadFile: function(file) {
- console.log("开始上传");
- var formdata = new FormData();
- formdata.append(para.filebase, file); //这个名字要和mvc后台配合
- var xhr = new XMLHttpRequest();
- xhr.upload.addEventListener("progress",
- function(e) {
- var percentComplete = Math.round(e.loaded * 100 / e.total);
- para.onProgress(percentComplete.toString() + '%');
- });
- xhr.addEventListener("load",
- function(e) {
- para.uploadComplete(xhr.responseText);
- });
- xhr.addEventListener("error",
- function(e) {
- para.uploadError(e);
- });
- xhr.open("post", para.url, true);
- xhr.send(formdata);
- },
全部的代码:(包含压缩和上传以及 demo):
github:https://github.com/stoneniqiu/h5upload/
小结:基本上就是这样了, 前端能够压缩图片的话,确实省了流量和时间。 插件是在上一篇的基础上进行改进的。
来源: http://www.jb51.net/article/111768.htm