-
Notifications
You must be signed in to change notification settings - Fork 0
Spec
Preload images with an option to cancel the transfer if needed without stoping the window loading.
Fetching images over the network is both slow and expensive. Browsers has a limit of how many connections per hostname can be established simultaneously. This configuration number may change anytime though.
Browser | Connections per Hostname | Max Connections |
---|---|---|
IE 9 | 6 | 35 |
IE 10 | 8 | 17 |
IE 11 | 13 | 17 |
Chrome 32 | 6 | 10 |
Chrome 34 | 6 | 10 |
Firefox 26 | 6 | 17 |
Firefox 27 | 6 | 17 |
Safari 7.0.1 | 6 | 17 |
Android 2.3 | 8 | 10 |
Android 4 | 6 | 17 |
Blackberry 7 | 7 | 9 |
Chrome Mobile 18 | 6 | 16 |
IEMobile 9 | 6 | 60 |
Source: May 28, 2015
http://www.browserscope.org/?category=network
Average of Connections per Hostname : 6.923076923076923
connections = [6,8,13,6,6,6,6,6,8,6,7,6,6]
sum = connections.reduce(function(c, n) {return c + n})
sum / connections.length
=> 6.923076923076923
Knowing the average number of parallel connections allowed by browsers (I will round it down to 6) it is worth knowing that if we rebase that number (e.g. say we are trying to download 20 images at the same time) we will get no warnings, errors or any other type of message regarding this issue, simply because the browser will handle the pool itself, so it's not big deal, the problem came when we are still downloading images that we no longer need, keeping useless connections open when we can be giving a better use to those connection to download more content the user may need right away.
TCP connection scheduler pseudo-code of a popular browser.
if (num_conn_open < max_conn) {
if (idle_conn && !conn_expired()) {
reuse_conn();
}
elseif {
open_new_conn();
close_expired_conn();
}
}
elseif {
open_new_conn();
close_oldest_idle_conn();
}
Source: Web Content Delivery Book
So, if delivering content quickly is important for your program, you may want to benefit for closing useless connections.
We can benefit for canceling image downloads if we no longer need those resources to be fetched, so we can have less memory being consumed and more available connections to fetch more data if needed.
Users will have to wait less time for new images to be displayed if they request new dynamic content and all the available connections per hostname were already in use by downloading images that the user no longer need.
- Extract all urls from the image tags and start the image loading only when the user scrolls to a specific one.
- Long page that dynamically loads images as users scroll through.
- Downloading multiple images to display a dynamic image gallery, the user closes the gallery before all images got transferred and opens a new gallery resulting on requesting more new images to be fetch.
- On Single Page Applications (SPA), downloading the images for a specific page, then the user request to view a new different page.
Need to cancel image transfers to have more parallels downloads available.
-
network.http.max-connections
andnetwork.http.max-connections-per-server
limit is different per browser. - Do not interrupt the current window loading
window.stop()
. - Browser support: Google Chrome, Firefox, Safari, IE9+
Creating image objects and relying on the onload
and onerror
handlers we
can benefit if the image is already in cache, it won't send another request to
the server.
Good:
- It allow us to cancel the image loading by removing the
src
attribute or by changing thesrc
attribute - Async fetching
- No limited by the same origin policy
Bad: Safari does not stop previous connections by changing the src
attribute.
We can use window.stop
to stop window loading.
Bad: This will stop large images, new windows, and other objects whose loading is deferred.
Creating an iframe with an about:blank
source and append the image on the
iframe to be loaded.
Good:
- Calling
iframe.contentWindow.stop()
will successfully stop the image download. - IE does not recognize the
stop
method, but it has its owndocument.execCommand("Stop");
which produce the same results. - Every browser uses Browser Cache by default for (images, JavaScript, CSS), unless you change the image's HTTP Headers. more info on HTTP caching. That means for us that downloading the image inside an iframe and then appending in on the current window will not cause a new requests but will be loaded from cache.
Bad:
- Require an extra iframe element to be created per instance
- Appending the image on the main window will cause a new download (if cache is disabled)
Good:
-
XMLHttpRequest
can be aborted - Async fetching
Bad:
- Limite by the same origin policy
- Will produce a new connection per every request (even if the resource was fetched before)
- Provide a way to cancel data transfer for registered images while they are not yet fully fetched.
- Provide a way to get notified when a registered image gets downloaded or an error happened while downloading it.
If we have a way to cancel image downloads then we can have more connections per hostname available to made new requests right away if needed.
- Browsers caches images requests, so if we try to fetch an already loaded image it will serve it from the cache and will not perform a new request.
Create new image objects and listen the onload
and onerror
handlers, using
this approach give us the benefit that if the image is already in cache, it
won't send another request to the server.
Use a blank data:uri string to avoid removing the src
attribute, having an
empty src
seems to be problematic
Empty Image src can Destroy your Site - @slicknet - November 30, 2009,
so instead of removing the src
attribute or emptying it, we replace it with a
small data:uri string, which will cause no extra HTTP request. The downside is
that this will fire the image load
event, which in this case is not what we
want, so before replacing the src
we unbind the image event listeners.
Changing the src
to a data:uri string string will cancel the previous image
download on the following browsers: Chrome, Firefox, Opera and IE.
Safari does not honor this behavior, so for Safari specifically we will use a
different approach. Creating an iframe and loading the image inside the iframe
will allow us to call window.stop
on the iframe content to successfully close
the connection.
This can be used for the blank data:uri Base64 Encode of 1x1px Transparent GIF
- Need to load new images to display on the screen.
- A new instance per image is created and we set a callback to be run when the image successfully loads or an error occurs.
- If the callback is run we check if it was an error and handle it.
- If the callback is run and is not an error we display the image.
- If new content is requested and we no longer need to wait for these images
to be loaded, we
abort
all the unfinished instances.
We need a public method that we call for every instance created. This method will be in charge of canceling the registered image download.
I found out that on Chrome, Firefox, Opera and even IE changing the src
attribute to a small data:uri for the in-memory object image will stop the
download. However Safari will keep the transfer until the download is fulfill.
Changing the src
attribute is the most simple solution, so we can use that
approach by default for any browser and implement a different approach for Safari.
For Safari, creating an iframe, appending the image to the iframe and calling
contentWindow.stop()
on the iframe will do the trick.
To start listening for an instance to load or to fail we should call the load
method. This will bind the unload
and onerror
events and update the image
source.
Because the simple approach of changing the src
attribute from the in-memory
image object will work for most browsers, I decided to use a strategy of module
inclusion. This way we have a default behavior shared by those browsers
and a different object that will be included just in case the browser
detected is Safari, which will have a different approach. We can use the
navigator.userAgent
string to determine which behavior to include.
if ( /^((?!chrome).)*safari/i.test(window.navigator.userAgent) )
this._implementation = window.MainClassSafari
else
this._implementation = window.MainClassDefault
This will determine which behavior our main class should include.
MainClass.discoverImplementation()
include( MainClass._implementation )
The include
function there is charge of merging the properties from the passed
Object to the prototype
of our MainClass.
var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
// so we do not get notified for the load of the data:uri
unbindImageEvents();
// Default approach
image.setAttribute('src', BLANK);
// Approach for Safari
iframe.contentWindow.stop();
// Default approach
image.setAttribute('src', passedImageSource);
// Safari approach
iframe = createElement('iframe')
iframe.src = 'about:blank'
iframe display none
body append iframe
iframe append image
image.setAttribute('src', passedImageSource);
Being able to halt image fetching over the network allow us to have more available connections to fetch more data if needed and to use less memory.