Foreword
Hello. Not so long ago, I wrote
an article on creating a flash image loader . There I mentioned that the loader can be implemented using the html5 File API. A few nights and - hurray - I did it. It is time to tell what tricks I used, in which browsers it works, and whether to use it at all.
Let me remind you in brief of the requirements: you must implement an image loader that supports batch loading, creating thumbnails (and uploading them to the server), and an acceptable interface.
I understand perfectly well that my article uses the current implementation of a not yet fully developed standard, and therefore I will list browsers that are relevant at the moment:
- Firefox 8
- Chrome 15
- Opera 11.60 beta
- Safari 5.1.1
- Internet Explorer 9
Now about the sad. For IE 9 there is no implementation of the File API, so I will not consider it (the browser). Well, let's go.
Appearance
Since time immemorial, there has been the task of making a stylish button for calling the file selection dialog. Therefore, violent crutches were used. For example, a popular solution is to make the input transparent and hang over a beautiful diva. That is, it all depends on the input, on its size. All of the above browsers support a different solution. You can programmatically generate inputa click in them. And in essence, call the file selection dialog. And the input itself can be easily hidden:
<input id="input_file" type="file" multiple style="position:absolute; top:-999px; visibility:hidden"/> <div id="button" style="background-color: blue; width: 100px; height:40px;"></div>
<script type="text/javascript"> var input = document.querySelector("#input_file"); var btn = document.querySelector("#button"); btn.onclick = function () { input.click(); }; </script>
This method allows you to forget about the input as a control, which in my opinion is very convenient.
About downloading a file to the browser
In order to manipulate the file data, for example, resize the image, you need to get this data. To do this, you need a FileReader. To create thumbnails, take a Canvas and load the file data there. This is possible if the data is presented as base64:
')
var files; var reader = new FileReader(); var cv = document.createElement("canvas"); var cvContext = cv.getContext("2d"); input.onchange = function () { files = input.files; reader.readAsDataURL(files[0]); }; reader.onload = function (e) { var im = new Image(); im.onload = function (e) { cv.width = 100; cv.height = 100; cvContext.drawImage(im, 0, 0, 100, 100);
So far everything is quite transparent. However, we must immediately say that downloading data to the browser is not supported in Safari. The most interesting thing is that you can upload a file to the server, but not to the browser. Neither FileReader nor URL is supported. However, for our problem there is one solution, but I would honestly not use it. I'll come back to this later.
About receiving thumbnails and sending to server
So. We have the original image. We have miniatures on canvas. We need to get all this, group and send to the server. What is easier, right? This is where problems arise. At this stage, browsers behave quite differently. Consider solutions for everyone. Of course, from simple to complex.
Firefox
Everything is simple. Canvas has a mozGetAsFile method, the name of which speaks for itself. Firefox also supports FormData. This means that there is a container for our files. XMLHttpRequest will easily send this data to a server where you can pick it up. The upload process can be monitored using upload.onprogress.
var blobData = cv.mozGetAsFile(name, files[0].type); var form = new FormData(); form.append("Filedata0", files[0]); form.append("Filedata1", blobData); var xhr = new XMLHttpRequest(); xhr.open("POST", "load.php", true); xhr.onload = function () { console.log(this.response); } xhr.upload.onprogress = function (e) { console.log(e.position / e.totalSize) * 100; } xhr.send(form);
There is only one minus. The mozGetAsFile method does not allow to select the quality of the paged image.
Chrome
Here, there is no mozGetAsFile at all. It is possible to get the image in base64 (This makes the toDataURL method). But it did not suit me, and I still led the image to blob. Comments in the code:
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
These data can already be written in FormData and sent in the same way as in firefox.
Opera
Here we have big problems. You can get a thumbnail and turn it into an ArrayBuffer just like in Chrome, but how to send it? Opera does not support FormData and BlobBuilder. And XMLHttpRequest can send only ArrayBuffer except text. Here we will help the experience of creating a bootloader on a flash. We will have to generate the form header with the data ourselves, write it to the ArrayBuffer and send it.
var sBase64 = canva.toDataURL(type, 1); var aBase64 = sBase64.split(','); var sData = atob(aBase64[1]); var aBufferView = new Uint8Array(sData.length); for (var i = 0; i < aBufferView.length; i++) { aBufferView[i] = sData.charCodeAt(i); } var fBuilder = new FormBuilder(); fBuilder.addFile(aBufferView); var form = fBuilder.getForm(); var xhr = new XMLHttpRequest(); xhr.open("POST", "load.php", true); xhr.onload = function () { alert(this.response); } xhr.setRequestHeader('Content-type', 'multipart/form-data; boundary=' + fBuilder.BOUND); xhr.send(form); function FormBuilder() { this.getBoundary = function () { var _boundary = ""; for (var i = 0; i < 0x20; i++) { _boundary += String.fromCharCode(97 + Math.random() * 25); } return _boundary; } this.addFile = function (name, buffer) { var sHeader = this.ADDB + this.BOUND; sHeader += this.ENTER; sHeader += 'Content-Disposition: form-data; name="Filedata' + this.index + '"; filename="' + name + '"'; sHeader += this.ENTER; sHeader += 'Content-Type: application/octet-stream'; sHeader += this.ENTER; sHeader += this.ENTER; this.index++; this.header = this.sumBuffers(this.header, this.StrToBuffer(sHeader), buffer, this.EnterBuffer); } this.addParam = function (name, value) { var sHeader = this.ADDB + this.BOUND; sHeader += this.ENTER; sHeader += 'Content-Disposition: form-data; name="'+ name + '"'; sHeader += this.ENTER; sHeader += this.ENTER; sHeader += value; sHeader += this.ENTER; this.header = this.sumBuffers(this.header, this.StrToBuffer(sHeader)); } this.getForm = function () { var sHeader = this.ENTER; sHeader += this.ENTER; sHeader += (this.ADDB + this.BOUND + this.ADDB); var aHeader = this.StrToBuffer(sHeader); return this.sumBuffers(this.header, aHeader).buffer; } this.StrToBuffer = function (str) { var buffer = new Uint8Array(str.length); for (var i = 0; i < buffer.length; i++) { buffer[i] = str.charCodeAt(i); } return buffer; } this.sumBuffers = function () { var sumLength = 0, position = 0, aSumHeader; for (var i = 0; i < arguments.length; i++) { sumLength += arguments[i].length; } aSumHeader = new Uint8Array(sumLength); for (var i = 0; i < arguments.length; i++) { aSumHeader.set(arguments[i], position); position += arguments[i].length; } return aSumHeader; } this.BOUND = this.getBoundary(); this.ENTER = "\r\n"; this.EnterBuffer = this.StrToBuffer(this.ENTER); this.ADDB = "--"; this.index = 0; this.header = new Uint8Array(0); }
This is such a free translation from actionscript to javascript of my class from the first article. With it, we essentially emulate FormData. By the way, in Chrome, it works great. But firefox swears - he does not know how to transmit ArrayBuffer.
Let's return to the Opera. Everything works, but it will not work to track the download: onprogress in Opera is not supported (as by the way, in the URLLoader Fleshevsky).
Safari
I have already said above that in Safari we do not have access to the file data, and therefore practically nothing can be done. However, if you decided to definitely make a functional image loader on html5 and with Safari support, then there is a pseudo-solution. The fact is that although there is no access to the file data, you can upload it to the server. And you can do anything on the server. The idea is simple: having received and saved the file, transfer it back (in the form of base64 or just links with the subsequent loading into Canvas). And here you can try to implement one of the above options. Naturally, the method is not good, but if absolutely necessary, then you can do so.
Conclusion
The conclusions from the above are pretty simple. First, the File API is clearly not mature yet. Browsers are trying to somehow maintain what is in the specification, but the standard is still at the stage of discussion and refinement. Despite this, we still have a fairly powerful tool that allows us to solve problems not only on paper.
I hope the article will help someone.
Example
I cite a small
demo , the functionality there is minimal, but demonstrates how this should work.
Yes, and more. The minimum server code for this example is:
foreach($_FILES as $key => $value){ $filename = substr_replace($key, '.', -4, 1); move_uploaded_file($value['tmp_name'], $filename); } echo 'complete';
If you need a full-fledged loader with all the options and features, then write. Maybe I will.
And, of course, do not forget that the version of Opera for example 11.60 beta.