So basically I want to add few images to zip file and be able to download it. I have two variables holding sources of images - pic1, pic2 and a global variable basePic for holding base64 string. I have a function to convert image to base64 :
var pic; //http://www.somesite.com/i/picture.jpg
var pic2; //http://www.somesite.com/i/picture2.jpg
var basePic;// base64 string of an image
function getBase64FromImageUrl(url) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var canvas = document.createElement("canvas");
canvas.width =this.width;
canvas.height =this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURL = canvas.toDataURL("image/png");
basePic=dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
};
img.src = url; // I was trying to put this after setAttribute, but it's the same effect
}
Later I'm trying to create a zip file and invoke this function like that:
var zip = new JSZip();
getBase64FromImageUrl(pic);
alert("random text"); // without it, it's just empty file added to zip
zip.file("picture.png",basePic, {base64: true});
getBase64FromImageUrl(pic2);
alert("random text");
zip.file("picture2.png",basePic, {base64: true});
zip.generateAsync({type:"blob"})
.then(function(content) {
saveAs(content, "example.zip");
});
This function works with one image, but it get's more random with more than one. If I run my code few times without refreshing the page it may get the result that I need. But most of the time in my zip file is just only second picture twice and I have to alert something after calling this function, because basePic would be empty without. I want to fix this function to be able, to easily convert multiple sources of images to base64 strings.
1.How to make this function work as it should, input_url -> output_base64, with multiple images ?
2.How to do that without the need to alert something after every call of that function ?
The problem is that the zipping process starts before the image(s) has loaded. The process of adding the files to the zip archive must start from inside the onload
handler.
However, there are several things to consider here as well:
- If you want to add images as-is to the archive there is no need to go via canvas
- Converting to and from Data-URIs are very expensive and memory costly. JSZip uses pako_delate internally which works much better with typed array views (
Uint8Array
)
I would propose the following approach instead:
- Create zip object at beginning of app (or before starting loading files)
- Load each file using
XMLHttpRequest
s and request content as ArrayBuffer
. This will allow you to load any kind of file directly to binary format which will increase performance and resources.
- Add files as they come
- When all URLs are passed call the
generateAsync()
method and you should be done.
Example
This is not a production-ready example and isn't intended to be, but it will show the main steps. Adjust to your scenario, add error handling and sanity checks where appropriate.
// create archive before loading process begin
var zip = new JSZip();
// image list to add:
var images = [
"//i.imgur.com/qDFCWVnb.jpg",
"//i.imgur.com/VBvzEq1b.jpg",
"//i.imgur.com/ZWtUEuLb.jpg"
],
index = 0; // for loader
// function to load a single image as arraybuffer using XMLHttpRequests
// it will assume cross-origin usage is allowed
function loadAsArrayBuffer(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.onerror = function() {/* handle errors*/};
xhr.onload = function() {
if (xhr.status === 200) {callback(xhr.response, url)}
else {/* handle errors*/}
};
xhr.send();
}
// loading process. Here it will load one and one image.
// You can modify it to load several at once but some browsers put
// a cap on how many XHR connections can be open at the same time..
(function load() {
if (index < images.length) {
loadAsArrayBuffer(images[index++], function(buffer, url) {
var filename = getFilename(url);
zip.file(filename, buffer); // image has loaded, add it to archive
load(); // load next image
})
}
else { // done! present archive somehow
zip.generateAsync({type:"blob"}).then(function(content) {
// save out
lnk.href = (URL || webkitURL).createObjectURL(content);
lnk.innerHTML = "Right-click + Save as to download zip file";
lnk.download = "DemoZip.zip";
});
}
})();
// Just for this demo! keep separate array with filename or
// modify to allow for "filename-less" uris.
function getFilename(url) {
return url.substr(url.lastIndexOf("/") + 1)
}
<script src="https://raw.githubusercontent.com/Stuk/jszip/master/dist/jszip.min.js"></script>
Loading external lib + images... hold on...
<br><a id=lnk></a>