html5 文件系统,Html5进阶 文件系统

在javascript的世界里无法处理二进制数据,如果需要处理,只能使用charCodeAt()方法,一个个字节地从文字编码转成二进制数据,还有一种办法是将二进制数据转成Base64编码,再进行处理。这两种方法不仅速度慢,而且容易出错。ECMAScript 5引入了Blob对象,允许直接操作二进制数据。

1 Blob对象

Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的API(比如File对象),都是建立在Blob对象基础上的,继承了它的属性和方法。

生成Blob对象有两种方法:一种是使用Blob构造函数,另一种是对现有的Blob对象使用slice方法切出一部分。

(1)Blob构造函数,接受两个参数。第一个参数是一个包含实际数据的数组,第二个参数是数据的类型,这两个参数都不是必需的。

var htmlParts = ["hey!"];

var myBlob = new Blob(htmlParts, { "type" : "text\/xml" });

下面是一个利用Blob对象,生成可下载文件的例子。

var a = document.createElement("a");

a.href = window.URL.createObjectURL(myBlob);

a.download = "hello-world.txt";

a.textContent = "Download Hello World!";

body.appendChild(a);

上面的代码生成了一个超级链接,点击后提示下载文本文件hello-world.txt,文件内容为“Hello World”。

(2)Blob对象的slice方法,将二进制数据按照字节分块,返回一个新的Blob对象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一个使用XMLHttpRequest对象,将大文件分割上传的例子。

function upload(blobOrFile) {

var xhr = new XMLHttpRequest();

xhr.open('POST', '/server', true);

xhr.onload = function(e) { ... };

xhr.send(blobOrFile);

}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {

var blob = this.files[0];

const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.

const SIZE = blob.size;

var start = 0;

var end = BYTES_PER_CHUNK;

while(start < SIZE) {

upload(blob.slice(start, end));

start = end;

end = start + BYTES_PER_CHUNK;

}

}, false);

});

这里有个小问问题,分片上传每次流量为1024*1024,也就是1m,最后一次上传的量需要单独计算,不一定刚好为1m。

var totalSize = file.size //获取file的大小 单位字节

var perchunk = 1024*1024,strart = 0,end = perchunk;

var count = totalSize % perchunk == 0 ? totalSize/perchunk : Math.floor(totalSize/perchunk) + 1 //需要上传的次数

var index = 0;

while(index < count ){

if(index == count -1){

upload(start , totalSize)

}else{

upload(start , end)

}

start = end

end = start + perchunk

index ++

}

这样写才能保证数据正确的传输

3)Blob对象有两个只读属性:

size:二进制数据的大小,单位为字节。

type:二进制数据的MIME类型,全部为小写,如果类型未知,则该值为空字符串。

下面展示一段我学习blob的代码

Reading a Binary Large Object (blob)

Displays file data between a starting byte value and ending byte value.

Select a file:

Enter byte range:

Start byte (counting from the 0th byte).

End byte (counting from the 0th byte).

Show File Content for Byte Range

js代码

var globals =

{

selectedFile: null,

fileSize: 0

};

window.addEventListener('load', init, false);

// 检测是否支持filereader,下面会讲到

function fileApiSupportCheck() {

if (window.File && window.FileReader && window.FileList && window.Blob) {

// All the File APIs are supported.

}

else {

document.getElementById('alert').innerHTML = '

FILE APIs NOT FULLY SUPPORTED - UPDATE YOUR BROWSER

';

}

}

function init() {

fileApiSupportCheck();

document.getElementById('fileInput').addEventListener('change', fileInputHandler, false);

document.getElementById('byteRangeButton').addEventListener('click', byteRangeButtonHandler, false);

}

//文件上传回调函数

function fileInputHandler(evt) {

var f = evt.target.files[0]; // The solo file selected by the user.

if (!f.size) {

document.getElementById('alert').innerHTML = 'Please select a file (that contains some content).';

return;

}

globals.selectedFile = f;

globals.fileSize = f.size;

document.getElementById('byteCount').innerHTML = '

' + f.name + ' (' + (f.type || 'n/a') + ') = ' + f.size + ' bytes';

document.getElementById('hidder').style.visibility = "visible"; // We've selected a valid file and can now safely show the byte range input markup.

}

function byteRangeButtonHandler() {

var start = Number(document.getElementById('byteStart').value);

var end = Number(document.getElementById('byteEnd').value);

readBlob(start, end);

}

(4) 普及一个知识

通过canvas绘制的图片可以和blob在一起使用

var canvas = document.getElementById('canvas');

canvas.toBlob(function(blob) {

var newImg = document.createElement('img'),

url = URL.createObjectURL(blob);

newImg.onload = function() {

// no longer need to read the blob so it's revoked

URL.revokeObjectURL(url);

};

newImg.src = url;

document.body.appendChild(newImg);

});

Note that here we're creating a PNG image; if you add a second parameter to the toBlob() call, you can specify the image type. For example, to get the image in JPEG format:

大概意思是说默认是png的格式,我们可以手动制定格式类型。

canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG at 95% quality

我们讲了这么多blob对象的知识,那么它的应用何在,我们接着看

2 FileList对象

File API提供File对象,它是FileList对象的成员,包含了文件的一些元信息,比如文件名、上次改动时间、文件大小和文件类型。

var selected_file = document.getElementById('input').files[0];

var fileName = selected_file.name;

var fileSize = selected_file.size;

var fileType = selected_file.type;

File对象的属性值如下。

name:文件名,该属性只读。

size:文件大小,单位为字节,该属性只读。

type:文件的MIME类型,如果分辨不出类型,则为空字符串,该属性只读。

lastModified:文件的上次修改时间,格式为时间戳。

lastModifiedDate:文件的上次修改时间,格式为Date对象实例。

3d0e318171e1

我们聊了这么多,无非就是想在文件上传的过程中使用这些api,那么还要继续学习。

3 FileReader API

FileReader API用于读取文件,即把文件内容读入内存。它的参数是File对象或Blob对象。

对于不同类型的文件,FileReader提供不同的方法读取文件。

readAsBinaryString(Blob|File):返回二进制字符串,该字符串每个字节包含一个0到255之间的整数。

readAsText(Blob|File, opt_encoding):返回文本字符串。默认情况下,文本编码格式是’UTF-8’,可以通过可选的格式参数,指定其他编码格式的文本。

readAsDataURL(Blob|File):返回一个基于Base64编码的data-uri对象。

readAsArrayBuffer(Blob|File):返回一个ArrayBuffer对象。

readAsText方法用于读取文本文件,它的第一个参数是File或Blob对象,第二个参数是前一个参数的编码方法,如果省略就默认为UTF-8编码。该方法是异步方法,一般监听onload件,用来确定文件是否加载结束,方法是判断FileReader实例的result属性是否有值。其他三种读取方法,用法与readAsText方法类似。

var reader = new FileReader();

reader.onload = function(e) {

var text = e.target.result;

}

reader.readAsText(file, encoding);

亲测了一遍,这个text在utf-8下是乱码的。

readAsDataURL方法返回一个data URL,它的作用基本上是将文件数据进行Base64编码。你可以将返回值设为图像的src属性。

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

if(file.type.indexOf('image') !== -1) {

var reader = new FileReader();

reader.onload = function (e) {

var dataURL = e.target.result;

}

reader.readAsDataURL(file);

}

dataURL是一段base64编码的url。

什么是 base64 编码?

我不是来讲概念的,直接切入正题,图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址。

那么为什么要使用 base64 传输图片文件?上文也有提及,因为这样可以节省一个 http 请求。图片的 base64 编码可以算是前端优化的一环。效益虽小,但却能积少成多。

readAsBinaryString方法可以读取任意类型的文件,而不仅仅是文本文件,返回文件的原始的二进制内容。这个方法与XMLHttpRequest.sendAsBinary方法结合使用,就可以使用JavaScript上传任意文件到服务器。

var reader = new FileReader();

reader.onload = function(e) {

var rawData = reader.result;

}

reader.readAsBinaryString(file);

FileReader对象采用异步方式读取文件,可以为一系列事件指定回调函数。

onabort方法:读取中断或调用reader.abort()方法时触发。

onerror方法:读取出错时触发。

onload方法:读取成功后触发。

onloadend方法:读取完成后触发,不管是否成功。触发顺序排在 onload 或 onerror 后面。

onloadstart方法:读取将要开始时触发。

onprogress方法:读取过程中周期性触发。

下面是一个onprogress事件回调函数的例子,主要用来显示读取进度。

var reader = new FileReader();

reader.onprogress = updateProgress;

function updateProgress(evt) {

if (evt.lengthComputable) {

var percentLoaded = Math.round((evt.loaded / evt.totalEric Bidelman) * 100);

var progress = document.querySelector('.percent');

if (percentLoaded < 100) {

progress.style.width = percentLoaded + '%';

progress.textContent = percentLoaded + '%';

}

}

}

下面跟大家讲一个很重要的知识点,拖拽文件上传的实现,相信大家在qq里面已经用到过。

οndragenter="document.getElementById('output').textContent = ''; event.stopPropagation(); event.preventDefault();"

οndragοver="event.stopPropagation(); event.preventDefault();"

οndrοp="event.stopPropagation(); event.preventDefault();

dodrop(event);">

DROP FILES HERE FROM FINDER OR EXPLORER

在新的html5 api里面有关于拖拽讲解的,这里就不说了,大家可以自行google。

function dodrop(event)

{

var dt = event.dataTransfer;

var files = dt.files;

var count = files.length;

output("File Count: " + count + "\n");

for (var i = 0; i < files.length; i++) {

var fileReader = new fileReader()

fileReader.onload = function(e){

document.createElement('.img').src = e.target.result

}

fileReader.readAsDataURL(files[i])

output(" File " + i + ":\n(" + (typeof files[i]) + ") : " +

files[i].name + " " + files[i].size + "\n");

}

}

function output(text)

{

document.getElementById("output").textContent += text;

//dump(text);

}

在ondrop事件上通过event.dataTransfer获取files对象。

4 文件断点续传

文件名文件类型文件大小上传进度

{{fileName}}{{fileType}}{{fileSize}}{{progress}}

3d0e318171e1

// 选择文件-显示文件信息

$('#myFile').change(function(e) {

var file,

uploadItem = [],

uploadItemTpl = $('#file-upload-tpl').html(),

size,

percent,

progress = '未上传',

uploadVal = '开始上传';

for (var i = 0, j = this.files.length; i < j; ++i) {

file = this.files[i];

percent = undefined;

progress = '未上传';

uploadVal = '开始上传';

// 计算文件大小

size = file.size > 1024

? file.size / 1024 > 1024

? file.size / (1024 * 1024) > 1024

? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'

: (file.size / (1024 * 1024)).toFixed(2) + 'MB'

: (file.size / 1024).toFixed(2) + 'KB'

: (file.size).toFixed(2) + 'B';

// 初始通过本地记录,判断该文件是否曾经上传过

percent = window.localStorage.getItem(file.name + '_p');

if (percent && percent !== '100.0') {

progress = '已上传 ' + percent + '%';

uploadVal = '继续上传';

}

// 更新文件信息列表

uploadItem.push(uploadItemTpl

.replace(/{{fileName}}/g, file.name)

.replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件')

.replace('{{fileSize}}', size)

.replace('{{progress}}', progress)

.replace('{{totalSize}}', file.size)

.replace('{{uploadVal}}', uploadVal)

);

}

$('#upload-list').children('tbody').html(uploadItem.join(''))

.end().show();

});

当选择文件后,我们把信息渲染到了table列表里面了。当我们单独点击某个文件后该文件就开始上传。

$(document).on('click', '.upload-item-btn', function() {

var $this = $(this),

state = $this.attr('data-state'),

msg = {

done: '上传成功',

failed: '上传失败',

in: '上传中...',

paused: '暂停中...'

},

fileName = $this.attr('data-name'),

$progress = $this.closest('tr').find('.upload-progress'),

eachSize = 1024,

totalSize = $this.attr('data-size'),

chunks = Math.ceil(totalSize / eachSize),

percent = window.localStorage.getItem(fileName + '_percent') || 0;

chunk = window.localStorage.getItem(fileName + '_chunk') || 0;

// 暂停上传操作

isPaused = 0;

// 进行暂停上传操作

// 未实现,这里通过动态的设置isPaused值并不能阻止下方ajax请求的调用

if (state === 'uploading') {

$this.val('继续上传').attr('data-state', 'paused');

$progress.text(msg['paused'] + percent + '%');

window.localStorage.setItem(fileName + '_status','0');

isPaused = 1;

console.log('暂停:', isPaused);

}

// 进行开始/继续上传操作

else if (state === 'paused' || state === 'default') {

$this.val('暂停上传').attr('data-state', 'uploading');

window.localStorage.setItem(fileName + '_status','1');

isPaused = 0;

}

// 第一次点击上传

if(ispaused == 0)

startUpload(chunk);

// 上传操作 times: 第几次

function startUpload(chunk) {

// 上传之前查询是否以及上传过分片

chunk = parseInt(chunk, 10);

// 判断是否为末分片

var isLastChunk = (chunk == (chunks - 1) ? 1 : 0);

// 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传

if (times === 'first' && isLastChunk === 1) {

window.localStorage.setItem(fileName + '_chunk', 0);

chunk = 0;

isLastChunk = 0;

}

// 设置分片的开始结尾

var blobFrom = chunk * eachSize, // 分段开始

blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段结尾

percent = (100 * blobTo / totalSize).toFixed(1), // 已上传的百分比

timeout = 5000, // 超时时间

fd = new FormData($('#myForm')[0]);

fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件

fd.append('fileName', fileName); // 文件名

fd.append('totalSize', totalSize); // 文件总大小

fd.append('isLastChunk', isLastChunk); // 是否为末段

fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上传)

// 上传

$.ajax({

type: 'post',

url: '/fileTest.php',

data: fd,

processData: false,

contentType: false,

timeout: timeout,

success: function(rs) {

rs = JSON.parse(rs);

// 上传成功

if (rs.status === 200) {

// 记录已经上传的百分比

window.localStorage.setItem(fileName + '_p', percent);

// 已经上传完毕

if (chunk === (chunks - 1)) {

$progress.text(msg['done']);

$this.val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');

if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {

$('#upload-all-btn').val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');

}

} else {

// 记录已经上传的分片

window.localStorage.setItem(fileName + '_chunk', ++chunk);

$progress.text(msg['in'] + percent + '%');

// 每回再次执行,需要判断状态值

if (window.localStorage.getItem(fileName + '_status') == 1) {

startUpload(chunk++);

}

}

}

// 上传失败,上传失败分很多种情况,具体按实际来设置

else if (rs.status === 500) {

$progress.text(msg['failed']);

}

},

error: function() {

$progress.text(msg['failed']);

}

});

}

});

我们这里需要注意一个问题,点击暂停后改变了状态paused = 1,在localstorage里面设置了_status为0,那么之前的ajax请求就需要判断每次请求的状态值是否为1,为1才会在回调中递归执行。

FormData 对象的使用

通过FormData对象可以组装一组用 XMLHttpRequest

发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit() 方法传输的数据格式相同

举个例子说明吧

我们通过post方法向服务器提交数据,其实我还可以不写action就是提交地址。

var formData = new FormData($('#myForm'));

var request = new XMLHttpRequest();

request.open("POST", "fileTest.php");

request.send(formData);

使用FormData对象上传文件

Your email address:

Custom file label:

File to stash:

然后使用下面的代码发送请求:

var form = document.forms.namedItem("fileinfo");

form.addEventListener('submit', function(ev) {

var oOutput = document.querySelector("div"),

oData = new FormData(form);

oData.append("CustomField", "This is some extra data");

var oReq = new XMLHttpRequest();

oReq.open("POST", "stash.php", true);

oReq.onload = function(oEvent) {

if (oReq.status == 200) {

oOutput.innerHTML = "Uploaded!";

} else {

oOutput.innerHTML = "Error " + oReq.status + " occurred when trying to upload your file.
";

}

};

oReq.send(oData);

ev.preventDefault();

}, false);

构造的formdata可以通过append添加额外数据。注意也可以append blob对象。

最后说一下formdata的使用场景:

使用jquery的serializeArray函数,但是这个方法对于input[type="file"]是无效的,FormData可以用来处理带有multipart/form-data编码类型的表单,一般都是带有input[type="file"]的表单.

大家有兴趣自己可以去看下表单提交的知识,有时间我会讲一期专题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值