I have a JS function that once the page is loaded swaps the assets from a transparent gif to full images via the data-src below.
<img src="1x1.gif" data-src="full-photo.png" class="asset" /> // My image
window.addEventListener('load', function() {
defer_images();
function defer_images() {
var loadedImages = 0;
var imgDefer = document.getElementsByClassName('asset');
for (var i = 0; i < imgDefer.length; i++) {
if (imgDefer[i].getAttribute('data-src')) {
imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));
var iWidth = imgDefer[i].naturalWidth; // Check image exists
if (iWidth) {
loadedImages++;
} else {
console.log("Image missing: "+imgDefer[i].getAttribute('data-src'));
}
}
// all images exist and have been replaced
if (imgDefer.length === loadedImages) {
doThings();
}
}
}
});
This seems to work fine on a cached page. But if I reload the page and switch tabs – the code doesn't complete as loadedImages++ is never fired.
I can't use setInterval or setTimeout to re-check as this code is used in DoubleClick.
Any help would be really appreciated.
You can detect that images are loaded by using the onload event and failed to load using onerror.
for (var i = 0; i < imgDefer.length; i++) {
var img = imgDefer[i];
if (img.getAttribute('data-src')) {
img.addEventListener("load", function() {
loadedImages++;
});
img.addEventListener("error", function() {
console.log("Image missing: "+ this.getAttribute("data-src"))
});
img.src = img.getAttribute('data-src');
}
}
The reason it fails for non-cached is because it takes time to load the image, and your for loop is fast. Use the events to trigger when the image is loaded.
You can read more here.
Note: Detecting that all images are loaded may require using promises:
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener("load", () => resolve(img));
img.addEventListener("error", err => reject(err));
img.src = src;
// append to the dom or replace here
});
};
Then you can use then() and catch()
Related
The goal: check if all images in the page are loaded. If yes, call to a function, if not, again to check if all images are loaded.
My try:
let checkImages = new Promise(resolve => { resolve(areCompleted()); });
function areCompleted() {
let images = document.querySelectorAll('img');
images = Array.from(images);
for (let i = 0; i < images.length; i++) {
if (!images[i].complete) {
return false;
}
}
return true;
}
If all images are completed, it resolves the promise with true, if not, false.
checkImages.then(completed => {
if (completed) {
completedFunction();
} else {
// Check again
}
});
If the response is true, call a function, if not... I don't know how to do the same check again, but I want to do that checking until the response is true.
This function will check for already loaded images and attach an event listener to all the others so that it can tell when every image in a given container is loaded...
function onImagesLoaded(container, event) {
var images = container.getElementsByTagName("img");
var loaded = images.length;
for (var i = 0; i < images.length; i++) {
if (images[i].complete) {
loaded--;
}
else {
images[i].addEventListener("load", function() {
loaded--;
if (loaded == 0) {
event();
}
});
}
if (loaded == 0) {
event();
}
}
}
var container = document.getElementById("container");
onImagesLoaded(container, function() {
alert("All the images have loaded");
});
<div id="container">
<img src="https://cdn.vox-cdn.com/thumbor/C1SdoDActSv8tPONx_OjwEobUjw=/0x0:1004x753/1200x800/filters:focal(0x0:1004x753)/cdn.vox-cdn.com/uploads/chorus_image/image/49523369/20150428-cloud-computing.0.jpg" />
<img src="https://images.techhive.com/images/article/2016/08/clouds-100678070-primary.idge.jpg" />
<img src="https://www.quali.com/wp-content/uploads/2016/04/101-HEADER-IMAGE.jpg" />
<img src="https://cdn.computerworlduk.com/cmsdata/features/3641280/cloud_istock_malerapaso_thumb800.jpg" />
</div>
This will still work if all the images have already loaded, due to being cached, or if there are no images in the container.
If you want to check all images in a page, simply change the container selector to the body instead...
var container = document.getElementsByTagName("body")[0];
Looking for a simplest way for:
lazy load JS to load images only on a portfolio site.
Should be work on any browser, no jquery please, but simple pure js coding.
Have CSS class called ".image-box" where are the img.
Site is responsive but don't use different image source files for different responsive sizes.
Thanks for help me.
here the js code, i trying this but can't see a progressive image loading:
function init() {
var imgDefer = document.getElementsByTagName('img');
for (var i = 0; i < imgDefer.length; i++) {
if (imgDefer[i].getAttribute('data-src')) {
imgDefer[i].setAttribute('src', imgDefer[i].getAttribute('data-src'));
}
}
}
window.onload = init;
How can i use this on html?
Ok, load the script and then?
Here's my custom lazy loader that I've been meaning to release as open source, but didn't get around to do so yet.
It's dependency free and only uses the regular DOM API.
It might not be perfect, but it should get you started. Please let me know if you need pointers on how to implement the missing imports.
import {registerViewportChangeCallback, unregisterViewportChangeCallback,
elementInView} from './viewportHandler';
import {querySelectorAllCached} from './index';
let lazyCache = {};
let lazyCacheList = [];
let imgElements = null;
let allLoaded = false;
function removeEventListeners (el) {
el.removeEventListener('load', onLoad);
el.removeEventListener('error', onError);
}
function onLoad (e) {
e.target.classList.remove('akm-lazy-loading');
e.target.classList.add('akm-lazy-loaded');
removeEventListeners(e.target);
}
function onError (e) {
e.target.classList.remove('akm-lazy-loading');
e.target.classList.add('akm-lazy-error');
removeEventListeners(e.target);
}
function updateImages () {
if (allLoaded) {
return;
}
if (!imgElements) {
// Find all images on the page and sort them top to bottom
// so that we can start preloading the topmost images first
imgElements = querySelectorAllCached(
'img[data-src], img[data-srcset]')
.sort((a, b) => {
const aTop = a.getBoundingClientRect().top;
const bTop = b.getBoundingClientRect().top;
if (aTop < bTop) {
return -1;
} else if (bTop > aTop) {
return 1;
}
return 0;
});
}
for (let i = 0; i < imgElements.length; i++) {
if (lazyCache[i]) {
continue;
}
const el = imgElements[i];
if (!elementInView(el)) {
continue;
}
loadImage(i);
}
}
function loadImage (i) {
const el = imgElements[i];
lazyCache[i] = true;
lazyCacheList = Object.keys(lazyCache);
if (el.src.indexOf('mno_0643') !== -1) {
// debugger;
}
el.addEventListener('load', onLoad);
el.onLoad = onLoad;
el.addEventListener('error', onError);
el.onError = onError;
el.classList.add('akm-lazy-loading');
if (el.parentElement.tagName.toLowerCase() === 'picture') {
for (const src of
Array.from(el.parentElement.querySelectorAll('source'))) {
if (src.dataset.srcset) {
src.srcset = src.dataset.srcset;
delete src.dataset.srcset;
}
if (src.dataset.src) {
src.src = src.dataset.src;
delete src.dataset.src;
}
}
}
if (el.dataset.srcset) {
el.srcset = el.dataset.srcset;
delete el.dataset.srcset;
}
if (el.dataset.src) {
el.src = el.dataset.src;
delete el.dataset.src;
}
if (lazyCacheList.length === imgElements.length) {
onAllLoaded();
}
}
function onAllLoaded () {
allLoaded = true;
unregisterViewportChangeCallback(updateImages);
}
export function initLazyload () {
registerViewportChangeCallback(updateImages);
}
Is an Intersection Observer cheating?
window.addEventListener("load", function (event) {
var images = document.querySelectorAll('#lazy-img');
//if the browser doesn't support IO
if (!('IntersectionObserver' in window)) {
LoadImagesOldWay(images);
} else {
createObserver(images);
}
}, false);
function createObserver(images) {
var options = {
//root defaults
root: null,
rootMargin: "0px",
//threshold is how much of the element is intersected before firing callback
threshold: .01
};
//create an observer, add the options and callback function
//then add each image to the observer.
var observer = new IntersectionObserver(handleIntersect, options);
images.forEach(function (image) {
observer.observe(image);
});
}
//load the smallest image for old browsers
function LoadImagesOldWay(images) {
images.forEach(function (image) {
var url = image.getAttribute('data-src');
image.setAttribute('src', url);
});
}
function handleIntersect(entries, observer) {
// Loop through the entries
entries.forEach(function (entry) {
//if this entry is intersecting whatsoever
if (entry.intersectionRatio > 0) {
var image = entry.target;
//we get our responsive image set
var url = image.getAttribute('data-src');
//and set them as the actual source
image.setAttribute('src', url);
//we unobserve (Separate) the image entirely
observer.unobserve(entry.target);
console.log('lazy-image loaded!:simpleIO.js line 49 to remove this!');
}
});
}
I like IO because it is extremely robust and open-ended. You could lazy load anything with the intersection observer.
Check out an example with responsive images on my github
edit- wow missed all of the downvotes you got. Not sure why someone saw -1 and thought it need to be even worse. I thought it was a good question, just worded poorly. Don't let em get to you. :D
Simple-Intersection-Observer
I need to load multiple image asynchronously from file field and them check if the dimensions are valid or not. I am pretty close, I just need to get the height of previously loaded image on call back. This is my effort so far:
let files = this.fileUpload.files; //get all uploaded files
for (var i = 0, f; f = files[i]; i++) { //iterate over uploaded file
console.log(f);
let img = new Image();
img.name=f.name;
img.size=f.size;
img.onload = () =>{alert(img.height)} //it is giving height here
if (img.complete) { //callback
alert(img.name + 'loaded');
load_count++;
library_store.uploaded_image.push(
{
height:img.height,
width:img.width, // not coming, just wondering how to get
//the image height from load
name:img.name,
size:img.size
}
);
}
if(load_count === uploaded_file_count){ // if all files are loaded
//do all validation here , I need height and width here
}
What is the best way to do this?
Wouldn't you want to move library_store logic to img.onload? Like below:
let files = this.fileUpload.files; //get all uploaded files
for (var i = 0, f; f = files[i]; i++) { //iterate over uploaded file
console.log(f);
let img = new Image();
img.name=f.name;
img.size=f.size;
img.onload = function() {
// hoping that ```this``` here refers to ```img```
alert(this.name + 'loaded');
load_count++;
library_store.uploaded_image.push({
height:this.height,
width:this.width,
name:this.name,
size:this.size
});
if(load_count === uploaded_file_count){ // if all files are loaded
//do all validation here , I need height and width here
}
}
// img.onload = () =>{alert(img.height)} //it is giving height here
/*
if (img.complete) { //callback
alert(img.name + 'loaded');
load_count++;
library_store.uploaded_image.push({
height:img.height,
width:img.width,
name:img.name,
size:img.size
});
if(load_count === uploaded_file_count){ // if all files are loaded
//do all validation here , I need height and width here
}
}
*/
}
First let's see why you will always fall in this if(img.complete) block even though your images have not been loaded yet:
The complete property of the HTMLImageElement only tells if its resource is being loaded at the time you get the property.
It will report true if the loading succeed, failed, and if the src has not been set.
var img = new Image();
console.log('no-src', img.complete);
img.onerror = function() {
console.log('in-error', img.complete);
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="
};
img.onload = function() {
console.log('in-load', img.complete);
}
img.src = "/some/fake-path.png";
console.log('while loading', img.complete);
And, at the time you get it, you didn't set this src attribute yet, so it will report true even though your image has not yet loaded its resource.
So what you want is an image preloader:
function preloadImages(srcArray, mustAllSucceed) {
return Promise.all(srcArray.map(loadImage));
function loadImage(src) {
return new Promise((resolve, reject) => {
var img = new Image();
img.onload = success;
img.onerror = mustAllSucceed ? success : reject;
img.src = src;
function success() {
resolve(img)
};
});
}
}
preloadImages(['https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg', 'https://upload.wikimedia.org/wikipedia/commons/9/9b/Gran_Mezquita_de_Isfah%C3%A1n%2C_Isfah%C3%A1n%2C_Ir%C3%A1n%2C_2016-09-20%2C_DD_34-36_HDR.jpg'])
.then(images => {
images.forEach(img => console.log(img.src, img.width, img.height));
}, true);
I have some divs that has a class named class='tweetCon'
each containing 1 image and I want to check if the image source exist or not so if it is available I dont do anything but if not I will change the image source with an appropriate image my code is as follow:
$('.tweetimgcon').children('img').each(function () {
imageExists($(this).attr("src"), function (exists) {
if (exists == true) {
} else {
$(this).attr("src", "https://pbs.twimg.com/profile_images/2284174758/v65oai7fxn47qv9nectx.png");
}
}, function () {
});
});
and also imageExists() is as follow:
function imageExists(url, callback,callback2) {
var img = new Image();
img.onload = function() { callback(true);callback2(); };
img.onerror = function() { callback(false);callback2(); };
img.src = url;
}
now the problem is that the src for the images that are not available does not set properly though when I check the src of those images by console.log it shows that they are properly set but it is not shown and when I use inspect element of chrome I can see that src is not set . Can anyone help me?
The point is $(this) doesn't point to your object, because you call $(this) inside your imgExists callback function but NOT the jQuery callback function each, so the this object doesn't point to your original img tag!
The solution should be save the object reference first, try the below:
$('.tweetimgcon').children('img').each(function () {
var _this = $(this);
imageExists($(this).attr("src"), function (exists) {
if (exists == true) {
} else {
_this.attr("src", "https://pbs.twimg.com/profile_images/2284174758/v65oai7fxn47qv9nectx.png");
}
}, function () {
});
});
Just for a little tip for a better callback function use call.
function imageExists(url, cb, ecb) {
var img = new Image();
img.onload = function() { cb.call(img,true,url); };
img.onerror = function() { ecb.call(img,false,url); };
img.src = url;
}
I added img as the first argument and that will then be your this keyword instead of window.
Testing Bin
I'm using the canvas feature of html5. I've got some images to draw on the canvas and I need to check that they have all loaded before I can use them.
I have declared them inside an array, I need a way of checking if they have all loaded at the same time but I am not sure how to do this.
Here is my code:
var color = new Array();
color[0] = new Image();
color[0].src = "green.png";
color[1] = new Image();
color[1].src = "blue.png";
Currently to check if the images have loaded, I would have to do it one by one like so:
color[0].onload = function(){
//code here
}
color[1].onload = function(){
//code here
}
If I had a lot more images, Which I will later in in development, This would be a really inefficient way of checking them all.
How would I check them all at the same time?
If you want to call a function when all the images are loaded, You can try following, it worked for me
var imageCount = images.length;
var imagesLoaded = 0;
for(var i=0; i<imageCount; i++){
images[i].onload = function(){
imagesLoaded++;
if(imagesLoaded == imageCount){
allLoaded();
}
}
}
function allLoaded(){
drawImages();
}
Can't you simply use a loop and assign the same function to all onloads?
var myImages = ["green.png", "blue.png"];
(function() {
var imageCount = myImages.length;
var loadedCount = 0, errorCount = 0;
var checkAllLoaded = function() {
if (loadedCount + errorCount == imageCount ) {
// do what you need to do.
}
};
var onload = function() {
loadedCount++;
checkAllLoaded();
}, onerror = function() {
errorCount++;
checkAllLoaded();
};
for (var i = 0; i < imageCount; i++) {
var img = new Image();
img.onload = onload;
img.onerror = onerror;
img.src = myImages[i];
}
})();
Use the window.onload which fires when all images/frames and external resources are loaded:
window.onload = function(){
// your code here........
};
So, you can safely put your image-related code in window.onload because by the time all images have already loaded.
More information here.
A hackish way to do it is add the JS command in another file and place it in the footer. This way it loads last.
However, using jQuery(document).ready also works better than the native window.onload.
You are using Chrome aren't you?
The solution with Promise would be:
const images = [new Image(), new Image()]
for (const image of images) {
image.src = 'https://picsum.photos/200'
}
function imageIsLoaded(image) {
return new Promise(resolve => {
image.onload = () => resolve()
image.onerror = () => resolve()
})
}
Promise.all(images.map(imageIsLoaded)).then(() => {
alert('All images are loaded')
})
Just onload method in for loop does not solve this task, since onload method is executing asynchronously in the loop. So that larger images in the middle of a loop may be skipped in case if you have some sort of callback just for the last image in the loop.
You can use Async Await to chain the loop to track image loading synchronously.
function loadEachImage(value) {
return new Promise((resolve) => {
var thumb_img = new Image();
thumb_img.src = value;
thumb_img.onload = function () {
console.log(value);
resolve(value); // Can be image width or height values here
}
});
}
function loadImages() {
let i;
let promises = [];
$('.article-thumb img').each(function(i) {
promises.push(loadEachImage( $(this).attr('src') ));
});
Promise.all(promises)
.then((results) => {
console.log("images loaded:", results); // As a `results` you can get values of all images and process it
})
.catch((e) => {
// Handle errors here
});
}
loadImages();
But the disadvantage of this method that it increases loading time since all images are loading synchronously.
Also you can use simple for loop and run callback after each iteration to update/process latest loaded image value. So that you do not have to wait when smaller images are loaded only after the largest.
var article_thumb_h = [];
var article_thumb_min_h = 0;
$('.article-thumb img').each(function(i) {
var thumb_img = new Image();
thumb_img.src = $(this).attr('src');
thumb_img.onload = function () {
article_thumb_h.push( this.height ); // Push height of image whatever is loaded
article_thumb_min_h = Math.min.apply(null, article_thumb_h); // Get min height from array
$('.article-thumb img').height( article_thumb_min_h ); // Update height for all images asynchronously
}
});
Or just use this approach to make a callback after all images are loaded.
It all depends on what you want to do. Hope it will help to somebody.
try this code:
<div class="image-wrap" data-id="2">
<img src="https://www.hd-wallpapersdownload.com/script/bulk-upload/desktop-free-peacock-feather-images-dowload.jpg" class="img-load" data-id="1">
<i class="fa fa-spinner fa-spin loader-2" style="font-size:24px"></i>
</div>
<div class="image-wrap" data-id="3">
<img src="http://diagramcenter.org/wp-content/uploads/2016/03/image.png" class="img-load" data-id="1">
<i class="fa fa-spinner fa-spin loader-3" style="font-size:24px"></i>
</div>
<script type="text/javascript">
jQuery(document).ready(function(){
var allImages = jQuery(".img-load").length;
jQuery(".img-load").each(function(){
var image = jQuery(this);
jQuery('<img />').attr('src', image.attr('src')).one("load",function(){
var dataid = image.parent().attr('data-id');
console.log(dataid);
console.log('load');
});
});
});
</script>