在做图片上传时,大图片如果没有压缩直接上传时间会非常长,因为有的图片太大,传到服务器上再压缩太慢了,而且损耗流量。
思路是将图片抽样显示在canvas上,然后用通过canvas.toDataURL方法得到base64字符串来实现压缩。
废话不多少不多说直接看代码:
本次测试使用了 zepto.min.js 插件,更新版本的下载请点击这里
主要js代码:
//图片压缩处理 ; (function () { /** * 加载的时候进行抽样检测 * 在iOS中,大于2M的图片会抽样渲染 */ function detectSubsampling(img) { var iw = img.naturalWidth, ih = img.naturalHeight; if (iw * ih > 1024 * 1024) { //大于百万像素的就会进行抽样 var canvas = document.createElement(‘canvas‘); canvas.width = canvas.height = 1; var ctx = canvas.getContext(‘2d‘); ctx.drawImage(img, -iw + 1, 0); // subsampled image becomes half smaller in rendering size. // check alpha channel value to confirm image is covering edge pixel or not. // if alpha value is 0 image is not covering, hence subsampled. return ctx.getImageData(0, 0, 1, 1).data[3] === 0; } else { return false; } } /** * Detecting vertical squash in loaded image. * Fixes a bug which squash image vertically while drawing into canvas for some images. */ function detectVerticalSquash(img, iw, ih) { var canvas = document.createElement(‘canvas‘); canvas.width = 1; canvas.height = ih; var ctx = canvas.getContext(‘2d‘); ctx.drawImage(img, 0, 0); var data = ctx.getImageData(0, 0, 1, ih).data; // search image edge pixel position in case it is squashed vertically. var sy = 0; var ey = ih; var py = ih; while (py > sy) { var alpha = data[(py - 1) * 4 + 3]; if (alpha === 0) { ey = py; } else { sy = py; } py = (ey + sy) >> 1; } var ratio = (py / ih); return (ratio === 0) ? 1 : ratio; } /** * Rendering image element (with resizing) and get its data URL */ function renderImageToDataURL(img, options, doSquash) { var canvas = document.createElement(‘canvas‘); renderImageToCanvas(img, canvas, options, doSquash); return canvas.toDataURL("image/jpeg", options.quality || 0.8); } /** * Rendering image element (with resizing) into the canvas element */ function renderImageToCanvas(img, canvas, options, doSquash) { var iw = img.naturalWidth, ih = img.naturalHeight; if (!(iw + ih)) return; var width = options.width, height = options.height; var ctx = canvas.getContext(‘2d‘); ctx.save(); transformCoordinate(canvas, ctx, width, height, options.orientation); var subsampled = detectSubsampling(img); if (subsampled) { iw /= 2; ih /= 2; } var d = 1024; // size of tiling canvas var tmpCanvas = document.createElement(‘canvas‘); tmpCanvas.width = tmpCanvas.height = d; var tmpCtx = tmpCanvas.getContext(‘2d‘); var vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1; var dw = Math.ceil(d * width / iw); var dh = Math.ceil(d * height / ih / vertSquashRatio); var sy = 0; var dy = 0; while (sy < ih) { var sx = 0; var dx = 0; while (sx < iw) { tmpCtx.clearRect(0, 0, d, d); tmpCtx.drawImage(img, -sx, -sy); ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh); sx += d; dx += dw; } sy += d; dy += dh; } ctx.restore(); tmpCanvas = tmpCtx = null; } /** * Transform canvas coordination according to specified frame size and orientation * 根据帧的大小和方向调整canvas * 方向值是来自EXIF标签 */ function transformCoordinate(canvas, ctx, width, height, orientation) { switch (orientation) { case 5: case 6: case 7: case 8: canvas.width = height; canvas.height = width; break; default: canvas.width = width; canvas.height = height; } switch (orientation) { case 2: // horizontal flip ctx.translate(width, 0); ctx.scale(-1, 1); break; case 3: // 180 rotate left ctx.translate(width, height); ctx.rotate(Math.PI); break; case 4: // vertical flip ctx.translate(0, height); ctx.scale(1, -1); break; case 5: // vertical flip + 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.scale(1, -1); break; case 6: // 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.translate(0, -height); break; case 7: // horizontal flip + 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.translate(width, -height); ctx.scale(-1, 1); break; case 8: // 90 rotate left ctx.rotate(-0.5 * Math.PI); ctx.translate(-width, 0); break; default: break; } } var URL = window.URL && window.URL.createObjectURL ? window.URL : window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : null; /** * MegaPixImage class */ function MegaPixImage(srcImage) { if (window.Blob && srcImage instanceof Blob) { if (!URL) { throw Error("No createObjectURL function found to create blob url"); } var img = new Image(); img.src = URL.createObjectURL(srcImage); this.blob = srcImage; srcImage = img; } if (!srcImage.naturalWidth && !srcImage.naturalHeight) { var _this = this; srcImage.onload = srcImage.onerror = function () { var listeners = _this.imageLoadListeners; if (listeners) { _this.imageLoadListeners = null; for (var i = 0, len = listeners.length; i < len; i++) { listeners[i](); } } }; this.imageLoadListeners = []; } this.srcImage = srcImage; } /** * Rendering megapix image into specified target element */ MegaPixImage.prototype.render = function (target, options, callback) { if (this.imageLoadListeners) { var _this = this; this.imageLoadListeners.push(function () { _this.render(target, options, callback); }); return; } options = options || {}; var imgWidth = this.srcImage.naturalWidth, imgHeight = this.srcImage.naturalHeight, width = options.width, height = options.height, maxWidth = options.maxWidth, maxHeight = options.maxHeight, doSquash = !this.blob || this.blob.type === ‘image/jpeg‘; if (width && !height) { height = (imgHeight * width / imgWidth) << 0; } else if (height && !width) { width = (imgWidth * height / imgHeight) << 0; } else { width = imgWidth; height = imgHeight; } if (maxWidth && width > maxWidth) { width = maxWidth; height = (imgHeight * width / imgWidth) << 0; } if (maxHeight && height > maxHeight) { height = maxHeight; width = (imgWidth * height / imgHeight) << 0; } var opt = { width: width, height: height }; for (var k in options) opt[k] = options[k]; 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); } }; /** * Export class to global */ if (typeof define === ‘function‘ && define.amd) { define([], function () { return MegaPixImage; }); // for AMD loader } else if (typeof exports === ‘object‘) { module.exports = MegaPixImage; // for CommonJS } else { this.MegaPixImage = MegaPixImage; } })(); ; (function ($) { $.extend($, { fileUpload: function (options) { var para = { multiple: true, //是否可以多选上传 filebutton: ".filePicker", //上传文件的按钮 uploadButton: null, url: "/Home/NotCompressUploadImg", //小型图片不压缩上传url filebase: "mfile", // 此参数要对应 mvc action 的 HttpPostedFileBase类型 参数名称 (和action的参数必须相同) base64strUrl: "/Home/CompressImgBase64Upload", //大图片压缩上传url imgGreaterCompress: 1024, //图片的大小大于多少进行压缩上传 limitCount: 2, //(多选上传)一次最多选择上传的图片最大数量 maxUploadCount: 3, //最多能上传多少张图片 maxWidth: 300, //在客户端显示图片最大宽度 maxHeight: 300, //在客户端显示图片最大高度 previewImageClassName: "previewImg previewImg_1", //显示图片的样式类名称 inputFileImageClassName: "inputFileImage", auto: true, previewDivId : "preview", //图片预览,动态添加图片预览img标签,添加那个div内 previewZoom: true, uploadComplete: function (jsonResultData) { //上传成功执行此函数 res是服务端返回的数据 var imgArray = document.getElementById(para.previewDivId).getElementsByTagName("img"); var finallyImg = document.getElementById(para.previewDivId).getElementsByTagName("img")[imgArray.length-1]; if (jsonResultData.Success) { //上传成功 finallyImg.setAttribute("serverFilePath",jsonResultData.FilePath); //给动态添加的标签添加 图片上传到服务器图片地址属性 console.log("uploadComplete", jsonResultData); uploadCount++; core.checkComplete(); } else { //上传未成功 alert("出错了!请重试"); finallyImg.remove(); //删除预览图片标签 } }, uploadError: function (err) { //上传失败执行此函数 console.log("uploadError", err); }, onProgress: function (percent) { // 提供给外部获取单个文件的上传进度,供外部实现上传进度效果 console.log(‘进度条:‘+percent); }, }; para = $.extend(para, options); var $self = $(para.filebutton); //先加入一个file元素(上传文件按钮) var multiple = ""; // 设置多选的参数 para.multiple ? multiple = "multiple" : multiple = ""; $self.css(‘position‘, ‘relative‘); $self.append(‘<input id="fileImage" class=‘+para.inputFileImageClassName+‘ style="opacity:0;position:absolute;top: 0;left: 0;width:100%;height:100%" accept="image/*" type="file" size="30" name="fileselect[]" ‘ + multiple + ‘>‘); var doms = { "fileToUpload": $self.find("#fileImage"), "thumb": $self.find(".thumb"), "progress": $self.find(".upload-progress") //进度条 }; 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,", ""); } function simpleSize(size) { if (!size) return "0"; if (size < 1024) { return size; } var kb = size / 1024; if (kb < 1024) { return kb.toFixed(2) + "K"; } var mb = kb / 1024; if (mb < 1024) { return mb.toFixed(2) + "M"; } var gb = mb / 1024; return gb.toFixed(2) + "G"; }; //Convert DataURL to Blob to send over Ajax function dataURItoBlob(dataUrl) { // convert base64 to raw binary data held in a string // doesn‘t handle URLEncoded DataURIs - see SO answer #6850276 for code that does this var byteString = atob(dataUrl.split(‘,‘)[1]); // separate out the mime component var mimeString = dataUrl.split(‘,‘)[0].split(‘:‘)[1].split(‘;‘)[0]; // write the bytes of the string to an ArrayBuffer 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‘ }); } var uploadCount = 0; var core = { fileSelected: function () { var files = $("#fileImage")[0].files; var count = files.length; if (count +document.getElementById(para.previewDivId).getElementsByTagName("img").length> para.maxUploadCount) { //最大只能上传多少张图片(document.getElementById(para.previewDivId).getElementsByTagName("img").length 获取已经上传了多少张图片) alert("最多只能上传"+para.maxUploadCount+"张图片!"); return; } console.log("共有" + count + "个文件"); for (var i = 0; i < count; i++) { if (i >= para.limitCount) { //最大一次性选择多少张图片 alert("最多只能选择"+para.limitCount+"张图片!"); break; } var item = files[i]; console.log("原图片大小", item.size); if (item.size > para.imgGreaterCompress) { //图片打印多少M进行压缩上传 console.log("图片大于2M,开始进行压缩..."); (function(img) { var mpImg = new MegaPixImage(img); var resImg = document.getElementById("resultImage"); resImg.file = img; mpImg.render(resImg, { maxWidth: para.maxWidth, maxHeight: para.maxHeight, quality: 1 }, function() { var base64 = getBase64Image(resImg); var base641 = resImg.src; console.log("base64", base64.length, simpleSize(base64.length), base641.length, simpleSize(base641.length)); if (para.auto) core.uploadBase64str(base64); }); })(item); } else { if (para.auto) core.uploadFile(item); } core.previewImage(item); } }, 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) { var jsonResultData= JSON.parse(xhr.responseText); para.uploadComplete(jsonResultData); }); xhr.addEventListener("error", function (e) { para.uploadError(e); }); xhr.open("post", para.base64strUrl, true); xhr.send(formdata); }, 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) { var jsonResultData= JSON.parse(xhr.responseText); para.uploadComplete(jsonResultData); }); xhr.addEventListener("error", function (e) { para.uploadError(e); }); xhr.open("post", para.url, true); xhr.send(formdata); }, checkComplete:function() { var all = (doms.fileToUpload)[0].files.length; if (all == uploadCount) { console.log(all + "个文件上传完毕"); doms.fileToUpload.remove(); //删除上次动态添加 上传按钮 //input有一个问题就是选择重复的文件不会触发change事件,所以做了一个处理,再每次上传完之后删掉这个元素再新增一个input。 $self.append(‘<input id="fileImage" class=‘+para.inputFileImageClassName+‘ style="opacity:0;position:absolute;top: 0;left: 0;width:100%;height:100%" accept="image/*" type="file" size="30" name="fileselect[]" ‘ + multiple + ‘>‘); } }, uploadFiles: function () { var files = (doms.fileToUpload)[0].files; for (var i = 0; i < files.length; i++) { core.uploadFile(files[i]); } }, previewImage: function (file) { //将压缩后的图片显示 if (!para.previewZoom) return; var img = document.createElement("img"); img.file = file; img.className =para.previewImageClassName; //样式类名称 (添加一个) //img.className = "previewImg previewImg_1"; //样式类名称(添加两个) //img.classList.add("previewImg"); //添加样式类 //img.classList.add("previewImg_1"); //添加样式类 //img.setAttribute("test","aaa"); $(para.previewZoom).append(img); // 使用FileReader方法显示图片内容 var reader = new FileReader(); reader.onload = (function (aImg) { return function (e) { aImg.src = e.target.result; }; })(img); reader.readAsDataURL(file); } } $(document).on("change", "#fileImage", function () { core.fileSelected(); }); $(document).on("click", para.filebutton, function () { console.log("clicked"); }); if (para.uploadButton) { $(document).on("click", para.uploadButton, function () { core.uploadFiles(); }); } } }); })(Zepto);
视图页面代码:
@{ ViewBag.Title = "h5图片压缩与上传"; } <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <style> .filePicker { background: none repeat scroll 0 0 #00B7EE; border-radius: 3px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); color: #FFFFFF; cursor: pointer; display: inline-block; font-size: 18px; height: 44px; line-height: 44px; width: 90%; min-width: 120px; margin: 0 auto 0px; overflow: hidden; transition: background 0.2s; -moz-transition: background 0.2s; -webkit-transition: background 0.2s; -o-transition: background 0.2s; } #preview { margin-top: 30px; } #preview img { max-width: 30%; } .previewImg { border:5px solid red; } .previewImg_1 { min-width: 120px; } .inputFileImage { border:5px solid red; } </style> <body> <div class="page" id="upload"> <h2> 测试H5图片压缩与上传</h2> <div id="dd" class="filePicker"> 点击选择文件</div> <img id="resultImage" style="display: none;"/> <div id="preview"> </div> </div> <button onclick="GetServerFilePath(‘preview‘)">获取上传到服务端图片路径</button> <script src="/Scripts/zepto.min.js" type="text/javascript"></script> <script src="/Scripts/h5upload.js" type="text/javascript"></script> <script> var para = { filebutton: "#dd", previewZoom: "#preview", previewDivId: "preview" }; $.fileUpload(para); function GetServerFilePath(previewDivId) { var serverFilePathArray = []; //存储服务端返回图片路径 var imgArray = document.getElementById(previewDivId).getElementsByTagName("img"); for (var i = 0; i < imgArray.length; i++) { serverFilePathArray.push(imgArray[i].getAttribute("serverFilePath")); } alert(serverFilePathArray.join(",")); return serverFilePathArray; } </script> </body> </html>
控制器代码:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; namespace H5UploadDemo.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } /// <summary> /// 未压缩上传 /// </summary> /// <param name="mfile"></param> /// <returns></returns> [HttpPost] public ActionResult NotCompressUploadImg(HttpPostedFileBase mfile) { if (mfile != null) { var path = "/Upload/Image/"; var uploadpath = Server.MapPath("~"+path); if (!Directory.Exists(uploadpath)) { Directory.CreateDirectory(uploadpath); } string fileName = Path.GetFileName(mfile.FileName);// 原始文件名称 string fileExtension = Path.GetExtension(fileName); // 文件扩展名 string saveName = Guid.NewGuid() + fileExtension; // 保存文件名称 这是个好方法。 //string saveName = Encrypt.GenerateOrderNumber() + fileExtension; // 保存文件名称 这是个好方法。 mfile.SaveAs(uploadpath + saveName); return Json(new { Success = true, FilePath = path + saveName, Message = "上传成功!" }); } return Json(new { Success = false, Message = "请选择要上传的文件!" }, JsonRequestBehavior.AllowGet); } /// <summary> /// 压缩上传 /// </summary> /// <param name="base64str"></param> /// <returns></returns> [HttpPost] public ActionResult CompressImgBase64Upload(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 = "/Upload/Image/"; var uploadpath = Server.MapPath("~" + path); if (!Directory.Exists(uploadpath)) { Directory.CreateDirectory(uploadpath); } var saveName = Guid.NewGuid() + ".jpg"; img.Save(uploadpath+saveName); return Json(new { Success = true, FilePath = path + saveName, Message = "上传成功!" }); } } catch (Exception e) { return Json(new { Success = false, FilePath ="", Message = "出错了!" }); } } } }
完整代码地址:http://pan.baidu.com/s/1c2LuVDm 密码:gwdc
时间: 2024-10-14 19:49:04