简述:
一个项目上,之前已经做好了用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同样提供了这样的接口,并且上面我们用到过。