首页 > 代码库 > 图片上传那些事
图片上传那些事
简述:
一个项目上,之前已经做好了用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同样提供了这样的接口,并且上面我们用到过。