支持IE9的异步文件上传研究

背景描述

有这么一个需求:一个表单,有十几个input,一个文件上传的input,可以上传一张图片。

  1. 要求上传图片的input是隐藏的;
  2. 图片在上传之前可以预览
  3. 上传之前,可以控制上传文件小于2M
  4. 上传之前,可以限制上传图片的类型,只支持JPG/PNG
  5. 提交是异步的,如果失败,可以返回一个用户友好的信息。
  6. 用户如果换一张图片,还是可以限制大小、类型,并预览
  7. 提交之前,必须验证图片不能为空
  8. 不能重复上传图片
  9. 支持到Chrome、Firefox、IE11、IE0、IE9

方案1:使用jquery.form.jsajaxSubmit 异步提交表单。

先是在Chrome下开发。使用隐藏的file input。点击用作预览的div,触发 file input 的点击事件。

  <div class="coverImage">
    <img src="" alt="">
  </div>
$(document).on("click", ".coverImage", function(event){
  event.preventDefault();
  var _this=this;
  $(_this).closest(".classimg").find(".classimginput").trigger("click");
});

file input 的点击事件被触发之后,调用预览函数

<input type="file" style="display:none;" class="classimginput" onchange="classShowImage(this)">
/**
 * 商品封面显示图片预览
 * @param  {DOM} that 文件上传按钮
 */
function classShowImage(that){
  var objUrl = getObjectURL(that.files[0]) ;
  if (objUrl) {
    $(that).closest(".classimg").find(".classimgitem img").attr("src", objUrl);
  }
}
/**
 * 获取上传图片的url
 * @param  {object} file 上传的文件对象
 */
function getObjectURL(file) {
    var url = null;
    if (window.createObjectURL != undefined) { // basic
        url = window.createObjectURL(file);
    } else if (window.URL != undefined) { // mozilla(firefox)
        url = window.URL.createObjectURL(file);
    } else if (window.webkitURL != undefined) { // webkit or chrome
        url = window.webkitURL.createObjectURL(file);
    }
    return url;
}

要校验上传的图片不能为空:

    var $image = $form.find(".coverImage").children("img");
    if($image.length > 0) {

        $src = $image.attr("src");

        if(undefined === $src || '' == $src)
        {
            $.dialog({ content : '商品封面不能为空!', width : "300px", ok : function() { } }); return false;
        }
    }

要限制图片的大小和类型,在classShowImage函数里添加校验:

/**
 *发布商品/商品管理-- 商品封面显示图片预览
 * @param  {DOM} that 文件上传按钮
 */
function classShowImage(that){
    var file = that.files[0];
    var fileType = file["type"];
    var ValidImageTypes = [ "image/jpeg", "image/png"];
    if ($.inArray(fileType, ValidImageTypes) < 0) {
        $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); return false;
        return false;
    }
    var min_file_size = fileSizeToBytes(2, 'MB');
    if(file.size > min_file_size)
    {
        console.log('对不起,您提交的文件超出了大小限制!');
        $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); return false;
    }

    var objUrl = getObjectURL(that.files[0]) ;
    if (objUrl) {
        $(that).closest(".classimg").find(".classimgitem img").attr("src", objUrl);

    }
}

但实际上,这样是不能限制上传图片的大小和格式的。在调用classShowImage预览的时候,大小和格式不对,返回false,没有预览,但是文件实际上是进到 file input 里了。再次点击cover div,弹出文件选择框,如果还选择同一张图片,却不能触发 file input 的 onchange 事件了,因为file input 的内容没变。

页面表现就是:选一个不合标准的图片,第一次提示超出大小限制或者不支持。再选这张图片,就没什么反应了。

如果没有进一步的校验,提交的时候,就可以提交到后台了。

那么,就有两个问题需要解决了:

  1. 不能把不符合标准的图片上传到服务器
  2. 再次选择同一张不符合标准的图片,要有反应。
  • 注意到上面的 file input 是没有 name 属性的。

没有name 的file input 会不会被上传?

  • 观察了一下,似乎没有被上传。

那么一个方案就有了:每次提交之前,clone一个带name的file input

为了解决第一个问题,引入了 clone.

/**
 *发布商品/商品管理-- 商品封面显示图片预览
 * @param  {DOM} that 文件上传按钮
 */
function classShowImage(that){
    var file = that.files[0];
    var fileType = file["type"];
    var ValidImageTypes = [ "image/jpeg", "image/png"];
    if ($.inArray(fileType, ValidImageTypes) < 0) {
        $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); 
        return false;
    }
    var min_file_size = fileSizeToBytes(2, 'MB');
    if(file.size > min_file_size)
    {
        $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); 

        return false;
    }
    
    // 这里是不是应该先把 可能存在的clone 去掉啊。否则可能重复  
    $parent = $(that).parent();
    var preClone = $parent.find(".classimginput_realUpload");
    console.log(preClone);
    if(preClone.length > 0) {
        preClone.remove();
    }

    $parent = $(that).parent();
    $clone = $(that).clone().removeClass('classimginput').addClass('classimginput_realUpload').attr("name","myImage" );
    $clone[0].onchange = undefined;
    $parent.append($clone);
    var objUrl = getObjectURL(that.files[0]) ;
    if (objUrl) {
        $(that).closest(".classimg").find(".classimgitem img").attr("src", objUrl);
    }
}

为了解决第二个问题,从 stackoverflow 找到这个方案

<input type="file" style="display:none;" class="classimginput" onchange="classShowImage(this); this.value=null; return false;">

在Chrome和Firefox下,完美执行。但是遇到IE11就悲剧了。

对于IE来说,onchange 事件每次都触发两次。原因是:

Internet Explorer 11 fires the onchange event of a file input field when resetting it's value programmatically

即:使用代码改变 file input 的,也要触发 onchange 事件。

解决IE事件触发两次的问题

使用一个 flag 变量。在 div 点击的时候设置为true,在 classShowImage 执行一次的时候,设置为false。如果 flag 为false,则 classShowImage 不执行。

var flag = false;

$(document).on("click", ".coverImage", function(event){
  event.preventDefault();
  flag = true;
  var _this=this;
  $(_this).closest(".classimg").find(".classimginput").trigger("click");
});
/**
 *发布商品/商品管理-- 商品封面显示图片预览
 * @param  {DOM} that 文件上传按钮
 */
function classShowImage(that){
    if(!flag)
    {
        return;
    }
    var file = that.files[0];
    var fileType = file["type"];
    var ValidImageTypes = [ "image/jpeg", "image/png"];
    if ($.inArray(fileType, ValidImageTypes) < 0) {
        $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); 
        return false;
    }
    var min_file_size = fileSizeToBytes(2, 'MB');
    if(file.size > min_file_size)
    {
        $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); 

        return false;
    }
    
    // 这里是不是应该先把 可能存在的clone 去掉啊。否则可能重复
    $parent = $(that).parent();
    var preClone = $parent.find(".classimginput_realUpload");
    console.log(preClone);
    if(preClone.length > 0) {
        preClone.remove();
    }
    
    $parent = $(that).parent();
    $clone = $(that).clone().removeClass('classimginput').addClass('classimginput_realUpload').attr("name","myImage" );
    $clone[0].onchange = undefined;
    $parent.append($clone);
    var objUrl = getObjectURL(that.files[0]) ;
    if (objUrl) {
        $(that).closest(".classimg").find(".classimgitem img").attr("src", objUrl);
    }
    flag = false;
}

这样完美的解决了IE11 的 onchange 事件 触发两次的问题。但是在IE11上传的时候就悲剧了。

  • IE 不允许 使用 隐藏的 file input 上传文件。每次上传都是空的。

IE11上传失败

-----------------------------7e11e02c10450
Content-Disposition: form-data; name="tb_teaching_service_courseImage"

null
-----------------------------7e11e02c10450
Content-Disposition: form-data; name="tb_teaching_service_courseImage"

null

上传的数据是空的。

  • 无论是原来的 隐藏file input,还是clone,都是上传失败。

目前看,似乎IE 不能clone。解决办法,就是 校验失败的时候,把 file input 移出 form。成功之后,再移入form。
https://hromnik.wordpress.com/2013/10/02/clone-file-input/

原因在这里

IE won't let you hide a type="file" input for security reasons. Apparently, Chrome has a more refined way of detecting whether a user is actually interacting with the screen, versus whether events are triggered programatically. There are also reported issues with this in Opera and FF.

You can get around this issue with a css "hack":

[Note - tested in IE10 - did not test $.post() but I believe it should still work]

Show code snippet

This doesn't explain a number of the described behaviors in your post, and I assume it's because there's additional factors at play in code that you haven't posted here.

It's pretty well documented on SO (here, here, here, and the primary reference to this posted answer) that you cannot trigger the change event of a hidden file input in a number of browsers. HTH


https://hromnik.wordpress.com/2013/10/02/clone-file-input/

http://jsfiddle.net/E8DBZ/2/

file upload is not possible through ajax. You can upload file, without refreshing page by using IFrame. you can check further detail here

UPDATE:

With XHR2, File upload through AJAX is supported. E.g. through FormData object, but unfortunately it is not supported by all/old browsers.

FormData support starts from following desktop browsers versions. IE 10+, Firefox 4.0+, Chrome 7+, Safari 5+, Opera 12+

For more detail, see MDN link

Internet Explorer 11 fires the onchange event of a file input field when resetting it's value programmatically

https://www.alfajango.com/blog/ajax-file-uploads-with-the-iframe-method/

http://blueimp.github.io/jQuery-File-Upload/

stackoverflow 上的一个解决方案。

方案2:使用FormData

    $('#onlineCourseForm').submit(function() {

        var $form = $(this);

        var Data = new FormData();
        var allInput = $(this).find('input').each(function() {

            var $this = $(this);
            if($this.attr('type') == 'file')
            {
                Data.append("tb_teaching_service_courseImage",this.files[0]);
            } else {
                Data.append($this.attr('name'),$this.val());
            }
        });

        $.ajax({
            url: url,
            type: 'POST',
            data: Data,
            cache: false,
            contentType: false,     //不可缺参数
            processData: false,     //不可缺参数
            success:function(data) {
                console.log(data);
                afterSuccess(data, $form);
            }
        });

        return false;
    });

然而,直到IE10,才开始支持 FormData。

也就是说,要想支持IE9,还得想其他解决方案。同时,要想完成:

  1. 上传之前,可以控制上传文件小于2M
  2. 上传之前,可以限制上传图片的类型,只支持JPG/PNG

这两条,需要用到 File API,然而:

That requires the File API, which isn't supported by IE9.

(And note that size is already a number [on browsers that support the File API], no need to parseInt it.)

@user875293: Not using just JavaScript and web standards, no. It may be possible with things like Flash, (signed) Java applets, or Silverlight. – T.J. Crowder Apr 30 '13 at 17:25

If you go to Internet Explorer, Tools, Internet Option, Security, Custom, find the "Include local directory path When uploading files to a server" (it is quite a ways down) and click on "Enable" . This will work

同时,IE9的预览也是一个问题。

IE9的预览

网上找了两篇文章:

http://www.it610.com/article/2328406.htm

http://www.cnblogs.com/rubylouvre/p/4597344.html

其中,前端大牛司徒正美的这篇说的比较清楚

按照这篇文章,可以炮制出:

function classShowImage( that) {

    if ( !(GetIEVersion() && GetIEVersion() < 10) ){
        var file = that.files[0];
        if(file && file["type"])
        {
            var fileType = file["type"];
            var ValidImageTypes = [ "image/jpeg", "image/png"];
            if ($.inArray(fileType, ValidImageTypes) < 0) {
                $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); 
                return false;
            }
        }
        var min_file_size = fileSizeToBytes(2, 'MB');
        if( file && file.size && file.size > min_file_size)
        {
            $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); 
            return false;
        }
    }

    if(that && that.files && that.files[0] )
    {
        var objUrl = getObjectURL(that.files[0]) ;
        if (objUrl) {
            $(that).closest(".classimg").find(".classimgitem img").attr("src", objUrl);
        }
    } else {
        $file = $(that);
        dataURL = $file.val();

        var realImg = $(that).closest(".classimg").find(".classimgitem img")[0];
        realImg.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)";
        realImg.filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = dataURL;
    }
}

这样就完美解决IE9的预览问题了。但是还不解决限制文件类型、大小、异步上传的问题。

方案3:使用plupload

直到看到《前端上传组件Plupload使用指南》 这篇文章,打算用 plupload 试一试。

首先把demo 复制到本地。主要是看4、图片预览功能demo

试了一下,可以完美的实现上传、预览功能。接下来要做的就是实现 限制类型、大小,已经限制重复文件的问题了。

  • flash的地址必须写对,否则,对于IE9来说,可能会就是莫名其妙的不起作用
    var uploader = new plupload.Uploader({ //实例化一个plupload上传对象
        browse_button : 'browse',
        url : 'upload.html',
        flash_swf_url : 'js/Moxie.swf',                 // 实际项目中的地址,一定要写对
        silverlight_xap_url : 'js/Moxie.xap',           //
    });
    uploader.init(); //初始化

否则的话,IE就是预览不出来。如果debug的话,会发现

function previewImage(file,callback){//file为plupload事件监听函数参数中的file对象,callback为预览图片准备完成的回调函数
  if(!file || !/image\//.test(file.type)) return; //确保文件是图片
  if(file.type=='image/gif'){//gif使用FileReader进行预览,因为mOxie.Image只支持jpg和png
    var fr = new mOxie.FileReader();
    fr.onload = function(){
      callback(fr.result);
      fr.destroy();
      fr = null;
    }
    fr.readAsDataURL(file.getSource());
  }else{
    var preloader = new mOxie.Image();
    preloader.onload = function() {
      preloader.downsize( 300, 300 );//先压缩一下要预览的图片,宽300,高300
      var imgsrc = preloader.type=='image/jpeg' ? preloader.getAsDataURL('image/jpeg',80) : preloader.getAsDataURL(); //得到图片src,实质为一个base64编码的数据
      callback && callback(imgsrc); //callback传入的参数为预览图片的url
      preloader.destroy();
      preloader = null;
    };
    // var a = file.getSource() ;
    preloader.load( file.getSource() );
  }
}

这里的 file 这个变量,里面没有size 属性。说明flash的地址不对。而IE9 不支持 File API,要想获得文件大小和类型,必须通过flash。

  • 注意到:这个插件是可以上传多文件的。首先必须解决这个:

这篇文章 里说,

这里官方api里面有removeFile(file)但是,用再这里不太好使。于是使用了另一个方法splice

试了一下,果然不好用

在demo的绑定事件里修改一下:

    //绑定文件添加进队列事件
    uploader.bind('FilesAdded',function(uploader,files){

      plupload.each(files,function(file) {
        if(uploader.files.length > 1)
        {
          uploader.files.splice(file.id, 1);      //删除部分文件
          // uploader.removeFile(file.id);        // 不起作用 
        }
      });

      for(var i = 0, len = files.length; i<len; i++){
        var file_name = files[i].name; //文件名
        !function(i){
          previewImage(files[i],function(imgsrc){
            $(_this).find("img").attr("src", imgsrc);
          })
        }(i);
      }
    });

上传之前,限制大小和类型:

        var ValidImageTypes = [ "image/jpeg", "image/png"];
        if ($.inArray(file.type, ValidImageTypes) < 0) {
          $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); return false;
          return false;
        }

        var min_file_size = fileSizeToBytes(2, 'MB');
        if( file.size > min_file_size)
        {
            $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); return false;
            return false;
        }

可以先把图片上传到服务器,返回图片地址,然后再跟其他input一起提交。

      uploader.bind('BeforeUpload', function(uploader,file) {

        var ValidImageTypes = [ "image/jpeg", "image/png"];
        if ($.inArray(file.type, ValidImageTypes) < 0) {
          $.dialog({ content : '仅支持JPG/PNG格式的文件!', width : "300px", ok : function() { } }); return false;
          return false;
        }

        var min_file_size = fileSizeToBytes(2, 'MB');
        if( file.size > min_file_size)
        {
            $.dialog({ content : '对不起,您提交的文件超出了大小限制!', width : "300px", ok : function() { } }); return false;
            return false;
        }

      });
      
      // 上传成功之后,把获得的地址放到form 的input里提交到服务器。
      uploader.bind('FileUploaded', function(up, file, info) {
        if(info && info.response)
        {
          var reData = $.parseJSON(info.response)

          if(reData.result) {

              var myImage = reData.myImage;
              $form.find("input[name=\"service[myImage]\"]").val(myImage);

            return reData.result;
          } else {
            return false;
          }

        } else {

          $.dialog({ content : '上传商品封面失败!请稍后再试', width : "300px", ok : function() { } }); return false;
          return false;
        }

      });
      
      // 上传失败处理
      uploader.bind('Error', function(uploader, data) {

      });

      // 开始上传
      uploader.start();
    } else {
      return false;
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 文件上传在网页应用中应该是一个很常用的功能,但是我是第一次做,所以也是网上找资料了,看了如阮一峰老师的《文件上传的...
    jorgon阅读 1,457评论 0 0
  • 本文包括:1、文件上传概述2、利用 Commons-fileupload 组件实现文件上传3、核心API——Dis...
    廖少少阅读 12,513评论 5 91
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,662评论 0 15
  • 连续灵修75天经文 【诗1:2】惟喜爱耶和华的律法,昼夜思想,这人便为有福。 《感动》我没有做到昼夜思想神的话,但...
    报佳音阅读 119评论 0 0
  • (1) 近日,两张名为“观音人民法庭婚姻家庭考试卷”的图片在网上意外走红。 一对育有一对子女的80后夫妻,本意到法...
    向阳花开啦阅读 228评论 0 0