Javascript Preloading Images: Add to DOM, or not? - javascript

Is it necessary to add an <img> to the DOM in order to preload it?
$(function whenDOMIsHappy(){
var $img = $('<img />')
.load(loadHandler)
.error(errorHandler)
.attr({src:"squatchordle-squashgarden.jpg"});
$img.appendTo('body .preloads'); // is this at all necessary?
});
// assuming <div class="preloads" style="display:none;"></div> exists in <body>.
I've seen mixed messages about this technique. I'm using jQuery, but the question applies to vanilla-people too.
I am interested in keeping this working in all major browsers.

All browsers I've tested do load images even if they're not in the DOM. You can test this with https://jsfiddle.net/84tu2s9p/.
const img = new Image();
img.src = "https://picsum.photos/200/300";
img.onload = () => console.log("loaded");
img.onerror = err => console.error(err);
Safari 13, 11.1, 10.1
Edge 18
Firefox 72, 70, 62
Chrome 78, 71
Opera 64
IE11
(Not meant to be an exhaustive list. I just tried a variety of versions and browsers.)
There's also the new image.decode() API that is intended for this use case of preloading images and avoids potential dropped frames when actually decoding the image to render it. Edge doesn't support it yet (Chromium-based Edge will though).
Given that HTML Canvas can use images without them being in the DOM, I think they have to load images.

as opposed to creating and then appending elements to the dom, why not just initialize a new image in javascript, then set its source to your images URL. this method should load your image without actually applying it to an element or rendering it on the dom - YET... take a peek:
someImageArray[0] = new Image();
someImageArray[0].src = "http://placehold.it/700x200/";
from here you are free to do what you wish with that image using javascript - render it directly in canvas or create an element out of it. however you might not even have to do anything. say if its already being referenced in other ajax based content. provided the URL is identical, the browser should use the cached version to draw the dom.
hope this helps here is a reference to a decent article about pre-loading with a few more options...

There is no guarantee that Images will be preloaded if you don't add it to the DOM! If you don't add it, the JavaScript Compiler can aggressively garbage collect the Image before it tries to load, because the element is not used at all.
This currently happens in firefox! There your images will not be preloaded if you don't add them to the DOM! - So be on the safe side and add them.

Related

Once an image has failed loading, how can I make the browser retry?

I'm making a Chrome extension which in order to reduce bandwidth usage it stops all outcoming requests which are images.
I want to provide functionality where if the user clicks on the image (or technically a layer on top of that image) it would try to reload the image, this time not being blocked by the extension.
How can I tell the browser to retry loading the image? And if there isn't a straightforward way to do it, what would be a work around? Deleting the old image from the DOM and adding it again?
Any help is appreciated. :)
EDIT 1:
To answer #CBroe's question:
Using the chrome.webRequest.onBeforeRequest API in a background script.
To answer #jfriend00's question:
The usual placeholder "couldn't load image" icon, I guess also known as "broken file" icon:
See all those broken images?
That screenshot also illustrates the point of a layer on top of another image. Should those images not be broken, the loaded image would be there but that layer (the one in a dark grey which shows the image's dimensions) still remains there.
The desired href still exists there in the img tag:
If simply assigning the same src value to the img element is not enough¹, then create a new Image object in JavaScript, and assign the value to its src property.
¹ It might not be, if the browser just goes, “oh hey, that is the same value for the src attribute that the img already had, so I don’t have to do anything” – creating a new JS Image object however should make the browser request that resource again if he realizes he does not have it cached already.
What I would do instead is replace the URLs of the images with an image from your extension. A 1x1 pixel transparent GIF or PNG.
When you do this, add an attribute to all of the elements you replaced... something like data-yourextension-originalurl, with the URL of the original image. If the user then wants to load images, it's easy enough to go back and fix those image elements.
While I'm not too familiar with the Chrome API, a quick glance seems to suggest that there's no way to get the specific img element from each onBeforeRequest, which you'd need to know in order to figure out where to attach custom code.
This may be better accomplished with native JavaScript of some sort. For example, if Chrome lets you inject code on load, you could apply a function like the one below to all img elements after document load but before image load.
// Given an img element, replaces its src with a placeholder URL,
// and sets its click action to load its original src
function makePlaceholder(elem){
elem["data-oldtitle"] = elem.title;
elem["data-oldhref"] = elem.href;
elem["data-oldsrc"] = elem.src;
elem["data-oldonclick"] = elem.onClick;
elem.title = "Click to load the blocked image.";
elem.href = '';
elem.src = "http://example.com/placeholder.png";
elem.onClick = function(){
this.src = this["data-oldsrc"];
this.title = this["data-oldtitle"];
this.href = this["data-oldhref"];
this.onClick = this["data-oldonclick"];
};
}
The simple way to force reloading an image in JavaScript is:
var img = document.getElementById("myImage");
img.src = img.src.replace(/\?.+/,"") + "?" + new Date().getTime();
This adds a unique QueryString to the image which basically forces the browser to not use a cached version of the image.

IE8 not releasing memory for image elements

I have a JavaScript widget that switches between image frames to create motion,
the widget fetches these frames when its render method is called, and discards reference to them when the dispose method is called.
The implementation works by using PreloadJS (http://www.createjs.com/#!/PreloadJS/documentation) to load a set of frames, it then renders the current frame to a <canvas>, or toggles display:none and display:block on the image elements on legacy browsers.
IE8 is causing issues because it doesn't want to free up the memory from these frames when the dispose method is called. (Note: This only happens if the images are in the DOM, otherwise everything is okay.)
The complete source can be found here: http://pastie.org/8061442
The images are added to the DOM on line 201:
var i = 0, img, node = self.$node[0];
for ( ; i < self.imageSet.length; i += 1 ) {
img = self.queue._loadedResults[self.imageSet[i]]
img.setAttribute("draggable", "false")
img.style.display = "none"
node.appendChild(img)
img = null
}
i = node = null
If this code is removed (and the accompanying disposal code here on :251-259) then the images are disposed correctly, it's only when the elements are added to the DOM that there is a problem.
If the render method is called after the dispose fails, the classic Stack verflow at line: 0 error gets thrown.
Any ideas / suggestions would be very helpful. Please look at the code before suggesting though, all references to the images has been nulled and the instance of the image loader has been disposed also, there are no event listeners on the image elements that would create a circular reference, etc. As far as I can see the images should be freed.

jQuery alternative to bind('load') for use with images [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
jQuery callback on image load (even when the image is cached)
So the .load() method has been deprecated in jQuery. load() is a shortcut for `bind('load', function).
From http://api.jquery.com/load-event/:
Caveats of the load event when used with images
A common challenge developers attempt to solve using the .load()
shortcut is to execute a function when an image (or collection of
images) have completely loaded. There are several known caveats with
this that should be noted. These are:
It doesn't work consistently nor reliably cross-browser It doesn't
fire correctly in WebKit if the image src is set to the same src as
before It doesn't correctly bubble up the DOM tree Can cease to fire
for images that already live in the browser's cache`
How does one perform a function on image load consistently? My Current code looks like so:
$(newImage).bind('load', function() {
createSlices(slide);
});
However after reading the docs (although I haven't noticed any problems) I am worried it may not fire for some users.
Try this plugin man: called waitforimages plugin: https://github.com/alexanderdickson/waitForImages (scroll down the link you will see various advanced use) - demo http://jsfiddle.net/FZ7Xy/
Hope it fits the need :)
Code
$('selector').waitForImages(function() {
// All descendant images have loaded, now slide up.
$(this).slideUp();
});
var img = new Image();
img.onload = function() {
}
img.src= src-image
the above code works on all browsers and even without jquery.
You're right to worry, IE7/8 in particular will sometimes fail to fire the load event if the image is cached.
One technique is to immediately check if the image is complete after binding the load event:
$(newImage).bind('load error', function() {
createSlices(slide);
});
// check if the image is immediately complete (cached)
if (newImage.complete || typeof newImage.complete === 'undefined') {
createSlices(slide);
}
I've tested with success in:
IE 7/8/9/10
FF4+
Chrome (latest)
Safari 5+
There is a great library that handles exactly this: https://github.com/desandro/imagesloaded
I've used it in production and it works wonderfully. Extremely reliable, not a single complaint.

Javascript slowly "flush" DOM Tree

I have really strange problem. I'm trying to get width and height of an image using following way:
Create a image tag and add .src to it.
add this image tag to document.body.
making this image tag visible (display:inline).
getting .offsetWidth and .offsetHeight of element.
hidding image tag(display:none);
all this is done using JS and it all work really well on my localhost, but when I uploaded a site to my client hosting provider I was amazed. offsetWidth and .offsetHeight was 0 in step 4. Why do that happen? I think that I need some kind of "flush" after step 3 before step 4, but I'm not exactly sure. Any suggestions ? Thank you.
You have to wait for the image to actually load over the network. You don't need to add the thing to the DOM either:
var img = new Image();
img.onload = function() {
handleImageSize(this.width, this.height);
};
img.src = "http://whatever.com/your/img.jpg";
Here, "handleImageSize" would be a function you write to do whatever it is you need to do with the image dimensions.
This was probably caused by the image not being loaded yet. Try something like this:
img.onload = function() {
// get the offsetWidth and offsetHeight
}
As other answers suggest, is due to the image not being fully loaded by the DOM. This was working fine on your local server as I suspect you were loading images locally, meaning they were almost instantaneous; however after moving them to the server there is a slight delay. It's an easily overlooked problem, that is easily fixed.
Other answers will do the trick; but here's the jQuery version for sake of argument
var img = $('<img>', {
src: 'http://dummyimage.com/150/000/fff.gif'
}).one('load', function() {
var offsets = $(this).offset();
});
$('body').append(img);
jsFiddle

shift-reload in FF gives unexpected results

I'm presenting a simple animation using img.src replace and the <canvas> tag. At present it's only expected to work in FireFox (FF 3.5.3, Mac OS X 10.5.5), so cross-browser compatibility isn't (yet) an issue.
When the page is first loaded, or loaded into an new window or tab, all seems to work as expected, and the cache behavior on a simple reload does not seem to be an issue; however, if I try to force a reload with shift-reload, I get a problem. Even though the images have been pre-loaded, the preloaded images for the animation don't seem to be available to the browser which then tries to load each new img.src from the server.
Am I looking at a browser bug here, or is there something buggy in my code that I can't see? This is my first shot at implementing a js class, so there might be a lot here that I don't understand.
Any insight from the assembled wise here would be welcome. You can see what I'm talking about at:
http://neolography.com/staging/mrfm/spin-sim.html
Thanks,
Jon
When you shift reload you're telling the browser to reload - not from the cache.
So it shouldn't be a surprise that you're getting the images from the server.
Images can be preloaded in javascript with the following code:
img = new Image();
img.src = "your/image/path";
If you want the images loaded before you use them that might help.
I had a look at your code and you have the following in document.ready()
function countLoadedImages() {
loadedImgs++;
if (loadedImgs == images.length){
$("#loading-image").hide();
$("#controls").fadeIn(100);
}
}
animation = new simAnim("snap", "stripchart", 800, deriveFrameData(spindata));
the animation = new simAnim line is executed regardless if all 100 images are loaded or not...
One possibility to fix this would be to move that line inside the countLoadedImages function like so:
function countLoadedImages() {
loadedImgs++;
if (loadedImgs == images.length){
$("#loading-image").hide();
$("#controls").fadeIn(100);
animation = new simAnim("snap", "stripchart", 800, deriveFrameData(spindata));
}
}
this way that function will be executed once all the images have loaded
Thanks to ekhaled, who tried. I'm now satisfied that this is a browser bug:
Mozilla bug #504184
I have a stripped down example at http://neolography.com/staging/shift-reload/shift-reload-testcase.html which I will leave up. I encourage all to vote for this bug in the mozilla bug tracker so that it will get fixed.
j

Categories

Resources