As I mentioned in the title I have a problem properly embedding my animation in my Django project.
At some point, I decided to update the logo used by my web page built-in Django.
Having small experience with JavaScript I was looking for approaches that fit my needs.
I built an animation engine having some inspiration from this StackOverflow answer. (Thanks to user gilly3).
My approach is, though, different in this topic because I realised that if I would have a lot of pictures then sticking/concatenating them together in one file might be difficult. I've decided to use another approach and use a bunch of files instead of one. For that sake, I built a function with a generator which I could call in another function to display all pictures in order. This animation engine looks like this:
function* indexGenerator() {
let index = 1;
while (index < 28) {
yield index++;
}
if (index = 28)
yield* indexGenerator();
};
var number = indexGenerator();
setInterval(function animationslider()
{
var file = "<img src=\"../../../static/my_project/logos/media/slides_banner_f/slide_" + number.next().value + ".jpg\" />";
document.getElementById("animation-slider").innerHTML = file;
$("#animation-slider").fadeIn(1000);
}, 100);
$("#animation-slider").fadeIn(1000); doesn't do the trick with values from 10-1000.
I record what this looks like:
https://youtu.be/RVBtLbBArh0
I suspect that the solution to the first relocation problem can be solved using CSS, but I don't know how to do it, yet.
The blinking/flickering of the animation is probably caused by loading all these images one by one. In the example video above, there are 28 of them. I wonder if this content would be loaded asynchronously (e.g. using Ajax) would it solve the problem? Is it possible to load all and after all is loaded, then display the animation (some sort of preloading)? I will be grateful for all suggestions and hints.
I'm aware that in the topic mentioned by me before there is a solution. But I'm curious about other approaches.
At the moment, from what I checked, the best method would be to convert pictures to the format:
APNG
WebP which seems to be better quality.
GIF
Having that kind of file, there is no problem with loading and flickering.
The flickering is the time it takes to create the dom element, insert it into your page, make the web request to download the image, and final render the image. That's too much work to do in the moment you want to animate the next frame! It would look even worse running from a client with a poor connection to the server.
To solve this, preload all of the images. This is the benefit of a sprite - it loads all at once by its very nature. But, if you have individual images, you can still preload them. Just hide them all at first, and show them one by one.
One way you can show them one at a time, is to set up a style that hides all but the first child image. Then, to animate, move the first image to the end of the list.
#animation-slider img ~ img {
display: none;
}
const animationContainer = document.getElementById("animation-slider");
for (let i = 0; i < 28; i++) {
const img = document.createElement("img");
img.src = `../../../static/my_project/logos/media/slides_banner_f/slide_${i}.jpg`;
animationContainer.appendChild(img);
}
addEventListener("load", () => {
setInterval(() => animationContainer.appendChild(animationContainer.children[0]), 100);
});
Here's a working example, using my own image:
const animationContainer = document.getElementById("animation-slider");
for (let i = 1; i < 16; i++) {
const img = document.createElement("img");
img.src = `https://jumpingfishes.com/dancingpurpleunicorns/charging${i.toString().padStart(2, "0")}.png`;
animationContainer.appendChild(img);
}
addEventListener("load", () => {
setInterval(() => animationContainer.appendChild(animationContainer.children[0]), 100);
});
#animation-slider img ~ img {
display: none;
}
<div id="animation-slider"></div>
Related
To give you some background, many (if not all) websites load their images one by one, so if there are a lot of images, and/or you have a slow computer, most of the images wont show up. This is avoidable for the most part, however if you're running a script to exact image URLs, then you don't need to see the image, you just want its URL. My question is as follows:
Is it possible to trick a webpage into thinking an image is done loading so that it will start loading the next one?
Typically browser will not wait for one image to be downloaded before requesting the next image. It will request all images simultaneously, as soon as it gets the srcs of those images.
Are you sure that the images are indeed waiting for previous image to download or are they waiting for a specific time interval?
In case if you are sure that it depends on download of previous image, then what you can do is, route all your requests through some proxy server / firewall and configure it to return an empty file with HTTP status 200 whenever an image is requested from that site.
That way the browser (or actually the website code) will assume that it has downloaded the image successfully.
how do I do that? – Jack Kasbrack
That's actually a very open ended / opinion based question. It will also depend on your OS, browser, system permissions etc. Assuming you are using Windows and have sufficient permissions, you can try using Fiddler. It has an AutoResponder functionality that you can use.
(I've no affiliation with Fiddler / Telerik as such. I'm suggesting it only as an example and because I've used it in the past and know that it can be used for the aforementioned purpose. There will be many more products that provide similar functionality and you should use the product of your choice.)
use a plugin called lazy load. what it does is it will load the whole webpage and will just load the image later on. it will only load the image when the user scroll on it.
To extract all image URLs to a text file maybe you could use something like this,
If you execute this script inside any website it will list the URLs of the images
document.querySelectorAll('*[src]').forEach((item) => {
const isImage = item.src.match(/(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png|svg)/g);
if (isImage) console.log(item.src);
});
You could also use the same idea to read Style from elements and get images from background url or something, like that:
document.querySelectorAll('*').forEach((item) => {
const computedItem = getComputedStyle(item);
Object.keys(computedItem).forEach((attr) => {
const style = computedItem[attr];
const image = style.match(/(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png|svg)/g);
if (image) console.log(image[0]);
});
});
So, at the end of the day you could do some function like that, which will return an array of all images on the site
function getImageURLS() {
let images = [];
document.querySelectorAll('*').forEach((item) => {
const computedItem = getComputedStyle(item);
Object.keys(computedItem).forEach((attr) => {
const style = computedItem[attr];
const image = style.match(/(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png|svg)/g);
if (image) images.push(image[0]);
});
});
document.querySelectorAll('*[src]').forEach((item) => {
const isImage = item.src.match(/(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png|svg)/g);
if (isImage) images.push(item.src);
});
return images;
}
It can probably be optimized but, well you get the idea..
If you just want to extract images once. You can use some tools like
1) Chrome Extension
2) Software
3) Online website
If you want to run it multiple times. Probably use the above code https://stackoverflow.com/a/53245330/4674358 wrapped in if condition
if(document.readyState === "complete") {
extractURL();
}
else {
//Add onload or DOMContentLoaded event listeners here: for example,
window.addEventListener("onload", function () {
extractURL();
}, false);
//or
/*document.addEventListener("DOMContentLoaded", function () {
extractURL();
}, false);*/
}
extractURL() {
//code mentioned above
}
You want the "DOMContentLoaded" event docs. It fires as soon as the document is fully parsed, but before everything has been loaded.
let addIfImage = (list, image) => image.src.match(/(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|jpeg|gif|png|svg)/g) ?
[image.src, ...list] :
list;
let getSrcFromTags= (tag = 'img') => Array.from(document.getElementsByTagName(tag))
.reduce(addIfImage, []);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", doSomething);
} else { // `DOMContentLoaded` already fired
doSomething();
}
I am using this, works as expected:
var imageLoading = function(n) {
var image = document.images[n];
var downloadingImage = new Image();
downloadingImage.onload = function(){
image.src = this.src;
console.log('Image ' + n + ' loaded');
if (document.images[++n]) {
imageLoading(n);
}
};
downloadingImage.src = image.getAttribute("data-src");
}
document.addEventListener("DOMContentLoaded", function(event) {
setTimeout(function() {
imageLoading(0);
}, 0);
});
And change every src attribute of image element to data-src
I am loading a large number of images into a dynamic DIV and I am using a preloader to get the images.
imageObj = new Image();
imageObj.src = imgpath + imgname;
Each of these events creates a GET that I can see and monitor in Firebug.
If I know the name and path of an image, can I watch the relevant XMLHttpRequest to see if the GET has completed?
I do not want to rely on (or use) .onload events for this process.
The pseudo would look something like this...
if (imageObj.GET = 'complete')
Has anyone had any experience of this?
EDIT 1
Thanks to the help from Bart (see below) I have changed my image preloader to store an array of the image objects...
function imagePreLoader(imgname) {
images[imgnum] = new Image();
images[imgnum].src = imgpath + imgname;// load the image
imgnum ++;
}
And then, after all my other functions have run to build the content DIVs, I used the image.complete attribute in the following...
var interval = setInterval(function () {
imgcount = imgnum - 1; // because the imgnum counter ++ after src is called.
ok = 1;
for (i=0; i<imgcount; i++) {
if (images[i].complete == false){
ok = 0;
}
}
if (ok == 1) {
clearInterval(interval);
showIndexOnLoad();
}
}, 1000);
This waits until all the images are complete and only triggers the showIndexOnLoad() function when I get the 'ok' from the interval function.
All images now appear as I wanted, all at once with no additional waits for the GETs to catch up.
Well done Bart for putting me on to the image.complete attribute.
You can watch the complete property of the image to see if the image is fully loaded or not.
Here's an example.
http://jsfiddle.net/t3esV/1/
function load (source) {
var img = new Image();
img.src = source;
console.log('Loading ' + source);
var interval = setInterval(function () {
if (img.complete) {
clearInterval(interval);
complete(img);
}
}, 400);
};
function complete(img) {
console.log('Loaded', img.src);
document.body.appendChild(img);
};
Note: This example fails to clear the interval when something goes wrong and complete is never set to true.
Update
I wrote a simple jQuery.preload plugin to take advantage of the image.complete property.
This is a very interesting problem, and I am afraid there is no actual solution to this. The load event for images is when the image is being rendered and the browser knows the width and height of it.
What you would be after would be a tag-applicable readystatechange event. Alas, only IE allows you to bind those to non-document elements, so this is not an option.
There are a bunch of plug-ins that allow you to go around it, as well. One pretty hot one is https://github.com/desandro/imagesloaded , which has the added advantage of dealing with all the browser differences very efficiently. It, however, still relies on the load event (and I am pretty sure this is the only way to start doing what you want to do).
I have started learning javascript a couple of days ago and done the codeacadmey stuff and thought i will try make a simple game.
so i came up with the memory game where you have to find pairs of images.
it is all working and i got a score system in place but a few people have said the delay that happens once the cards have been chosen to allowing another chocie is hindering them and i cant figure out how to improve that performance.
here is a bit of code i think is causing the delay, is there any better way to produce the same result, sorry about before i am new to all this.
function check() {
clearInterval(tid);
if(people[secondchocie] === people[firstchocie]) {
cntr++;
(cntr === numOfMatches) {
stop();
score = checkScore(amountGoes);
$('#gameFinished').append('<p>Well done, you managed to complete the game your score is <span>' + score + '</span></p>');
}
turns = 0;
return;
} else {
document.images[firstchocie + numOfImages].src = backcard;
document.images[secondchocie + numOfImages].src = backcard;
turns = 0;
return;
}
}
I can't create comments, so I'll put this in an answer.
Although I agree with lukas.pukenis ...
Changing images can take some time if they aren't preloaded. To test this: Try to get them into the browser cache by adding them somewhere else in the page (i.e. with an IMG tag) before starting the game.
Then you'll be sure they are in the cache.
edit:
I recently used this:
var cache = [];
function preLoadImages(arrImg)
{
var args_len = arrImg.length;
for (var i = args_len; i--;)
{
var cacheImage = document.createElement('img');
cacheImage.src = arrImg[i];
cache.push(cacheImage);
}
}
preLoadImages(['images/img1.png','images/img2.png','images/img3.png',]);
you can add all images needed to the javascript array.
If your a quick study :) you can do the following:
If your page is generated by php you could let php read the entire images directory and write the filenames in the page as javascript code.
Or you could create an ajax request wich returns all paths to the images and sends them to the preload function as a callback.
Use Firefox 10.0.2 to open Framework.html from this project: http://code.google.com/p/unitspeeds-vhh/ (EDIT: I'm now preloading the icons. Use this revision if you'd like to try to debug the original problem.)
Hit F5 to refresh.
Hit F5 a few more times very quickly.
Expected: The unit icons (unitIconObj in the code) should always render.Actual: They never render on the first page load. First F5 usually shows all icons. Some very fast repetitions of F5 cause most most -- but not all -- icons to show.
The basic problem I'm trying to solve is icons not rendering properly the first time, and I assume this means I need to be preloading the images. I've been trying a few different methods to do this, but the behavior is not strictly reproducible, and I think I just don't know enough about refreshes and caching to figure this out myself. The icons themselves are very small image files, so I'm surprised to see that this is an issue at all.
Very sorry for the messy code and question -- I'm a noob! Any advice would be welcome.
Edit: Here's the part where I load the image file and render it:
for (var x = 0; x < sortedOutput.length; x++)
{
// Draw the unit icon
var unitIconObj = new Image();
if (sortedOutput[x].Filename == "--") // I don't have real icons for a few units
{
unitIconObj.src = "Icons/Creep.jpg";
} else
{
unitIconObj.src = "Icons/" + sortedOutput[x].Filename + ".jpg";
}
speedContext.drawImage(unitIconObj, ChartBuffer+textBlock+2+4*iconSpace, 50+(x*iconSpace), BarHeight, BarHeight);
}
Have you tried changing this so it waits to call drawImage function until the onload event on the image fires. This will ensure that the image is loaded before any of the other magic happens.
for (var x = 0; x < sortedOutput.length; x++)
{
var unitIconObj= new Image();
unitIconObj.onload = function(){
speedContext.drawImage(unitIconObj, ChartBuffer+textBlock+2+4*iconSpace, 50+(x*iconSpace), BarHeight, BarHeight);
};
if (sortedOutput[x].Filename == "--") // I don't have real icons for a few units
{
unitIconObj.src = "Icons/Creep.jpg";
} else
{
unitIconObj.src = "Icons/" + sortedOutput[x].Filename + ".jpg";
}
}
found this here: https://developer.mozilla.org/en/Canvas_tutorial/Using_images
I'm fully aware that this question has been asked and answered everywhere, both on SO and off. However, every time there seems to be a different answer, e.g. this, this and that.
I don't care whether it's using jQuery or not - what's important is that it works, and is cross-browser.]
So, what is the best way to preload images?
Unfortunately, that depends on your purpose.
If you plan to use the images for purposes of style, your best bet is to use sprites.
http://www.alistapart.com/articles/sprites2
However, if you plan to use the images in <img> tags, then you'll want to pre-load them with
function preload(sources)
{
var images = [];
for (i = 0, length = sources.length; i < length; ++i) {
images[i] = new Image();
images[i].src = sources[i];
}
}
(modified source taken from What is the best way to preload multiple images in JavaScript?)
using new Image() does not involve the expense of using DOM methods but a new request for the image specified will be added to the queue. As the image is, at this point, not actually added to the page, there is no re-rendering involved. I would recommend, however, adding this to the end of your page (as all of your scripts should be, when possible) to prevent it from holding up more critical elements.
Edit: Edited to reflect comment quite correctly pointing out that separate Image objects are required to work properly. Thanks, and my bad for not checking it more closely.
Edit2: edited to make the reusability more obvious
Edit 3 (3 years later):
Due to changes in how browsers handle non-visible images (display:none or, as in this answer, never appended to the document) a new approach to pre-loading is preferred.
You can use an Ajax request to force early retrieval of images. Using jQuery, for example:
jQuery.get(source);
Or in the context of our previous example, you could do:
function preload(sources)
{
jQuery.each(sources, function(i,source) { jQuery.get(source); });
}
Note that this doesn't apply to the case of sprites which are fine as-is. This is just for things like photo galleries or sliders/carousels with images where the images aren't loading because they are not visible initially.
Also note that this method does not work for IE (ajax is normally not used to retrieve image data).
Spriting
As others have mentioned, spriting works quite well for a variety of reasons, however, it's not as good as its made out to be.
On the upside, you end up making only one HTTP request for your images. YMMV though.
On the down side you are loading everything in one HTTP request. Since most current browsers are limited to 2 concurrent connections the image request can block other requests. Hence YMMV and something like your menu background might not render for a bit.
Multiple images share the same color palette so there is some saving but this is not always the case and even so it's negligible.
Compression is improved because there is more shared data between images.
Dealing with irregular shapes is tricky though. Combining all new images into the new one is another annoyance.
Low jack approach using <img> tags
If you are looking for the most definitive solution then you should go with the low-jack approach which I still prefer. Create <img> links to the images at the end of your document and set the width and height to 1x1 pixel and additionally put them in a hidden div. If they are at the end of the page, they will be loaded after other content.
As of January 2013 none of the methods described here worked for me, so here's what did instead, tested and working with Chrome 25 and Firefox 18. Uses jQuery and this plugin to work around the load event quirks:
function preload(sources, callback) {
if(sources.length) {
var preloaderDiv = $('<div style="display: none;"></div>').prependTo(document.body);
$.each(sources, function(i,source) {
$("<img/>").attr("src", source).appendTo(preloaderDiv);
if(i == (sources.length-1)) {
$(preloaderDiv).imagesLoaded(function() {
$(this).remove();
if(callback) callback();
});
}
});
} else {
if(callback) callback();
}
}
Usage:
preload(['/img/a.png', '/img/b.png', '/img/c.png'], function() {
console.log("done");
});
Note that you'll get mixed results if the cache is disabled, which it is by default on Chrome when the developer tools are open, so keep that in mind.
In my opinion, using Multipart XMLHttpRequest introduced by some libraries will be a preferred solution in the following years. However IE < v8, still don't support data:uri (even IE8 has limited support, allowing up to 32kb). Here is an implementation of parallel image preloading - http://code.google.com/p/core-framework/wiki/ImagePreloading , it's bundled in framework but still worth taking a look.
This was from a long time ago so I dont know how many people are still interested in preloading an image.
My solution was even more simple.
I just used CSS.
#hidden_preload {
height: 1px;
left: -20000px;
position: absolute;
top: -20000px;
width: 1px;
}
Here goes my simple solution with a fade in on the image after it is loaded.
function preloadImage(_imgUrl, _container){
var image = new Image();
image.src = _imgUrl;
image.onload = function(){
$(_container).fadeTo(500, 1);
};
}
For my use case I had a carousel with full screen images that I wanted to preload. However since the images display in order, and could take a few seconds each to load, it's important that I load them in order, sequentially.
For this I used the async library's waterfall() method (https://github.com/caolan/async#waterfall)
// Preload all images in the carousel in order.
image_preload_array = [];
$('div.carousel-image').each(function(){
var url = $(this).data('image-url');
image_preload_array.push(function(callback) {
var $img = $('<img/>')
$img.load(function() {
callback(null);
})[0].src = url;
});
});
async.waterfall(image_preload_array);
This works by creating an array of functions, each function is passed the parameter callback() which it needs to execute in order to call the next function in the array. The first parameter of callback() is an error message, which will exit the sequence if a non-null value is provided, so we pass null each time.
See this:
http://www.mattfarina.com/2007/02/01/preloading_images_with_jquery
Related question on SO:
jquery hidden preload