如何在手机浏览器中运用HTML5的文件API实现上传多图功能

html-css014

如何在手机浏览器中运用HTML5的文件API实现上传多图功能,第1张

Html5终于解决了上传文件的同时显示文件上传进度的老问题。现在大部分的网站用Flash去实现这一功能,还有一些网站继续采用Html <form>with enctype=multipart/form-data,但是需要修改服务器端可用才能显示给用户文件上传的进度。本质上你需要做的工作是在服务器端接收一个文件时,你发送给它一个字节流,所以你需要知道你已经接收到多少字节并以某种方式传达这些信息给客户端浏览器,在这个过程一直在不断的进行文件的上传。这种方式运行的非常好,不像Flash上传那这样充满了问题(特别是处理大文件上传的时候),然而这种方法是相当复杂的并且听起来不容易理解,因为你本质上是接管了整个服务器端的处理(获取字节流的时候)同时包括了在服务器端实现multipart/form-data协议,伴随一系列的其他事情。

使用Html5 上传文件

XMLHttpRequest 在Html5 规范中已经有全新的变化,规定了XMLHttpRequest Level 2规范(目前最新版本)包含下列新的特性:

处理字节流,例如作为上传或者下载的File,Blob,FormData对象

上传或者下载中的进度事件

跨站点请求

允许创建匿名请求

可以设置请求超时

在这篇文章中我们将能够更清楚的看到#1和#2两个特性。通常,上传文件用XMLHttpRequest并且提供上传进度信息给最终的用户,需要注意的是这种方式解决了不需要服务器端做任何改变,至少是目前处理multipart/form-data协议。所以服务器端的处理逻辑保留不变,这使得开发者适应这种技术相当容易。

图1:文件上传画面-准备上传 图2:显示上传完成画面

注意:上面的图片中,信息提示区域是提供给用户的:

当前选中文件的信息

文件名

文件大小

文件类型

上传完成多少的百分比进度条

上传速度或者上传带宽

距离上传完成大概还有多长时间

已上传文件大小

服务器端的响应

上面第6项或许看起来不重要,但事实上是相当重要的。因为我们用XMLHttpRequest,上传发生在后台,页面没有发生跳转等任何变化,所以对于你用它处理其他一些事情来说是一个非常好的特性。

Html5 Progress Event

对于Html5 Progress Events规范,Html5 Progess Events提供了下列与本次讨论相关的信息

total - 总的字节数

loaded - 到目前为止上传的字节数

lengthComputable - 可计算的已上传字节

请注意到我们需要用两个信息去计算要显示给用户的其他所有信息。要计算出来其他的信息通过上面我们得到信息是相当容易的,但是那需要一些额外的代码并且创建一个定时器。

Html5 Progress Event 应该是什么

考虑到有一部分人想更好的提供给用户所有的信息,所以Html5 Progress Event应该更好的满足需要,因为它给浏览器供应商提供这些额外信息是相当简单的,所以建议progress event应该修改成如下:

total - 总的字节数

loaded - 到目前为止上传的字节数

lengthComputable - 可计算的已上传字节

transferSpeed long类型

timeRemaining JavaScript 日期对象

Html5 上传 用XMLHttpRequest

浏览器支持情况

支持这一特性的浏览器最低版本

Firefox 4.0 beta 6

Chrome 6

Safari 5.02

IE 9 Beta and Opera 10.62 不支持这一特性

简单的示例

下面是一个完整的Html页面包含了实现文件上传并带有进度提示的JavaScript代码,只是实现了基本的功能,感兴趣的可以自己做扩展。 需要吧上传接口修改成自己服务的。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

<!DOCTYPE html>

<html>

<head>

<title>Upload Files using XMLHttpRequest - Minimal</title>

<script type="text/javascript">

function fileSelected() {

var file = document.getElementById('fileToUpload').files[0]

if (file) {

var fileSize = 0

if (file.size >1024 * 1024)

fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB'

else

fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB'

document.getElementById('fileName').innerHTML = 'Name: ' + file.name

document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize

document.getElementById('fileType').innerHTML = 'Type: ' + file.type

}

}

function uploadFile() {

var fd = new FormData()

fd.append("fileToUpload", document.getElementById('fileToUpload').files[0])

var xhr = new XMLHttpRequest()

xhr.upload.addEventListener("progress", uploadProgress, false)

xhr.addEventListener("load", uploadComplete, false)

xhr.addEventListener("error", uploadFailed, false)

xhr.addEventListener("abort", uploadCanceled, false)

xhr.open("POST", "upload.do")//修改成自己的接口

xhr.send(fd)

}

function uploadProgress(evt) {

if (evt.lengthComputable) {

var percentComplete = Math.round(evt.loaded * 100 / evt.total)

document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%'

}

else {

document.getElementById('progressNumber').innerHTML = 'unable to compute'

}

}

function uploadComplete(evt) {

/* 服务器端返回响应时候触发event事件*/

alert(evt.target.responseText)

}

function uploadFailed(evt) {

alert("There was an error attempting to upload the file.")

}

function uploadCanceled(evt) {

alert("The upload has been canceled by the user or the browser dropped the connection.")

}

</script>

</head>

<body>

<form id="form1" enctype="multipart/form-data" method="post" action="Upload.aspx">

<div class="row">

<label for="fileToUpload">Select a File to Upload</label><br />

<input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected()"/>

</div>

<div id="fileName"></div>

<div id="fileSize"></div>

<div id="fileType"></div>

<div class="row">

<input type="button" onclick="uploadFile()" value="Upload" />

</div>

<div id="progressNumber"></div>

</form>

</body>

</html>

HTML5技术支持WebApp在手机上拍照,显示在页面上并上传到服务器。这是手机微博应用中常见的功能,当然你也可以在其它类型应用中适当使用此技术。

1、 视频流

HTML5 的 The Media Capture(媒体捕捉) API 提供了对摄像头的可编程访问,用户可以直接用 getUserMedia(请注意目前仅Chrome和Opera支持)获得摄像头提供的视频流。我们需要做的是添加一个HTML5 的 Video 标签,并将从摄像头获得的视频作为这个标签的输入来源。

<video id=”video” autoplay=”"></video>

<script>

var video_element=document.getElementById(‘video’)

if(navigator.getUserMedia){ // opera应使用opera.getUserMedianow

navigator.getUserMedia(‘video’,success,error)//success是回调函数,当然你也可以直接在此写一个匿名函数

}

function success(stream){

video_element.src=stream

}

</script>

此时,video 标签内将显示动态的摄像视频流。下面需要进行拍照了。

2、 拍照

拍照是采用HTML5的Canvas功能,实时捕获Video标签的内容,因为Video元素可以作为Canvas图像的输入,所以这一点很好实现。主要代码如下:

var canvas=document.createElement(‘canvas’)//动态创建画布对象

var ctx=canvas.getContext(’2d’)

var cw=vw,ch=vh

ctx.fillStyle=”#ffffff”

ctx.fillRect(0,0,cw,ch)

ctx.drawImage(video_element,0,0,cw,ch,0,0,vw,vh)//将video对象内指定的区域捕捉绘制到画布上指定的区域,可进行不等大不等位的绘制。

document.body.append(canvas)

3、 图片获取

从Canvas获取图片数据的核心思路是用canvas的toDataURL将Canvas的数据转换为base64位编码的PNG图像,类似于“data:image/pngbase64,xxxxx”的格式。

var imgData=canvas.toDataURL(“image/png”)

这样,imgData变量就存储了一长串的字符数据内容,表示的就是一个PNG图像的base64编码。因为真正的图像数据是base64编码逗号之后的部分,所以要让实际服务器接收的图像数据应该是这部分,我们可以用两种办法来获取。

第一种:是在前端截取22位以后的字符串作为图像数据,例如:

var data=imgData.substr(22)

如果要在上传前获取图片的大小,可以使用:

var length=atob(data).length//atob 可解码用base-64解码的字串

第二种:是在后端获取传输的数据后用后台语言截取22位以后的字符串(也就是在前台略过上面这步直接上传)。例如PHP里:

$image=base64_decode(str_replace(‘data:image/jpegbase64,’,”,$data)

4、 图片上传

在前端可以使用Ajax将上面获得的图片数据上传到后台脚本。例如使用jQuery时可以用:

$.post(‘upload.php’,{‘data’:data})

在后台我们用PHP脚本接收数据并存储为图片。

function convert_data($data){

$image=base64_decode(str_replace(‘data:image/jpegbase64,’,”,$data)

save_to_file($image)

}

function save_to_file($image){

$fp=fopen($filename,’w')

fwrite($fp,$image)

fclose($fp)

}

以上的解决方案不仅能用于Web App拍照上传,也可以通过Canvas的编辑功能函数提供图片编辑,例如裁剪、上色、涂鸦、圈点等功能,然后把用户编辑完的图片上传保存到服务器上。

在还在不断补充修正的HTML5的驱动下,Web App与Native App之间的距离将越来越小。在可预见的不远的未来,越来越多老的和新的开发项目必将会迁移到WEB应用上来。

相关规范:

The MediaCapture API:http://www.w3.org/TR/media-capture-api/

Canvas:http://dev.w3.org/html5/2dcontext/

1.1 分片、并发

分片与并发结合,将一个大文件分割成多块,并发上传,极大地提高大文件的上传速度。

当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。

1.2 预览、压缩

支持常用图片格式jpg,jpeg,gif,bmp,png预览与压缩,节省网络数据传输。

解析jpeg中的meta信息,对于各种orientation做了正确的处理,同时压缩后上传保留图片的所有原始meta数据。

1.3 多途径添加文件

支持文件多选,类型过滤,拖拽(文件&文件夹),图片粘贴功能。

粘贴功能主要体现在当有图片数据在剪切板中时(截屏工具如QQ(Ctrl + ALT + A), 网页中右击图片点击复制),Ctrl + V便可添加此图片文件。

1.4 HTML5 &FLASH

兼容主流浏览器,接口一致,实现了两套运行时支持,用户无需关心内部用了什么内核。

同时Flash部分没有做任何UI相关的工作,方便不关心flash的用户扩展和自定义业务需求。

1.5 MD5秒传

当文件体积大、量比较多时,支持上传前做文件md5值验证,一致则可直接跳过。

如果服务端与前端统一修改算法,取段md5,可大大提升验证性能,耗时在20ms左右。

1.6 易扩展、可拆分

采用可拆分机制, 将各个功能独立成了小组件,可自由搭配。

采用AMD规范组织代码,清晰明了,方便高级玩家扩展。

2、引入资源

2.1 下载包内容

├── Uploader.swf // SWF文件,当使用Flash运行时需要引入。

├── webuploader.js // 完全版本。

├── webuploader.min.js // min版本

├── webuploader.flashonly.js // 只有Flash实现的版本。

├── webuploader.flashonly.min.js // min版本

├── webuploader.html5only.js // 只有Html5实现的版本。

├── webuploader.html5only.min.js // min版本

├── webuploader.withoutimage.js // 去除图片处理的版本,包括HTML5和FLASH.

└── webuploader.withoutimage.min.js // min版本

2.2 或者直接使用由staticfile提供的cdn版本,或者下载Git项目包。

// SWF文件,当使用Flash运行时需要引入。

├── http://cdn.staticfile.org/webuploader/0.1.0/Uploader.swf

// 完全版本。

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.js

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.min.js

// 只有Flash实现的版本。

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.flashonly.js

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.flashonly.min.js

// 只有Html5实现的版本。

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.html5only.js

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.html5only.min.js

// 去除图片处理的版本,包括HTML5和FLASH.

├── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.withoutimage.js

└── http://cdn.staticfile.org/webuploader/0.1.0/webuploader.withoutimage.min.js

2.3 DIY打包

WebUploader文件打包借助了Grunt工具来实现

2.3.1 环境依赖

1.git命令行工具

2.node &npm命令行工具

3.grunt (npm install grunt-cli -g)

2.3.2 编译代码

1.克隆 webuploader git仓库,git clone https://github.com/fex-team/webuploader.git。

2.安装node依赖,npm install。

3.执行grunt dist,此动作会在dist目录下面创建合并版本的js, 包括通过uglify压缩的min版本。

2.3.3 配置

打开webuploader仓库根目录下面的Gruntfile.js文件, 代码合并有buildtask来完成。找到build配置项。

Gruntfile.js已经配置了一个自定义合并的demo. 打包只支持HTML5的版本

// 自己配置的实例

// glob语法。

custom: {

preset: "custom",

cwd: "src",

src: [

'widgets/**/*.js',

'runtime/html5/**/*.js' ],

dest: "dist/webuploader.custom.js"

}

3、angular指令——<web-uploader>

3.1 指令功能

添加一个上传文件按钮,可以自行配置上传文件的类型和过滤规则,且在弹出的模态框中进行操作,支持连续上传,分类选择上传

3.2 使用说明

这里只是使用说明,可能会加一些注意事项,具体参数或者变量说明请参看后面

3.2.1 页面添加一个指令

<web-uploader class="btn btn-info" type="image" accept="accept">uploader</web-uploader>

3.2.2 配置上传类型和过滤规则

上传类型

type有四种类型,分别为

image:图片

video:音视频

flash:flash

file:办公文档,压缩文件等等

过滤规则

accept有四个对象属性,属性中包含标题、允许文件后缀、允许的mimetype

3.2.3 指令中绑定弹出模态框的事件

web-uploader这个指令中其实只做了一件事,给元素本身绑定弹出模态框的事件,具体上传文件是在模态框中完成的

3.2.4 初始化uploader类,配置相关属性

在模态框控制器中用到了$timeout

$timeout(function(){

//这里是上传配置代码

},0)

因为配置uploader时需要事先准备好dom元素,angular打开模态框是异步而JavaScript是单线程,所以实际上在执行模态框控制器中的代码时,模态框并没有打开,也就是dom并没有加载完成,这会导致WebUploader报a.runningtime is not a function...的错误

3.3 指令详细说明

3.3.1 父级controller中的配置

.controller('myCtrl',['$scope', '$modal', function($scope, $modal){

//配置允许上传的类型 图片/音视频/flash/文件

$scope.accept = {

//图片

image: {

title : 'Images',//标题

extensions : 'gif,jpg,jpeg,bmp,png,ico',//允许上传文件的后缀

mimeTypes : 'image/*'//允许的mimetype

},

//音视频

video: {

title : 'Videos',

extensions : 'wmv,asf,asx,rm,rmvb,ram,avi,mpg,dat,mp4,mpeg,divx,m4v,mov,qt,flv,f4v,mp3,wav,aac,m4a,wma,ra,3gp,3g2,dv,vob,mkv,ts',

mimeTypes : 'video/*,audio/*'

},

//flash

flash: {

title : 'Flashs',

extensions : 'swf,fla',

mimeTypes : 'application/x-shockwave-flash'

},

//办公文档,压缩文件等等

file: {

title : 'Files',

extensions : 'zip,rar,ppt,pptx,doc,docx,xls,xlsx,pdf',

mimeTypes : 'application/zip,application/x-rar-compressed,application/vnd.ms-powerpoint,application/vnd.openxmlformats- officedocument.presentationml.presentation,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms- excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/pdf'

}

}

}])

3.3.2 指令web-uploader

.directive('webUploader', ['$modal', function($modal){

return{

restrict: 'AE',

scope: {

accept: '=accept'

},

link: function($scope, $element, $attr){

$element.bind('click',function(){

var modalInstance = $modal.open({

controller: 'modalCtrl',

templateUrl: 'template/webuploader.tpl.html',

size:'lg',

resolve: {

items: function(){

return {

accept: $scope.accept,

type: $attr.type

}

}

}

})

modalInstance.result.then(function(returnStatus){

console.log(returnStatus)

},function(){

console.log('Modal dismissed at: ' + new Date())

})

})

}

}

}])