首页 > 代码库 > 图片上传那些事

图片上传那些事

简述:

一个项目上,之前已经做好了用flash上传图片的功能,但是现在因为客户端的限制,要求不用flash,可以使用html5,于是改造开始了。

先给出之前flash的界面:

需求: 

上面的图片里面可以看出三个需求:

1. 文件选择按钮外观需要美化

2. 可以预览图片

3. 上传图片(上传后的图片存在形式需要与之前flash上传的一样,也就是说需要byte[]的形式)

用户上传图片之后,需要把新的图片显示到其他位置,所以还有一个需求:

4. 上传成功后调用回调函数

 

思考:

1. 文件选择按钮外观需要美化

  这个问题比较容易,下面会直接给出方案。

2. 可以预览图片

  这个问题对于html5来说也是小事一件,下面会给出方案。

3. 上传图片

4. 上传成功后调用回调函数

  单纯的上传图片,使用form提交即可,与服务端交互之后调用回调函数也比较容易,ajax即可。但是如果要同时满足两个条件,问题来了:

  P1 : form提交的方式如何回调?

  p2 :  ajax的方式如何上传图片?

oh,shit,dilemma problem...

 

解决方案:

1. 文件选择按钮外观需要美化

  首先要有一个文件选择按钮: <input type="file" /> 

  针对按钮本身去优化样式,应该是行不通的,所以目前比较流行的方式都是把按钮设置为透明的,然后覆盖在一个带有样式的“按钮”上。一个提供外观,一个提供文件选择功能。参考代码如下:

  

<style>
a {
    display: inline-block;
    width: 108px;
    height: 30px;
    background-repeat: no-repeat;
    background-image: url("fileChooser.jpg");
    position: relative;
    overflow: hidden;
}
input{
          position: absolute;
    right: 0;
    top: 0;
    font-size: 100px;
    opacity: 0;
    filter: alpha(opacity = 0);
    cursor: pointer;
}
</style>

<a href="#">
    <input type="file"/>
</a>

 

2. 可以预览图片

  预览图片有两种方式,但其实都是殊途同归,就是如何获取到图片的url

  2.1 使用FileReader把图片读到内存中,然后再获取以data:开头的图片数据,并设置给img的src中

    

<input type="file">
<img id="img">
<script type="text/javascript">
$("input").change(function(){
    var file = this.files[0];
    var reader = new FileReader();
    reader.onload = function(e){
        $("#img").attr("src", reader.result);
    }
    reader.readAsDataURL(file);
});
</script>

     有关FileReader的详情可以到 http://www.w3.org/TR/file-upload/#dfn-filereader 查看,这里暂时不详述了,之后再写一个专题介绍。

  2.2 还可以使用createObjectURL方法来获取到图片的url来设置给img

    

<input type="file">
<img id="img">
<script type="text/javascript">
$("input").change(function(){
    var file = this.files[0];
    $("#img").attr("src", getObjectURL(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;
}

 

3. 上传图片

4. 上传成功后调用回调函数

  这两个问题一起来说。

  上面已经提到了,实际上根据上传+回调的需求,有两种解决方案,但是都会遇到各自的问题,而实际证明,两个问题都可以解决,并且都可以达到目的。我们都分析一下。

  P1 : form提交的方式如何回调?

    首先我们确定上传图片使用form提交的方式,这个时候遇到的问题是如何进行回调。

    对于form提交,提交之后需要回调函数,并且参数是从服务端返回回来的这种情况下,都有一个统一的解法:

    增加一个隐藏的iframe,让form提交的时候target指向这个iframe(这是response会返回内容到这个iframe),同时启动一个定时器去查看iframe是否被写完,写完之后,就可以调用回调函数了。

    给一段伪代码:

<form id="form" action="some URL" target="hidenFrame" method="post">
    <input name="file" type="file">
</form>
<img id="img">
<button id="submit"></submit>
<iframe name="hidenFrame"></iframe>

<script type="text/javascript">
$("input").change(function(){
    var file = this.files[0];
    $("#img").attr("src", getObjectURL(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;
}
$("#submit").click(function(){
    var form = $("#form")[0];
    form.enctype = "multipart/form-data";
    if ($("input")[0].files.length) {
        form.submit();
        var interval = setInterval(
                function() {
                    var written = check();//check() is some code to check if response is written in iframe
                    if (written) {
                        callback();// call callback
                        clearInterval(interval);
                    }
                }, 100);
    } 
});

      上述代码只有check()部分没有给出,读者自行填充即可。一般可以在response里面写上一段javascript代码,其中定义一个方法,方法中返回回调函数所需的参数,interval中每次都检查

      iframe里面是否已经定义了约定好的获取返回参数的方法,即可同时达到判断response是否已经完毕以及获取回调函数参数两个目的。

    

  p2 :  ajax的方式如何上传图片?

     ajax对回调来说非常容易,但是form上传到服务端的时候上传的是什么东西?如何构造数据才能传到服务端,让服务端解析?

     解决这个问题有一个很NB的方法,用FormData。(因为我在听说这个对象之前,做了很多工作来转换图片,以求达到跟form一样的格式传到服务端,所以觉得很NB)

     直接给出代码:

      

<form id="form" action="some URL" target="hidenFrame" method="post">
    <input name="file" type="file">
</form>
<img id="img">
<button id="submit"></submit>
<iframe name="hidenFrame"></iframe>

<script type="text/javascript">
$("input").change(function(){
    var file = this.files[0];
    $("#img").attr("src", getObjectURL(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;
}
$("#submit").click(function(){
    $.ajax({
        url: "some URL",
        async: true,
        type: "post",
        contentType:false,  //需要设置false,否则jquery会处理二进制数据,但我们不希望jquery处理
        processData:false,  //需要设置false,否则jquery会处理二进制数据,但我们不希望jquery处理
        data: new FormData($("#form")[0]),
        success: function(serverData){
            callback(serverData);
        }
    });
});

    很明显的,使用FormData+ajax的方式对上传+回调的需求使用起来最简洁。

 

一些没提到的事情:

1.  服务端如何解析上传的图片

  这个问题在上面没有提到,这里给出java的一个实现,其中因为最后存储图片以及获取图片路径的方法是公司内部的方法,贴出来也没什么用,所以给隐去了,但骨架如下:

  

import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


public class PortraitRequestHandler implements JspRequestHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        
        try {
            request.setCharacterEncoding("GBK");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        // 文件上傳部分
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (isMultipart == true) {
            try {
                FileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload upload = new ServletFileUpload(factory);

                // 得到所有的表单域,它们目前都被当作FileItem
                List<FileItem> fileItems = upload.parseRequest(request);
                Iterator<FileItem> iter = fileItems.iterator();

                // 依次处理每个表单域
                while (iter.hasNext()) {
                    FileItem item = (FileItem) iter.next();
                    if (!item.isFormField()) {// 如果是上传域
                        // 获得文件名及路径
                        String fileName = item.getName();
                        if (fileName != null) {
                            
                            byte[] b = new byte[(int) item.getSize()];
                            item.getInputStream().read(b);
                            // 这里b就是图片的内容,可以调用需要的方法存储
                        }
                    }
                }
                PrintWriter out = response.getWriter();
                out.append("/* 这里返回刚上传图片的访问路径 */");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } 
    }

}

 

  

 2. Form上传的时候,上传的到底是什么形式的数据?

  Form也好FormData+ajax的方式也好,实际上用什么形式上传用户选择的文件,都是浏览器做的工作,我们不是很清楚。但是从上面的服务端程序就可以看出,实际上最终是以byte流的形式post到服务端的,

  服务端也用了apache的fileupload来处理。

  想想其实不难理解。文件肯定是以二进制流的形式传到服务端进行存储,呃,废话。

思考题:

如果不用FormData,ajax方式咋传图片?

  提两个思路,抛砖引玉:上文提到了FileReader,FileReader有readAsArrayBuffer方法可以把图片读成byte流。  

  另一个思路: 图片有自己特有的一个保存形式,即 data:开始的一段数据。 FileReader同样提供了这样的接口,并且上面我们用到过。