进园一年多来,第一次写博客,好激动。原因主要是自己平时都是有写笔记,不习惯写博客,这次想写博客的原因是,这个问题确实我做了很久,已经做了近两周才解决了这个问题,,而且两周时间里尝试过了很多种办法,然后由于网上又没有多少人分享这个,决定自己写一下。
先自我介绍下,我是惠州学院大二(准大三)的学生,在去年暑假时候加了一个工作室开始写代码,也是在那个时候加入博客园,最近因为工作室要做微信端网页的开发需要一个上传图片功能,然后编码的时候发现用平时的方法做的上传功能在Android的微信端上实现不了,百度一下发现很多人也出现过同样的问题,也有人指出说这是被腾讯给“阉割”掉了的,经过两个星期的尝试后,找到了两种解决办法。
-
我尝试过的办法
- 首先是<input type=”file”>,这是我们最容易,同时也是最先用的,应该也是我试过那么多的插件都是用这个,这个只有在部分手机的微信版本上会显示upload disable,大部分手机微信还是支持的,但是做完我们会发现我们的微信可以选择文件,但是上传不了,百度了一下很多人说这个被腾讯给“阉割”掉了,一开始我也是这么觉得,所以就放弃了选择这个,后来发现,腾讯“阉割”掉了的不是<input type=”file”>,而是你上传往服务器发请求的那一块,具体怎么解释我是新手,真心不造怎么说,后面会再说,希望知道这个原理的能和我说明一下。
- 放弃了<input type=”file”>,我们工作室的师兄就推荐我一款插件,叫Uploadify,这款插件在它的官网上面有两种版本,一种是flash(免费),一种是HTML5(收费),不过也有大牛把HTML5的版本模仿了出来,网上找得到。首先是flash吧,这个也是另外一篇博文里博主推荐大家的方法(原文地址:http://blog.csdn.net/zz880329/article/details/12652063),我就尝试了一下,发现使用flash实现上传的话,选择文件的“样式”变了,变成了flash的选择文件,大家自己试过的话应该就会发现,flash选择文件的方式你看到的只有文件名,你都不知道自己要选哪张图片好,而且好像还需要手机有flash支持,所以这个flash被我放弃了。然后是HTML5的版本,打开demo的代码,上面确实没有了<input type=”file”>,但是实际运行起来,在网页还是用的是了<input type=”file”>,实际放到微信上好像也是行不通,后来也就放弃了。
- 后面又看到一篇介绍各种HTML5的上传插件的博文,然后又是像无头苍蝇那样各种尝试,最终只保留下了其中一种我觉得是最可行的,但是好像兼容性各方面还有待优化,后面我的第二种可行办法我会再介绍它的,希望大家都说下各自的理解吧,还有好多方面我都不太懂。
-
第一种可行的js插件
- 尝试过很多插件之后,基本都以失败告终。不过好像每次写代码都很幸运,我舍友在微信上找到一个网站是有实现相关功能的,然后我就去找到那个网站研究他们的上传插件,发现使用的插件我之前没有尝试过,于是就找到了这个插件并且成功实现功能,插件叫ajaxfileupload.js
//这段代码用于解决一些JQuery版本缺少的一个函数 jQuery.extend({ handleError: function (s, xhr, status, e) { // If a local callback was specified, fire it if (s.error) { s.error.call(s.context || s, xhr, status, e); } // Fire the global callback if (s.global) { (s.context ? jQuery(s.context) : jQuery.event).trigger("ajaxError", [xhr, s, e]); } }, //ajaxfileupload.js源代码 createUploadIframe: function(id, uri) { //create frame var frameId = \'jUploadFrame\' + id; var iframeHtml = \'<iframe id="\' + frameId + \'" name="\' + frameId + \'" style="position:absolute; top:-9999px; left:-9999px"\'; if(window.ActiveXObject) { if(typeof uri== \'boolean\'){ iframeHtml += \' src="\' + \'javascript:false\' + \'"\'; } else if(typeof uri== \'string\'){ iframeHtml += \' src="\' + uri + \'"\'; } } iframeHtml += \' />\'; jQuery(iframeHtml).appendTo(document.body); return jQuery(\'#\' + frameId).get(0); }, createUploadForm: function(id, fileElementId, data) { //create form var formId = \'jUploadForm\' + id; var fileId = \'jUploadFile\' + id; var form = jQuery(\'<form action="" method="POST" name="\' + formId + \'" id="\' + formId + \'" enctype="multipart/form-data"></form>\'); if(data) { for(var i in data) { jQuery(\'<input type="hidden" name="\' + i + \'" value="\' + data[i] + \'" />\').appendTo(form); } } var oldElement = jQuery(\'#\' + fileElementId); var newElement = jQuery(oldElement).clone(); jQuery(oldElement).attr(\'id\', fileId); jQuery(oldElement).before(newElement); jQuery(oldElement).appendTo(form); //set attributes jQuery(form).css(\'position\', \'absolute\'); jQuery(form).css(\'top\', \'-1200px\'); jQuery(form).css(\'left\', \'-1200px\'); jQuery(form).appendTo(\'body\'); return form; }, ajaxFileUpload: function(s) { // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout s = jQuery.extend({}, jQuery.ajaxSettings, s); var id = new Date().getTime() var form = jQuery.createUploadForm(id, s.fileElementId, (typeof(s.data)==\'undefined\'?false:s.data)); var io = jQuery.createUploadIframe(id, s.secureuri); var frameId = \'jUploadFrame\' + id; var formId = \'jUploadForm\' + id; // Watch for a new set of requests if ( s.global && ! jQuery.active++ ) { jQuery.event.trigger( "ajaxStart" ); } var requestDone = false; // Create the request object var xml = {} if ( s.global ) jQuery.event.trigger("ajaxSend", [xml, s]); // Wait for a response to come back var uploadCallback = function(isTimeout) { var io = document.getElementById(frameId); try { if(io.contentWindow) { xml.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null; xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document; }else if(io.contentDocument) { xml.responseText = io.contentDocument.document.body?io.contentDocument.document.body.innerHTML:null; xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document; } }catch(e) { jQuery.handleError(s, xml, null, e); } if ( xml || isTimeout == "timeout") { requestDone = true; var status; try { status = isTimeout != "timeout" ? "success" : "error"; // Make sure that the request was successful or notmodified if ( status != "error" ) { // process the data (runs the xml through httpData regardless of callback) var data = jQuery.uploadHttpData( xml, s.dataType ); // If a local callback was specified, fire it and pass it the data if ( s.success ) s.success( data, status ); // Fire the global callback if( s.global ) jQuery.event.trigger( "ajaxSuccess", [xml, s] ); } else jQuery.handleError(s, xml, status); } catch(e) { status = "error"; jQuery.handleError(s, xml, status, e); } // The request was completed if( s.global ) jQuery.event.trigger( "ajaxComplete", [xml, s] ); // Handle the global AJAX counter if ( s.global && ! --jQuery.active ) jQuery.event.trigger( "ajaxStop" ); // Process result if ( s.complete ) s.complete(xml, status); jQuery(io).unbind() setTimeout(function() { try { jQuery(io).remove(); jQuery(form).remove(); } catch(e) { jQuery.handleError(s, xml, null, e); } }, 100) xml = null } } // Timeout checker if ( s.timeout > 0 ) { setTimeout(function(){ // Check to see if the request is still happening if( !requestDone ) uploadCallback( "timeout" ); }, s.timeout); } try { var form = jQuery(\'#\' + formId); jQuery(form).attr(\'action\', s.url); jQuery(form).attr(\'method\', \'POST\'); jQuery(form).attr(\'target\', frameId); if(form.encoding) { jQuery(form).attr(\'encoding\', \'multipart/form-data\'); } else { jQuery(form).attr(\'enctype\', \'multipart/form-data\'); } jQuery(form).submit(); } catch(e) { jQuery.handleError(s, xml, null, e); } jQuery(\'#\' + frameId).load(uploadCallback ); return {abort: function () {}}; }, uploadHttpData: function( r, type ) { var data = !type; data = type == "xml" || data ? r.responseXML : r.responseText; // If the type is "script", eval it in global context if ( type == "script" ) jQuery.globalEval( data ); // Get the JavaScript object, if JSON is used. if ( type == "json" ) eval("data = " + data); //eval("data = \" " + data + " \" ");//此处根据网络版本修改的,修改后发现没用用,用于解决json问题 // evaluate scripts within html if ( type == "html" ) jQuery("<div>").html(data).evalScripts(); return data; } })
- 前台代码很简单
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1"> <script src="jquery-1.7.1.js" type="text/javascript"></script> <script src="ajaxfileupload.js" type="text/javascript"></script> <script type="text/javascript"> function ajaxFileUpload() { $.ajaxFileUpload ( { url: \'upload.aspx\', //用于文件上传的服务器端请求地址 secureuri: false, //一般设置为false fileElementId: \'file\', //文件上传空间的id属性 <input type="file" id="file" name="file" /> dataType: \'json\', //返回值类型 一般设置为json success: function (data, status) //服务器成功响应处理函数 { $("#img1").attr("src", data.imgurl); if (typeof (data.error) != \'undefined\') { if (data.error != \'\') { alert(data.error); } else { alert(data.msg); } } }, error: function (data, status, e)//服务器响应失败处理函数 { alert(e); } } ) return false; } </script> </head> <body> <input type="file" id="file" name="file" value="上传" accept="image/*;capture=camera" onchange="ajaxFileUpload()"/> <p><img id="img1" alt="上传成功啦" src="" /></p> </body> </html>
- 然后是C#的后台代码,这里我就没有放压缩图片大小的代码进来了,压缩图片的功能是根据教程做的,也是可以实现后台压缩图片的
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class Upload_Upload : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { HttpFileCollection files = Request.Files;//这里只能用<input type="file" />才能有效果,因为服务器控件是HttpInputFile类型 string filePath = "Upimages"; string msg = string.Empty; string error = string.Empty; string imgurl; if (files.Count > 0) { files[0].SaveAs(Server.MapPath(filePath+"/") + System.IO.Path.GetFileName(files[0].FileName)); msg = " 成功! 文件大小为:" + files[0].ContentLength; imgurl = "Upimages/" + files[0].FileName; string res = "{ error:\'" + error + "\', msg:\'" + msg + "\',imgurl:\'" + imgurl + "\'}"; Response.Write(res); Response.End(); } } }
通过这个实现了之后,腾讯“阉割”掉的应该不是<input type="file”>而是你上传的文件的文件流,<input type="file”>并不会失效(除了在部分低端手机和HTC)。而这个插件把这关键的发送文件流的部分“隐藏了起来”,具体隐藏的方法应该就是他js文件写的,所以这个插件成功了。
-
第二种可行的办法,仅限用于图片,其他类型文件我还没尝试
-
接下来是第二种可行的办法,也是在找控件的过程中发现的,感觉很棒,我在三星的微信上试过可以实现上传,但是在魅族上页面还是出错了,之后由于第一种方法可以兼容,于是就采用了第一种,没有对第二种进行深究了,不过还是分享一下。它的原理就是把你点击选择完图片后,用js将你选择的图片转为base64格式的数据,而且还能实现使用js对图片进行压缩后保存下压缩后的base64格式的字符串数据,而我们只需要把这串字符串发送到后台,然后在后台将base64格式的数据转为图片就容易啦,这样,腾讯也抓不了。
-
前台代码,我跟原作者代码有修改了一点,因为发现发送到后台base64转图片的时候,base64编码不能带上“文件头”(/^data:base64,/),还有就是加上了压缩图片所需要修改的参数的位置,这里默认压缩为100*100。(原作者好腻害的赶脚)。
-
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <title>图片压缩</title> <script src="jquery-1.7.1.js" type="text/javascript"></script> <style> body { margin:0; padding:0; } html { font-size:62.5%; } .imgzip { padding:1em; } .imgzip .itm { padding-bottom:1em; word-break:break-all; font-size:1.2rem; line-height:1.5em; } .imgzip .itm .tit { margin-bottom:.5em; background-color:#e71446; color:#FFF; padding:.5rem 1rem; border-radius:3px; } .imgzip .itm .cnt { padding:1rem; } .imgzip .itm .cnt img { display:block; max-width:100%; } .imgzip textarea { width:100%; height:20em; } </style> </head> <body> <input type="file" accept="image/*;capture=camera" class="input"> <input type="button" value="上传" onclick="upload();" /> <div class="imgzip"></div> <script> document.addEventListener(\'DOMContentLoaded\', init, false); function init() { var u = new UploadPic(); u.init({ input: document.querySelector(\'.input\'), callback: function (base64) { var html = \'\'; html = \'<div class="itm"><div class="tit">图片名称:</div><div class="cnt" id="name">\' + this.fileName + \'</div></div>\' + \'<div class="itm"><div class="tit">原始大小:</div><div class="cnt">\' + (this.fileSize / 1024).toFixed(2) + \'KB<\/div><\/div>\' + \'<div class="itm"><div class="tit">编码大小:</div><div class="cnt">\' + (base64.length / 1024).toFixed(2) + \'KB<\/div><\/div>\' + \'<div class="itm"><div class="tit">原始尺寸:</div><div class="cnt">\' + this.tw + \'px * \' + this.th + \'px<\/div><\/div>\' + \'<div class="itm"><div class="tit">编码尺寸:</div><div class="cnt">\' + this.sw + \'px * \' + this.sh + \'px<\/div><\/div>\' + \'<div class="itm"><div class="tit">图片预览:</div><div class="cnt"><img src="\' + base64 + \'"><\/div><\/div>\' + \'<div class="itm"><div class="tit">Base64编码:</div><div class="cnt"><textarea id="base64">\' + this.noHead + \'<\/textarea><\/div><\/div>\'; document.querySelector(\'.imgzip\').innerHTML = html; }, loading: function () { document.querySelector(\'.imgzip\').innerHTML = \'读取中,请稍候...\'; } }); } function UploadPic() { this.sw = 0; this.sh = 0; this.tw = 0; this.th = 0; this.scale = 0; this.maxWidth = 0; this.maxHeight = 0; this.maxSize = 0; this.fileSize = 0; this.fileDate = null; this.fileType = \'\'; this.fileName = \'\'; this.input = null; this.canvas = null; this.mime = {}; this.type = \'\'; this.callback = function () { }; this.loading = function () { }; this.noHead = ""; } UploadPic.prototype.init = function (options) { this.maxWidth = options.maxWidth || 800; this.maxHeight = options.maxHeight || 600; this.maxSize = options.maxSize || 3 * 1024 * 1024; this.input = options.input; this.mime = { \'png\': \'image/png\', \'jpg\': \'image/jpeg\', \'jpeg\': \'image/jpeg\', \'bmp\': \'image/bmp\' }; this.callback = options.callback || function () { }; this.loading = options.loading || function () { }; this._addEvent(); }; /** * @description 绑定事件 * @param {Object} elm 元素 * @param {Function} fn 绑定函数 */ UploadPic.prototype._addEvent = function () { var _this = this; function tmpSelectFile(ev) { _this._handelSelectFile(ev); } this.input.addEventListener(\'change\', tmpSelectFile, false); }; /** * @description 绑定事件 * @param {Object} elm 元素 * @param {Function} fn 绑定函数 */ UploadPic.prototype._handelSelectFile = function (ev) { var file = ev.target.files[0]; this.type = file.type // 如果没有文件类型,则通过后缀名判断(解决微信及360浏览器无法获取图片类型问题) if (!this.type) { this.type = this.mime[file.name.match(/\.([^\.]+)$/i)[1]]; } if (!/image.(png|jpg|jpeg|bmp)/.test(this.type)) { alert(\'选择的文件类型不是图片\'); return; } if (file.size > this.maxSize) { alert(\'选择文件大于\' + this.maxSize / 1024 / 1024 + \'M,请重新选择\'); return; } this.fileName = file.name; this.fileSize = file.size; this.fileType = this.type; this.fileDate = file.lastModifiedDate; this._readImage(file); }; /** * @description 读取图片文件 * @param {Object} image 图片文件 */ UploadPic.prototype._readImage = function (file) { var _this = this; function tmpCreateImage(uri) { _this._createImage(uri); } this.loading(); this._getURI(file, tmpCreateImage); }; /** * @description 通过文件获得URI * @param {Object} file 文件 * @param {Function} callback 回调函数,返回文件对应URI * return {Bool} 返回false */ UploadPic.prototype._getURI = function (file, callback) { var reader = new FileReader(); var _this = this; // function tmpLoad() { // 头不带图片格式,需填写格式 var re = /^data:base64,/; var ret = this.result + \'\'; if (re.test(ret)) ret = ret.replace(re, \'data:\' + _this.mime[_this.fileType] + \';base64,\'); //此处为自己加上的去掉base64不带“头”的判断 if (ret.indexOf(";base64,") >= 0) { var num = ret.indexOf(";base64,"); num = parseInt(num) + 8; _this.noHead = ret.substring(num); } callback && callback(ret); } reader.onload = tmpLoad; reader.readAsDataURL(file); return false; }; /** * @description 创建图片 * @param {Object} image 图片文件 */ UploadPic.prototype._createImage = function (uri) { var img = new Image(); var _this = this; function tmpLoad() { _this._drawImage(this); } img.onload = tmpLoad; img.src = uri; }; /** * @description 创建Canvas将图片画至其中,并获得压缩后的文件 * @param {Object} img 图片文件 * @param {Number} width 图片最大宽度 * @param {Number} height 图片最大高度 * @param {Function} callback 回调函数,参数为图片base64编码 * return {Object} 返回压缩后的图片 */ UploadPic.prototype._drawImage = function (img, callback) { // this.sw = img.width; // this.sh = img.height; //如果不需要压缩可将上面注释与下面的更换; this.tw = img.width; this.th = img.height; this.sw = 100; this.sh = 100; this.scale = (this.tw / this.th).toFixed(2); if (this.sw > this.maxWidth) { this.sw = this.maxWidth; this.sh = Math.round(this.sw / this.scale); } if (this.sh > this.maxHeight) { this.sh = this.maxHeight; this.sw = Math.round(this.sh * this.scale); } this.canvas = document.createElement(\'canvas\'); var ctx = this.canvas.getContext(\'2d\'); this.canvas.width = this.sw; this.canvas.height = this.sh; ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, this.sw, this.sh); this.callback(this.canvas.toDataURL(this.type)); ctx.clearRect(0, 0, this.tw, this.th); this.canvas.width = 0; this.canvas.height = 0; this.canvas = null; }; function upload() { var base64 = $("#base64").val(); var name = $("#name").text(); $.ajax( { type: "post", url: "Upload.ashx", data: { base64: base64, name: name }, async: true, success: function fun(rt) { alert(rt); } }); }; </script> </body> </html>
- 然后是后台C#代码,就是简单的转格式,保存文件那些。
<%@ WebHandler Language="C#" Class="Upload" %> using System; using System.Web; using System.Drawing; using System.Drawing.Imaging; using System.IO; public class Upload : IHttpHandler { public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; string base64 = context.Request["base64"]; string name = context.Request["name"]; byte[] arr = Convert.FromBase64String(base64); MemoryStream ms = new MemoryStream(arr); Bitmap bmp = new Bitmap(ms); //要保存的目录路径 string filePath = "Upimages"; filePath = context.Server.MapPath(filePath + "/" + name); //bmp.Save("Upimages/"+name + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); bmp.Save(filePath); ms.Close(); context.Response.Write("true"); } public bool IsReusable { get { return false; } } }
好啦,第一次写博客就是写到这里,不知道还有什么写差了的,希望各位提下建议,不喜可喷哈~
2014.08.22