I have the following HTML in my project.
<div class="container" id="crop">
<img id="timage" src="http://example.com/color/style/etc/" alt="timages" />
I also have the following javascript:
$(window).load(function () {
$("#slider").change(function update() {
sVal = $(this).val();
if (sVal == 2) {
$('#timage').prop('src',"http://example.com/" +
tForm +
"color.blahblah" +
itemCode +
"therest_ofthe_URL");}
sVal = $(this).val();
if (sVal == 3) {
$('#timage').prop('src',"http://example.com/" +
tForm +
"color.blahblah" +
itemCode +
"therest_ofthe_URL");}
);}
It works splendidly to replace the image with the string when the slider value reaches certain numbers. The problem is, the image is being created on the back end behind the scenes and takes quite some time before it is ready. In the meantime, you are just staring at the original image wondering if the slider did anything.
How do I add a loading indicator to let people know that the image is about to change?
First, place a loading indicator where you want it. You could replace #timage with a spinning gif, for example. Then use this code to start the new image loading:
var img = new Image();
img.onload = function () {
$('#timage').prop('src', img.src);
}
img.src = '/image/to/load/here';
The function will be executed when your new image has been retrieved from the server and loaded. Since it's already cached on the client, it should load instantly once the src for #timage is set.
Related
I am building an image gallery that is populated from a JSON file. Everything works as intended currently, but as of right now there is no pre-loading of content after the initial page load. What I would like to happen is after the "view more" button is clicked I will have some "loading" text show, the batch of images will preload, the "loading" text will disappear, then the images will be added to the page once all items have loaded.
Here is the section of the code that involves the JSON fetch request and the building of elements on the page:
var HTML = '';
var itemsStart = 6; // Starting number of items on page.
var itemsAdd = 9; // Number of items to add to page at a time via button click.
var pItems = document.getElementById('pItems');
var pWrapper = document.getElementById('pItemWrapper');
function addProjects() {
pItems.insertAdjacentHTML('beforeend', HTML);
console.log('BUILD PROJECTS');
}
//Load json
fetch('data/projects.json').then(function (response) {
return response.json();
}).then(function (data){
//Loop through first set of items to load on page.
for (var i = 0; i < itemsStart; i++) {
HTML += '<img src="' + data.projects[i].Image + '" alt=""></img>';
if (i == (itemsStart - 1)) {
addProjects();
}
}
//Load additional items when clicking 'view more'.
document.getElementById('view-more').addEventListener('click', function() {
HTML = '';
for (var i = itemsStart; i < itemsStart + itemsAdd; i++) {
if ((i < data.projects.length)) {
HTML += '<img src="' + data.projects[i].Image + '" alt=""></img>';
}
if (i == ((itemsStart + itemsAdd) - 1) ) {
addProjects();
}
}
itemsStart = itemsStart + itemsAdd;
});
}).catch(function(error) {
console.error('Something went wrong');
});
I'm not using jQuery so I'd like to stick to vanilla js. I don't know what I need to add to my button event listener beyond what I have, I've never tried preloading images like this without using a plugin but I feel like I don't need to load an entire plugin just for this one thing and I'd like to understand how this would work.
EDIT
I feel like I'm almost there, but I still have something wrong. I made some modifications to have each item inside its own container, but instead of that happening I am creating an empty container for each pass of the loop, then the last container gets each image added to it. My code looks like this:
var itemsAdd = 3;
//Load additional items when clicking 'view more'.
document.getElementById('view-more').addEventListener('click', function() {
//The loop will add the next 3 items in the json file per click.
for (var i = itemsStart; i < itemsStart + itemsAdd; i++) {
var placeholder = document.createElement('div');
var src = 'img/portfolio/' + data.projects[i].url;
placeholder.innerHTML= '<div class="img-container">' + data.projects[i].Title + '</div>';
var galleryItem = placeholder.firstChild;
preloadImage(src).then(function (image) {
galleryItem.append(image);
});
pItems.append(galleryItem);
}
itemsStart = itemsStart + itemsAdd;
});
The result I get is this:
Is this because of how the promise works for the preloadImage function?
Generally you would create an image with JavaScript through either document.createElement('img') or the Image() constructor. Both result an in instance of an HTMLImageElement.
With this, you'll have an image that is not connected to the DOM, so it's not visible to you or the user. You can use this element to load the image behind the scenes by setting the image' src property.
Then by listening to the onload event you can determine whenever the image has finished loading. From here you could continue your flow by adding the image to the DOM and, for example, fade it in with animation.
The example below shows this process in the form of a function that returns a Promise. This promise will resolve whenever the load event has been triggered.
const preloadImage = src =>
new Promise(resolve => {
const image = new Image();
const onLoad = () => {
resolve(image);
};
image.addEventListener('load', onLoad, {once: true});
image.src = src;
});
Using it should be like this:
const src = 'http://example.com/my-image.jpg';
preloadImage(src).then(image => {
// Here the image has been loaded and is available to be appended, or otherwise.
document.body.append(image);
});
In your case you would loop over each image, call the function while passing the URL of the image, and append the image to the DOM when it's finished loading.
You can handle any animations, like fade-ins with CSS.
Real world implementation
So how should you implement this in your project? You'll need to start at the point where you create your images. Currently your images are created as strings. But strings are just strings, they aren't HTML elements, yet.
I'd recommend that you'll create a placeholder for each image. This placeholder could visually indicate that an image is loading and act as a wrapper for the image. Add this placeholder immediately to the pItems element.
Then load the image for each Image in your data.projects array by calling the preloadImage. Whenever the image is loaded, append it to the placeholder we've just created. You should now have the effect that first a placeholder is added and the images are starting to appear one by one.
The same logic should be applied for the load more loop.
...
}).then(function (data){
for (let i = 0; i < itemsStart; i++) {
// Create a <div class="placeholder"> which should act as a placeholder / wrapper.
const placeholder = document.createElement('div');
placeholder.classList.add('placeholder');
// Create the image based on the Image value.
// Whenever the image is loaded, add it to the placeholder.
const src = data.projects[i].Image;
preloadImage(src).then(image => {
placeholder.append(image);
});
// Immediately add the placeholder.
// This line doesn't wait for preloadImage to finish because preloadImage is asynchronous. Look into Promises if that is new to you.
pItems.append(placeholder);
}
...
});
From here you've got control over how the placeholder should look and any animations an image inside that placeholder should have.
I think you could put a <div> with black background over the loading image using css and when the image is ready remove it with js. You can detect when the image is loaded using the img.onload = () => {} function.
Or you could place there an img with the loading screen and replace it with the actual image when the image has loaded.
I am not new to programming (Fortran, Python) but definitively new to javascript. I also apologize for below function having been taken from another Stack Overflow post but that is the best example I found for for helping me understand want I am trying.
I am writing a Reveal.js presentation that shall show satellite pictures named "sat_picts" + ymd + hr + min + ".jpg"". The pictures are updated every quarter of an hour but it may happen that when they are called the latest one is not yet available so I want to present the one before.
My readings make me understand that this is a characteristic of asynchronous behaviour of the problem and that callback functions are one way of solving the issue.
Unfortunately none of the examples or readings I found show explicitely how to retrieve the results of the callback function in my case "error" and "success". I need these results for an "if, else if" block which then writes in the HTML section the actual or the previous satellite pictures.
I would appreciate if someone could show how to resolve my issue or ev. how to better implement such a solution and or indicate me an appropriate reading.
<section> // Defines Reveal.js slide
<h5>Satellite Picture</h5>
<script>
function testImage(url, callback) {
let img = new Image();
img.onerror = img.onabort = function() {
// callback(url, "error");
let resx = callback(url, "error"); // thought was way to access resx i.e. "error"
alert(resx); // alert is working properly
}
img.onload = function() {
callback(url, "success");
let resy = callback(url, "success"); // thought was way to access resy i.e. "success"
alert(resy); // alert is working properly
}
img.src = url;
};
function record(url, result) {
// document.write(url, result); // ==> returns result but not in the Reveal.js HTML Section
return result;
};
let imgurl = "https://server/sat_picts" + ymd + hr + min + ".jpg";
testImage(imgurl, record);
// If the picture can be loaded then write satellite picture to document
if (resy === "success") document.write('<img src="https://server/sat_picts" + ymd + hr + min + ".jpg">')
// If the the picture cannot be loaded then write satellite picture from previous hour to document
else if (resx === "error") document.write('<img src="https://server/sat_picts" + ymd + hr + min_minus_15 + ".jpg">');
</script>
</section>
If I understand what you are trying to do, I think this is what you want:
<section id="targetSection">
<script>
// allow a success and error call back to be passed in as separate args
function testImage(url, successCallback, errorCallback) {
let img = new Image();
img.onerror = img.onabort = function() {
errorCallback();
}
img.onload = function() {
successCallback();
}
img.src = url;
};
// If the picture can be loaded then write satellite picture to document
function. onSuccess() {
// write the new image to the "section"
const img = document.createElement('img');
img.setAttribute('src', "https://server/sat_picts" + ymd + hr + min + ".jpg");
document.getElementById('targetSection').appendChild(img);
}
// If the the picture cannot be loaded then write satellite picture from previous hour to document
function onError() {
// write the new image to the "section"
const img = document.createElement('img');
img.setAttribute('src', "https://server/sat_picts" + ymd + hr + min_minus_15 + ".jpg");
document.getElementById('targetSection').appendChild(img);
}
let imgurl = "https://server/sat_picts" + ymd + hr + min + ".jpg";
// pass the two callbacks to be called on success or error respectively.
testImage(imgurl, onSuccess, onError);
</script>
</section>
I could be wrong about this, but it looks like the error might be the way the img tags are written in the document.write functions. The img tag needs to be closed with a '/' at the end:
<img src="https://server/sat_picts" + ymd + hr + min + ".jpg" />
The other possibility I can think of is a little gotcha in HTML, which is: when the containing element only has an image tag and no other content, then they won't show the image unless they have some dimensions (height, optionally width) associated with them, so you can check your HTML and if necessary add a CSS property to it that gives it some height (and/or width) to see if this is the issue.
e.g.
<div><img src="myImg.jpg" /></div>
would show nothing if the containing div hasn't been given a height (it will automatically span the width of the parent element).
I have a Python websocket application which sends an image path to a JavaScript HTML file. When the image path is received, I change the background image of the webpage to the supplied image.
The issue I'm having at the moment, is when the background changes from the old image to the new, there is a momentary 'flash' of which, which suggests that there is a period of time (albeit very brief) where the new image is being loaded.
I've tried various preloading methodologies, but I'm very new to JavaScript, so am not sure which method would provide for a seamless transition between the two images. This is the method I currently have implemented:
var NewImage = new Image();
NewImage = message.data; //This is the image string received from Python
document.body.style.backgroundImage = "url('" + NewImage + "')";
The above displays the image as desired (including my CSS formatting), but the transition is unsightly.
I also had a play around with the following method, which makes more sense to me, but I couldn't get it to work.
var NewImage = new Image();
//Websocket function here
PreloadImage;
function PreloadImage() {
NewImage.onload = ImageLoadComplete();
NewImage.src = message.data;
}
function ImageLoadComplete() {
document.body.style.backgroundImage = "url('" + NewImage + "')"
}
I'm not sure how to pass variables between the functions in this second method. Given the explicit 'onload' call in this method, I feel that this may provide the functionality I'm after.
How can I preload the images in order to seamlessly transition between them?
EDIT: The working code is posted below. Thanks to #blender for pointing me in the right direction :)
var NewImage = new Image;
NewImage.src = message.data; //Data from Python
if (NewImage.complete) {
NewImage.onload = ImageLoadComplete();
} else {
NewImage.onload = ImageLoadComplete;
}
function ImageLoadComplete() {
document.body.style.backgroundImage = "url('" + NewImage.src + "')";
}
You're not actually passing a callback function:
NewImage.onload = ImageLoadComplete();
You're passing in the result of calling ImageLoadComplete(), which means you call your callback immediately. Don't call the function and your code should work as expected (most of the time):
NewImage.onload = ImageLoadComplete;
One issue that you'll encounter is that onload may not get called by some browsers if the image is loaded from cache. You have to call the callback manually if that's the case:
if (NewImage.complete || NewImage.height > 0) {
ImageLoadComplete();
} else {
NewImage.onload = ImageLoadComplete;
}
I want to show a remote image on my page. I use Bootstrap 2.3.2 Carousel. All the information comes from another web site's RSS feed. I get data into a div like the following:
...
<div id="newsItem-<?php echo $i;?>" class="item" data-src="<?php echo $feed[$i]->image; ?>" data-alt="<?php echo $feed[$i]->title; ?>">
</div>
...
The images takes too long to load. Page is loaded about 15 seconds. So I have decided to load images after the page loading finished.
There could be various dimensions of the pictures to be displayed.
I want to show the largest existing one.
For each news item, all the images may have different but similar dimensions such as 1024x768, 620x350, 528x350, 527x350.
I have written a jQuery script to achieve this but something is wrong.
jQuery(function () {
jQuery("div[id^='newsItem-']").each(function () {
var r = jQuery(this).attr("data-src");
var r620 = r.replace(".jpg", "-620x350.jpg");
var r527 = r.replace(".jpg", "-527x350.jpg");
var r1024 = r.replace(".jpg", "-1024x678.jpg");
var r528 = r.replace(".jpg", "-528x350.jpg");
var altImg = jQuery(this).attr("data-alt");
if (pictureExists(r1024)){
r = r1024;
}
else if (pictureExists(r620)){
r = r620;
}
else if (pictureExists(r528)){
r = r528;
}
else if (pictureExists(r527)){
r = r527;
}
jQuery(this).prepend("<img src='" + r + "' alt='" + altImg + "' />");
jQuery(this).removeAttr("data-alt");
jQuery(this).removeAttr("data-src");
});
});
function pictureExists(url) {
var img = new Image();
img.src = url;
if (img.height !== 0) {
return false;
} else {
return true;
}
}
I want to display the largest existing picture in the carousel.
You cannot know the height/width of the image until its loaded. So its an async process.
In pictureExists function try to do it in this way:
/ Create new image
var img = new Image();
// Create var for image source
var imageSrc = "http://example.com/blah.jpg";
// define what happens once the image is loaded.
img.onload = function() {
// Stuff to do after image load ( jQuery and all that )
// Within here you can make use of src=imageSrc,
// knowing that it's been loaded.
};
// Attach the source last.
// The onload function will now trigger once it's loaded.
img.src = imageSrc;
If you want to use the above way then you will have to implement promise structure to tackle the async nature of the image load to fetch the height/width
Or you can use this small plugin.
https://github.com/desandro/imagesloaded
i call on a PHP script that generates graph images for me, however, it takes a few seconds. Is there a way to detect when it has finished loading, on the user side, and only swap it with the old image when the php script has finished and the image is ready?
here is the Javascript function i use to call the PHP script:
EDIT (CODE UPDATED)
function loadGraph(self,graph,varID) {
var img = new Image();
img.onload = function() {
$(self).parents().parents().siblings(".graph_container").empty().append(img);
};
img.src = 'drawGraph.php?type=journey_report&graph=' + graph +
(varID != null ? '&varID=' + varID : '') + '&companyID=<?php echo $_SESSION['companyID'] ?>';
}
and here is the graph container and the link that uses that function:
<div class="graph_container">
<img src="drawGraph.php?type=journey_report&graph=outOfDate_vs_upToDate&companyID=<?php
echo $_SESSION['companyID'] ?>" />
</div>
<div class="reportItemWrapper">
<div class="reportItem"><b>Total</b></div>
thanks!
Create an Image object and set its "onload" handler to a function that does what you do in that first block of code. Then set its "src" attribute to your URL.
var img = new Image();
img.onload = function() {
// that jQuery stuff
};
img.src = "drawGraph.php?type=journey_report ...";
Now that'll only work if the URL is cacheable. If not, then you could re-work that jQuery code so that you just stuff the Image element into the DOM.
$(self).parents().parents().siblings(".graph_container").empty().append(img);
(You'd still do that in the onload handler.)