Fast Image loading methods, low to high res with multiple backgrounds - javascript solution?
//
// You have dozen of hd photos, and don't want to embed them right from the get go
// in order to avoid 'interlaced' load, boost application load, etc.
// Idea is to place lo-res photos, temporarily, in place where hd ones should go,
// while downloading full quality images in the background.
//
// People usualy do this kind of caching by attaching 'onload' event handler to off-screen
// Image object ( created by new Image(), document.createElement('img'), or any
// other fashion ), which gets executed natively by a browser when the event
// ( 'onload' in this case ) occurs, and setting the '.src' property of an image to
// the phisical path ( relative/absolute ) of an img to start the download process.
// The script pressented here use that approach for multiple images,
// and notifies of task done by running provided function.
//
// So, solution is to provide locations of images you want to,
// and get notified when they get fully downloaded, and cached by browser.
// To do that you pass a function as 1st parameter to the fn below,
// passing as many images as needed after it.
//
// Code will scan through provided images keeping the ones that are actualy
// image files( .jpeg, .png, .tiff, etc.), create 'off-screen' Image objects
// and attach onload/onerror/onabort handler fn to each one( which will be called
// when coresponding circumstance occurs ), and initiate loading by setting the
// .src property of an Image object.
//
// After the 'load-handler' has been called the number of times that coresponds to
// number of images ( meaning the dload process is done ), script notifies you
// of job done by running the function you provided as first argument to it,
// additinaly passing images( that are cached and ready to go ) as
// parameters to callback fn you supplied.
//
// Inside the callback you do whatever you do with cached photos.
//
function hd_pics_dload( fn /* ,...hd-s */ ) {
var
n = 0, // this one is used as counter/flag to signal the download end
P = [], // array to hold Image objects
// here goes the image filtering stuff part,
// all the images that pass the 'regex' test
// ( the ones that have valid image extension )
// are considerd valid, and are kept for download
arg_imgs = Array.prototype.filter.call(
Array.prototype.slice.call( arguments, 1 ),
function ( imgstr ) {
return ( /\.(?:jpe?g|jpe|png|gif|bmp|tiff?|tga|iff)$/i ).test( imgstr );
}
);
// aborts script if no images are provided
// runs passed function anyway
if ( arg_imgs.length === 0 ) {
fn.apply( document, arg_imgs );
return arg_imgs;
}
// this part keeps track of number of 'load-handler' calls,
// when 'n' hits the amount of given photos
// provided callback is runned ( signaling load complete )
// and whatever code is inside of it, it is executed.
// it passes images as parameters to callback,
// and sets it's context ( this ) to document object
var hd_imgs_load_handler = function ( e ) {
// logs the progress to the console
console.log( e.type, ' -- > ', this.src );
( ++n === arg_imgs.length )
&& fn.apply( document, arg_imgs );
};
// this part loops through given images,
// populates the P[] with Image objects,
// attaches 'hd_imgs_load_handler' event handler to each one,
// and starts up the download thing( by setting '.src' to coresponding image path )
for (
var i = 0,
len = arg_imgs.length;
i < len;
i++
) {
P[i] = new Image;
P[i].onabort = hd_imgs_load_handler;
P[i].onerror = hd_imgs_load_handler;
P[i].onload = hd_imgs_load_handler;
P[i].src = arg_imgs[i];
}
// it gives back images that are about to be loaded
return arg_imgs;
}
//
// use:
hd_pics_dload(
// 1st provide a function that will handle photos once cached
function () { console.log('done -> ', arguments ); },
// and provide pics, as many as needed, here
// don't forget to separate all parameters with a comma
'http://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Flag_of_the_United_Nations.svg/2000px-Flag_of_the_United_Nations.svg.png',
'http://upload.wikimedia.org/wikipedia/commons/e/e5/IBM_Port-A-Punch.jpg',
'http://upload.wikimedia.org/wikipedia/commons/7/7e/Tim_Berners-Lee_CP_2.jpg',
'http://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/NewTux.svg/2000px-NewTux.svg.png',
'http://upload.wikimedia.org/wikipedia/commons/4/4c/Beekeeper_keeping_bees.jpg',
'http://upload.wikimedia.org/wikipedia/commons/9/9a/100607-F-1234S-004.jpg'
);
//
Load a low-res background image first, then a high-res one
Here's the method I use...
CSS:
#div_whatever {
position: whatever;
background-repeat: no-repeat;
background-position: whatever whatever;
background-image: url(dir/image.jpg);
/* image.jpg is a low-resolution at 30% quality. */
}
#img_highQuality {
display: none;
}
HTML:
<img id="img_highQuality" src="dir/image.png">
<!-- img.png is a full-resolution image. -->
<div id="div_whatever"></div>
JQUERY:
$("#img_highQuality").off().on("load", function() {
$("#div_whatever").css({
"background-image" : "url(dir/image.png)"
});
});
// Side note: I usually define CSS arrays because
// I inevitably want to go back and add another
// property at some point.
What happens:
- A low-res version of the background quickly loads.
- Meanwhile, the higher resolution version is loading as a hidden image.
- When the high-res image is loaded, jQuery swaps the div's low-res image with the high-res version.
PURE JS VERSION
This example would be efficient for changing one to many elements.
CSS:
.hidden {
display: none;
}
#div_whatever {
position: whatever;
background-repeat: no-repeat;
background-position: whatever whatever;
background-image: url(dir/image.jpg);
/* image.jpg is a low-resolution at 30% quality. */
}
HTML:
<div id="div_whatever"></div>
<img id="img_whatever" class="hidden" src="dir/image.png" onload="upgradeImage(this);">
JAVASCRIPT:
function upgradeImage(object) {
var id = object.id;
var target = "div_" + id.substring(4);
document.getElementById(target).style.backgroundImage = "url(" + object.src + ")";
}
UPDATE / ENHANCEMENT (1/31/2017)
This enhancement is inspired by gdbj's excellent point that my solution results in the image path being specified in three locations. Although I didn't use gdbj's addClass() technique, the following jQuery code is modified to extract the image path (rather than it being hardwired into the jQuery code). More importantly, this version allows for multiple low-res to high-res image substitutions.
CSS
.img_highres {
display: none;
}
#div_whatever1 {
width: 100px;
height: 100px;
background-repeat: no-repeat;
background-position: center center;
background-image: url(PATH_TO_LOW_RES_PHOTO_1);
}
#div_whatever2 {
width: 200px;
height: 200px;
background-repeat: no-repeat;
background-position: center center;
background-image: url(PATH_TO_LOW_RES_PHOTO_2);
}
HTML
<div id="div_whatever1"></div>
<img id="img_whatever1" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_1">
<div id="div_whatever2"></div>
<img id="img_whatever2" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_2">
JQUERY
$(function() {
$(".img_highres").off().on("load", function() {
var id = $(this).attr("id");
var highres = $(this).attr("src").toString();
var target = "#div_" + id.substring(4);
$(target).css("background-image", "url(" + highres + ")");
});
});
What's happens:
- Low res images are loaded for each of the divs based on their CSS
background-image settings. (Note that the CSS also sets the div to the intended
dimensions.) - Meanwhile, the higher resolution photos are being
loaded as hidden images (all sharing a class name of img_highres). - A jQuery function is triggered each time an img_highres photo
completes loading. - The jQuery function reads the image src path, and
changes the background image of the corresponding div. In the
example above, the naming convention is "div_[name]" for the visible divs
and "img_[same name]" for the high res images loaded in the
background.
How to change Multiple small-res image source with the actual high-res image after they have been loaded?
As the comment suggests, Progressive image loading like medium can be implemented by using a simple open-source files from GitHub blurry-image-load. Read readme.md and add the necessary files to your project and follow the instruction.
Display low resolution image and then high resolution image after few seconds (HTML)
It depends how you generate/create your images as to whether they will be progressive or not. If your images are not progressive, and you have ImageMagick installed (many Linuuxes do) you can convert an image from non-progressive (also known as baseline JPEG
) to progressive with this command and try it out on your website:
convert nonProgressive.jpg PJPEG:Progressive.jpg
ImageMagick is available for Windows, OSX, Linux for free from here.
Another way to minimise image sizes is with jhead
, and the following command strips out all EXIF information from your image to make it smaller - information removed is things like GPS coordinates, date and time picture was taken, camera model and focal length and shutter speed.
jhead -purejpg image.jpg
Updated Answer
In response to your further question about doing ALL your images, I am not here to tell you what to do! It is your website - you can do as you wish. I was merely suggesting a way for you to try it out on an image and see if you like the results and the performance. If you want to apply to it all your images, it is quite easy, either using standard tools, or GNU Parallel which will do the job in a fraction of the time by using all your CPU cores. Whatever you do, I would urge you to make a backup first
in case anything goes wrong or you later decide progressive, EXIF-stripped JPEGs are not for you.
So, after making a backup, you could do one of these options assuming your website is in /var/www:
find /var/www -iname "*.JPG" -exec convert "{}" "PJPEG:{}" \;
or the same again, with EXIF-stripping, and also colour profile, stripping:
find /var/www -iname "*.jpg" -exec convert "{}" -strip "PJPEG:{}" \;
Or you could use GNU Parallel, like this to use all your CPU cores:
find /var/www -iname "*.jpg" | parallel convert "{}" -strip "PJPEG:{}"
How to load a high res background image without impacting large contentful paint on page load
I have become keen on not using "background-image" for my high res background image.
I prefer to have something like :
<style>
.BackgroundImage-Container {
position: relative;
width : 100%;
height : 100%;
}
.BackgroundImage {
position: absolute;
top : 0;
right : 0;
bottom: 0;
left: 0;
width : 100%;
height : 100%;
object-fit: cover;
}
</style>
<picture class="BackgroundImage-Container">
<img class="BackgroundImage" src="./some-image.png" />
</picture>
So I am able to use any lazyload
, srcset
, <source media="" ></source>
that pleases me.
Image loading like google image PHP
Sounds like you want what are called "progressive JPEGs", where multiple images of varying quality are saved in one file, and the browser loads the low resolution images quickly to get something in the page, and replaces it with higher resolution images once they've been loaded.
Here's a tutorial on saving JPEGs for the web as progressive in Photoshop
Making my background images load faster
You shouldn't use .png for such an image. As a general rule, photographs should be .jpg and graphics (eg. logos) should be indexed .png
I reduced the file size by ~93% down to 89KB from 1.3MB and the visual difference is barely noticeable.
Here's the optimized image: Optimized
And here's yours: Original
Related Topics
Bug with Chrome's Localstorage Implementation
Remove Header and Footer in HTML to Print Page
Rotating a Div How to Set Around Which Point to Rotate
How to Calculate Descender Height in JavaScript
Replace Blinking Text Cursor with Custom Char
Implementing Flipcard on Click Instead of on Hover
How to Use Custom Fonts - Using Font-Face
Position Fixed - Horizontal Scroll
How to Keep Input Placeholder Visible When User Is Typing
Using Document Object in Nodejs
Jquery Draggable Event Changing the CSS of Child Element
Load Mobile CSS If User Is on Android
Vue Transition Not Triggering on Button Click
Highcharts - Issue About Full Chart Width
Anchor Tag Download Attribute Not Working :Bug in Chrome 35.0.1916.114
Load Image from Url and Draw to HTML5 Canvas
Jquery .Show() Not Revealing a Div with Visibility of Hidden