preload image with ajax - javascript

Found this technique of using ajax to preload things at: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
window.onload = function() {
setTimeout(function() {
// XHR to request a JS and a CSS
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.js');
xhr.send('');
xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.css');
xhr.send('');
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
I noticed that the 'ajax' preloading for this image isn't really ajax at all. It is the same as what I have been using for years already, just setting the url in a new image object's source and letting the browser load it into the cache.
Now imagine that there was an application where, I needed to actually cancel the preloading of the image if it took over a certain amount of time. There really is no good way to do this with just setting the image to src, unlike the xhr.abort() method which stops the loading of an actual xhr request.
Is there any reason that doing some thing like the below wouldn't preload the image just as well and allow the cancellation of the preload request?
function preload(url, timeout){
this.canceltimeout = function(){
clearTimeout(this.timeout);
this.loaded = true;
return false;
}
this.abort = function(){
this.xhr.abort();
this.aborted = true;
}
//creates a closure to bind the functions to the right execution scope
this.$_bind = function(method){
var obj = this;
return function (e){ obj[method](e);};
}
//set a default of 10 second timeout
if(timeout == null){
timeout = 10000;
}
this.aborted = false;
this.loaded = false;
this.xhr = new XMLHttpRequest();
this.xhr.onreadystatechange = this.$_bind('canceltimeout');
this.xhr.open('GET', url);
this.xhr.send('');
this.timeout = setTimeout(this.$_bind('abort'), timeout);
}
var llama = new preload('/image.gif');
show_image();
function show_image(){
if(llama.loaded){
var l = new Image();
l.src = '/image.gif';
application.appendChild(l);
}else if(llama.aborted){
var l = document.createElement('p');
l.innerHTML = 'image.gif got cancelled';
application.appendChild(l);
}else{
setTimeout(show_image, 10);
}
return false;
}

The main drawback is that unless you have configured your webserver to provide future freshness info(an Expires, or Cache-control: max-age http header that is in the future), the web browser may make a second http request to the server when you set the image.src, or just plain actually use the image in the document. If your web server had sent freshness validation headers(last-modified, or e-tag) then the image won't be redownloaded, but the request asking the server for freshness validation will stil be made, which is wasteful and adds latency to the process.
I don't know why, but browsers really like to cache the images when you hold a reference to an Image() object.
If you watch the net panel in a web browsers debug tools, you'll most browsers make the first 2 requests, but not the 3rd. Commenting out the code in the ajax callback, ands you'll see the request made for #3
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script>
<script>
var f = "0.png?" + (new Date); // 0.png should not have future freshness info headers
$.get(f, function() {
var i = new Image();
i.src = f;
});
setTimeout(function(){
//#3
var i = new Image();
i.src = f;
}, 5000);
</script>

Related

Migrate FileReader ReadAsBinaryString() to ReadAsArrayBuffer() or ReadAsText()

I realize that the new Mozilla Firefox return allocation size overflow (on FileReader.ReadAsBinaryString()) when the file bigger than 200MB (something like that).
Here's some of my code on test for client web browser:
function upload(fileInputId, fileIndex)
{
var file = document.getElementById(fileInputId).files[fileIndex];
var blob;
var reader = new FileReader();
reader.readAsBinaryString(file);
reader.onloadend = function(evt)
{
xhr = new XMLHttpRequest();
xhr.open("POST", "upload.php", true);
XMLHttpRequest.prototype.mySendAsBinary = function(text){
var data = new ArrayBuffer(text.length);
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
if(typeof window.Blob == "function")
{
blob = new Blob([data]);
}else{
var bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder)();
bb.append(data);
blob = bb.getBlob();
}
this.send(blob);
}
var eventSource = xhr.upload || xhr;
eventSource.addEventListener("progress", function(e) {
var position = e.position || e.loaded;
var total = e.totalSize || e.total;
var percentage = Math.round((position/total)*100);
});
xhr.onreadystatechange = function()
{
if(xhr.readyState == 4)
{
if(xhr.status == 200)
{
console.log("Done");
}else{
console.log("Fail");
}
}
};
xhr.mySendAsBinary(evt.target.result);
};
}
So I tried change it to FileReader.ReadAsArrayBuffer(), the error has not shown up but the data are not the same (as it's not read as binary string).
Did anyone has any solution to solve this problem? Is there any way that we can upload bigger file from JS to Web Server in raw/string other than FileReader implementation?
I read on Mozilla JS Documentation that said:
This feature is non-standard and is not on a standards track. Do not
use it on production sites facing the Web: it will not work for every
user. There may also be large incompatibilities between
implementations and the behavior may change in the future. - Mozilla
If not ReadAsBinaryString, the how to implement ReadAsArrayBuffer or ReadAsText
To send Files to a web-server, you simply don't need js. HTML alone is well able to do this with the <form> element.
Now if you want to go through js, for e.g catch the different ProgressEvents, then you can send directly your File, no need to read it whatsoever on your side.
To do this, you've got two (or three) solutions.
If your server is able to handle PUT requests, you can simply xhr.send(file);.
Otherwise, you'd have to go through a FormData.
// if you really want to go the XHR way
document.forms[0].onsubmit = function handleSubmit(evt) {
if(!window.FormData) { // old browser use the <form>
return;
}
// now we handle the submit through js
evt.preventDefault();
var fD = new FormData(this);
var xhr = new XMLHttpRequest();
xhr.onprogress = function handleProgress(evt){};
xhr.onload = function handleLoad(evt){};
xhr.onerror = function handleError(evt){};
xhr.open(this.method, this.action);
// xhr.send(fD); // won't work in StackSnippet
log(fD, this.method, this.action); // so we just log its content
};
function log(formData, method, action) {
console.log('would have sent');
for(let [key, val] of formData.entries())
console.log(key, val);
console.log('through', method);
console.log('to', action);
}
<!-- this in itself is enough -->
<form method="POST" action="your_server.page">
<input type="file" name="file_upload">
<input type="submit">
</form>
Now, you sent a comment saying that you can't upload Files bigger than 1GB to your server.
This limitation is only due to your server's config, so the best if you want to accept such big files is to configure it correctly.
But if you really want to send your File by chunks, even then don't get off of the Blob interface.
Indeed Blobs have a slice() method, so use it.
document.forms[0].onsubmit = function handleSubmit(evt) {
evt.preventDefault();
var file = this.elements[0].files[0];
var processed = 0;
if(file) {
// var MAX_CHUNK_SIZE = Math.min(file.size, server_max_size);
// for demo we just split in 10 chunks
var MAX_CHUNK_SIZE = file.size > 10 ? (file.size / 10) | 0 : 1;
loadChunk(0);
}
function loadChunk(start) {
var fD = new FormData();
var sliced = file.slice(start, start+MAX_CHUNK_SIZE);
processed += sliced.size; // only for demo
fD.append('file_upload', sliced, file.name);
fD.append('starting_index', start);
if(start + MAX_CHUNK_SIZE >= file.size) {
fD.append('last_chunk', true);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', 'your_server.page');
xhr.onload = function onchunkposted(evt) {
if(start + MAX_CHUNK_SIZE >= file.size) {
console.log('All done. Original file size: %s, total of chunks sizes %s', file.size, processed);
return;
}
loadChunk(start + MAX_CHUNK_SIZE);
};
// xhr.send(fD);
log(fD);
setTimeout(xhr.onload, 200); // fake XHR onload
}
};
function log(formData, method, action) {
console.log('would have sent');
for(let [key, val] of formData.entries())
console.log(key, val);
}
<form method="POST" action="your_server.page">
<input type="file" name="file_upload">
<input type="submit">
</form>
But you absolutely don't need to go through a FileReader for this operation.
Actually the only case where it could make sense to use a FileReader here would be for some Android browsers that don't support passing Blob into a FormData, even though they don't give a single clue about it.
So in this case, you'd have to set up your server to let you know the request was empty, and then only read the File as a dataURI that you would send in-place of the original File.
after a long week of research and sleepless nights, you can't upload binary strings without breaking it, also base64 doesn't work for all files, only images, the journey from the client-side to the server breaks the bytes being sent
Kaiido statement is correct
To send Files to a web-server, you simply don't need js
But that doesn't answer my question. Using the Simple XMLHttpRequest() can upload the file and track those progress as well. But still, it's not it. The direct upload, either from the <form> or using XMLHttpRequest() will need to increase your upload limit in php setting. This method is not convenience for me. How if the client upload file as 4GB? So I need to increase to 4GB. Then next time, client upload file as 6GB, then I have to increase to 6GB.
Using the slice() method is make sense for bigger file as we can send it part by part to server. But this time I am not using it yet.
Here's some of my test the worked as I want. I hope some expert could correct me if I am wrong.
My Upload.js
function upload(fileInputId, fileIndex)
{
var file = document.getElementById(fileInputId).files[fileIndex];
var blob;
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = function(evt)
{
xhr = new XMLHttpRequest();
xhr.open("POST", "upload.php?name=" + base64_encode(file.name), true);
XMLHttpRequest.prototype.mySendAsBinary = function(text){
var ui8a = new Uint8Array(new Int8Array(text));
if(typeof window.Blob == "function")
{
blob = new Blob([ui8a]);
}else{
var bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder)();
bb.append(ui8a);
blob = bb.getBlob();
}
this.send(blob);
}
var eventSource = xhr.upload || xhr;
eventSource.addEventListener("progress", function(e) {
var position = e.position || e.loaded;
var total = e.totalSize || e.total;
var percentage = Math.round((position/total)*100);
console.log(percentage);
});
xhr.onreadystatechange = function()
{
if(xhr.readyState == 4)
{
if(xhr.status == 200)
{
console.log("Done");
}else{
console.log("Fail");
}
}
};
xhr.mySendAsBinary(evt.target.result);
};
}
Below is how the PHP server listen to the ArrayBuffer from JS
if(isset($_GET["name"])){
$name = base64_decode($_GET["name"]);
$loc = $name;
$inputHandler = fopen('php://input', "r");
$fileHandler = fopen($loc, "w+");
while(true) {
//$buffer = fgets($inputHandler, 1024);
$buffer = fread($inputHandler, 1000000);
if (strlen($buffer) == 0) {
fclose($inputHandler);
fclose($fileHandler);
return true;
}
//$b = base64_encode($buffer);
fwrite($fileHandler, $buffer);
}
}
The above method works well. The FileReader read the file as ArrayBuffer the upload to server. For me, migrating from ReadAsBinaryString() to ReadAsArrayBuffer() is important and ReadAsArrayBuffer() has some better performance rather than ReadAsBinaryString()
Here's some reason, why some developer relies to FileReader API:
Streaming. Using this method, the file will be stream, so we can avoid setting the php multiple time.
Easy Encrypt. As the file is sending via ArrayBuffer, it is easy for developer to Encrypt the file while upload in progress.
This method also support upload any type of file. I ve done some test and I realize that ReadAsArrayBuffer() method are more faster than ReadAsBinaryString() and direct form upload. You may try it.
Security Notice
The above code is only under test code, to use it in production, you have to consider sending the data in GET or POST under HTTPS.

Add progress bar to lightbox2

I was searching a way to add a progress indicator to lightbox2 script. My JS is pretty poor and I need a hint on where to start.
I assume I need to rewrite Image class prototype, to add methods like onprogress. This is well described here
But when I add those methods at the start of the script, they don't operate at all. I tried inserting console.log() to one of them, nothing logged, they just don't execute.
See comments in code below.
What exactly am I doing wrong, please?
//start of the original lightbox.js
//this is the code I've inserted, you can see I've added
//multiple console.log()s here just to check if methods called
Image.prototype.load = function(url){
console.log('1');
var thisImg = this;
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url,true);
xmlHTTP.responseType = 'arraybuffer';
xmlHTTP.onload = function(e) {
var blob = new Blob([this.response]);
thisImg.src = window.URL.createObjectURL(blob);
console.log('2');
};
xmlHTTP.onprogress = function(e) {
thisImg.completedPercentage = parseInt((e.loaded / e.total) * 100);
console.log('3');
};
xmlHTTP.onloadstart = function() {
thisImg.completedPercentage = 0;
console.log('4');
};
xmlHTTP.send();
console.log('5');
};
Image.prototype.completedPercentage = 0;
//original script continues from here
....
//here imgPreloader declared, I assume it inherits methods from
//rewritten Image's prototype above
var imgPreloader = new Image();
imgPreloader.onload = (function(){
this.lightboxImage.src = this.imageArray[this.activeImage][0];
this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
}).bind(this);
//preloader's src changes and his methods should execute here
//but they don't
imgPreloader.src = this.imageArray[this.activeImage][0];

XMLHttpRequest on load in saveback to calling object

I'm trying to load in many json files for a HTML5 game that will serve as sprite sheets. Previously I've did this synchronously but my new goal is to do this asynchronously.
I have run into a problem though where I'm trying to saving back to the calling object. This is so the information loaded can be used later and so a flag (loaded) can be set so the system knows when a resource has been loaded. Below is my XMLHttpRequest code. I have substituted "spritesheet" for what ever the call should be to save back to the parent.
function SpriteSheet(filename)
{
var tmpFileName = "json/" + filename;
this.loaded = false;
var xhr = new XMLHttpRequest();
xhr.open("GET",tmpFileName,true);
xhr.onload = function(event){
var parsed = JSON.parse(this.responseText);
"spritesheet".img=new Image();
"spritesheet".img.src = "imgs/" + parsed["imgLoc"];
"spritesheet".animations = parsed["animations"];
"spritesheet".sprites = parsed["sprites"];
"spritesheet".loaded = true;
};
xhr.send();
}
Can somebody inform me how I can save back to the the parent or if this is completely the wrong approach can they point me in the direction of a solution.
I found that by creating a var in the 'class' that is a reference to the object and using it in the onload function works, for example:
function SpriteSheet(filename)
{
var tmpFileName = "json/" + filename;
this.loaded = false;
var caller = this;
var xhr = new XMLHttpRequest();
xhr.open("GET",tmpFileName,true);
xhr.onload = function(event){
var parsed = JSON.parse(this.responseText);
caller.img=new Image();
caller.img.src = "imgs/" + parsed["imgLoc"];
caller.animations = parsed["animations"];
caller.sprites = parsed["sprites"];
caller.loaded = true;
};
xhr.send();
}

How do I make an image load synchronously?

I want to create an object that has an image property, but I want the contstructor to finish running only once the image is loaded. Or to describe this with code:
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.src = 'path/to/file.png';
while(true)
{
this.graphic.onload = function(){break;};
//I know this won't work since the 'break' is on a different context
//but you got what I try to do.
}
}
})
For those who are unfamiliar with the Class notation I'm using in my script, it's based on this
Any ideas?
It is possible, but only with the ancient art of Base64 and Data-URL.
GIF image converted to Base64.
rune.b64
R0lGODlhIwAjAIAAAP///wAAACwAAAAAIwAjAAACf4SPqcsb3R40ocpJK7YaA35FnPdZGxg647kyqId2SQzHqdlCdgdmqcvbHXKi4AthYiGPvp9KVuoNocWLMOpUtHaS5CS54mZntiWNRWymn14tU7c2t6ukOJlKR5OiNTzQ7wb41LdnJ1coeNg3pojGqFZniPU4lTi0d4mpucmpUAAAOw==
JavaScript which loads the converted image form the same server via blocking AJAX.
loader.js
var request = new XMLHttpRequest();
var image = document.createElement('img');
request.open('GET', 'rune.b64', false);
request.send(null);
if (request.status === 200) {
image.src= 'data:image/gif;base64,' + request.responseText.trim();
document.getElementsByTagName("body")[0].appendChild(image);
}
Problems
Some older browsers don't like (big) Data-URLs
Base64 encoding makes images about 37% bigger
The whole UI is blocked till the image is loaded
This is a very-evil way
There is a non-evil way to load images in Javascript synchronously.
loadImage = async img => {
return new Promise((resolve, reject) => {
img.onload = async () => {
console.log("Image Loaded");
resolve(true);
};
});
};
Call it with await anywhere. like this
for(let i=0;i<photos.length;i++){
await loadImage(photos[i]);
}
It will load all images one by one.
Note: Calling function must be async to use await
Put the dependent code in the callback. There is no other non-evil way.
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.onload = function ()
{
// the rest of the ctor code here
};
this.graphic.src = 'path/to/file.png';
}
});
var timeOut = 5*1000; //ms - waiting for max 5s to laoad
var start = new Date().getTime();
while(1)
if(img.complete || img.naturalWidth || new Date().getTime()-start>timeOut)
break;
Based on this answer.
I wrapped it into function, and it worked!
for (var i = 0, i < asset.length; i++) {
var img = new Image();
img.src = "file:///" + folder + "/" + asset[i].name;
getWdrp(img);
function getWdrp (img) {
img.onload = function(){
// work with the image file
}
}
}
This is an example that worked for me, because before, when I was processing the image without wrapping in the function, it would work async, now it is async.

Browser crashes after 10-15 mins

In my app I'm displaying 10 charts (charts are from dygraphs.) to monitor data. For displaying charts I'm getting data from my sever by sending ajax request to 4 servlets on every 5 seconds. After 10-15 mins (don't know exact time.) my browser crashes saying "aw!! snap." What could be the reason? Is it javascript that is causing it? or is it because I'm sending request every 5 seconds?
Browser tested: Firefox and Chorme.
Note:- When I refresh the browser after crash it again works fine for 10-15 mins.
JS code:
var i=0;
var loc = new String();
var conn = new String();
var heapUsage = new String();
var cpuUsage = new String();
var thrdCnt = new String();
var heapUsageConsole = new String();
var cpuUsageConsole = new String();
var thrdCntConsole = new String();
var user = new String();
var MemTotal = new String();
function jubking(){
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var url = "MonitorDBServlet";
xmlhttp.open("POST", url, false);
xmlhttp.send(null);
var str = xmlhttp.responseText;
var strArr = str.split(",");
url = "MonitorTomcatServlet";
xmlhttp.open("POST", url, false);
xmlhttp.send(null);
var appstr = xmlhttp.responseText;
var appArr = appstr.split(",");
url = "MonitorConsoleTomcatServlet";
xmlhttp.open("POST", url, false);
xmlhttp.send(null);
var appstrConsole = xmlhttp.responseText;
var appArrConsole = appstrConsole.split(",");
url = "CpuMemoryServlet";
xmlhttp.open("POST", url, false);
xmlhttp.send(null);
var statesStr = xmlhttp.responseText;
var states = statesStr.split(",");
if(i>30){
loc = loc.substring(loc.indexOf("\n")+1);
loc += i+","+strArr[0]+","+strArr[1]+"\n";
//--- Do same thing all other var
} else {
loc += i+","+strArr[0]+","+strArr[1]+"\n";
//--- Do same thing all other var
}
document.getElementById("dbSize").innerHTML = strArr[3];
document.getElementById("HeapMemoryUsageMax").innerHTML = appArr[1];
document.getElementById("HeapMemoryUsageMaxConsole").innerHTML = appArrConsole[1];
g = new Dygraph(document.getElementById("dbLocks"),
",locksheld,lockswait\n"+loc+"");
g = new Dygraph(document.getElementById("activeConnection"),
",Connections\n"+conn+"");
g = new Dygraph(document.getElementById("example2"),
",heapUsage\n"+heapUsage+"");
g = new Dygraph(document.getElementById("example3"),
",cpuUsage\n"+cpuUsage+"");
g = new Dygraph(document.getElementById("example4"),
",thread,peakThread\n"+thrdCnt+"");
g = new Dygraph(document.getElementById("example6"),
",heapUsage\n"+heapUsageConsole+"");
g = new Dygraph(document.getElementById("example7"),
",\n"+cpuUsageConsole+"");
g = new Dygraph(document.getElementById("example8"),
",thread,peakThread\n"+thrdCntConsole+"");
g = new Dygraph(document.getElementById("cpuStates"),
",user,system,nice,idle\n"+user+"");
g = new Dygraph(document.getElementById("memStates"),
",MT,MF,B,C,ST,SF\n"+MemTotal+"");
i = i + 1;
setTimeout("jubking()", 5000);
}
You can use about:crashes in FF to view the specific reason for your crash. As mentioned by others, you could be leaking memory if you're caching off data (assigning it to a variable) returned by your AJAX call and not clearing it when the next call is made.
Edit:
Just saw your comment - 1,923,481 K is definitely too much - you're leaking data somewhere. What OS are you running? If you run FF from console in *nix, you usually get some form of a dump into console when something's going wrong (not sure about Windows).
You could possibly try decreasing your poll intervals to once every few seconds and step through the script using Firebug or Chrome's debugger to see what's happening. Worst case, start commenting things out until you figure out exactly what is making your app crash. And then, figure out a way to fix it :)
I suspect that your dygraphs usage is, as you note in your comments, the source of your trouble. It looks like you're binding new graphs over and over again when you only want to update the data, using a moving window for the data would also help. Try reworking your updater to work like this pseudo-JavaScript:
var graphs = {
dbLocks: {
graph: new DyGraph(/* ... */),
data: [ ]
},
activeConnection: {
graph: new DyGraph(/* ... */),
data: [ ]
},
// etc.
};
var DATA_WINDOW_SIZE = 1000; // Or whatever works for you.
function update(which, new_data) {
var g = graphs[which];
g.data.push(new_data);
if(g.data.length > DATA_WINDOW_SIZE)
g.data.shift();
g.graph.updateOptions({ file: g.data });
}
function jubking() {
// Launch all your AJAX calls and bind a callback to each
// one. The success callback would call the update() function
// above to update the graph and manage the data window.
// Wait for all the above asynchronous AJAX calls to finish and
// then restart the timer for the next round.
setTimeout(jubking, 5000);
}
The basic idea is to use window on your data with a reasonable maximum width so that the data doesn't grow to chew up all your memory. As you add a new data point at the end of your data cache, you drop old ones off the other end once you hit your maximum comfortable size.
You can find some techniques for waiting for several asynchronous AJAX calls to finish over here: How to confirm when more than one AJAX call has completed? (disclosure: yes, that's one of my other answers).
The answer above advocates re-using your Dygraph object and calling g.updateOptions({file:...}) to reduce memory usage. This is a great way to do it.
The other way is to call g.destroy() before you redefine the Dygraph object. This will make dygraphs clear out all of its internal arrays and DOM references. Example:
g = new Dygraph(...);
g.destroy();
g = new Dygraph(...);
Read more here: http://blog.dygraphs.com/2012/01/preventing-dygraphs-memory-leaks.html

Categories

Resources