支持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;
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 文件上傳在網頁應用中應該是一個很常用的功能,但是我是第一次做,所以也是網上找資料了,看了如阮一峰老師的《文件上傳的...
    jorgon閱讀 1,489評論 0 0
  • 本文包括:1、文件上傳概述2、利用 Commons-fileupload 組件實現文件上傳3、核心API——Dis...
    廖少少閱讀 12,613評論 5 91
  • 點擊查看原文 Web SDK 開發手冊 SDK 概述 網易云信 SDK 為 Web 應用提供一個完善的 IM 系統...
    layjoy閱讀 13,945評論 0 15
  • 連續靈修75天經文 【詩1:2】惟喜愛耶和華的律法,晝夜思想,這人便為有福。 《感動》我沒有做到晝夜思想神的話,但...
    報佳音閱讀 169評論 0 0
  • (1) 近日,兩張名為“觀音人民法庭婚姻家庭考試卷”的圖片在網上意外走紅。 一對育有一對子女的80后夫妻,本意到法...
    向陽花開啦閱讀 251評論 0 0