I am building a slideshow with a few hundred images and would like to build a nice loading bar, so the idea was to preload the images using JavaScript, then initialize the rest of the UI afterwords.
Preloading the images is not a problem, but getting the browser to update the status as things load is. I've tried a few things, but the browser will only repaint the display after it finishes.
I've even tried the script from this question, but I get the same results.
Here's what I've got so far (imgList is an array of filenames. I'm using Prototype.)
var imageBuf = []
var loadCount = 0
$('loadStatus').update("0/"+imgList.length)
function init() {
imgList.each(function(element){
imageBuf[element] = new Image()
//imageBuf[element].onload = window.setTimeout("count()",0) // gives "not implemented" error in IE
imageBuf[element].onload = function(){count()}
imageBuf[element].src = "thumbs/"+element
})
}
function count() {
loadCount++
$('loadStatus').update(loadCount+"/"+imgList.length)
}
init()
Try using the function from my answer to this question:
function incrementallyProcess(workerCallback, data, chunkSize, timeout, completionCallback) {
var itemIndex = 0;
(function() {
var remainingDataLength = (data.length - itemIndex);
var currentChunkSize = (remainingDataLength >= chunkSize) ? chunkSize : remainingDataLength;
if(itemIndex < data.length) {
while(currentChunkSize--) {
workerCallback(data[itemIndex++]);
}
setTimeout(arguments.callee, timeout);
} else if(completionCallback) {
completionCallback();
}
})();
}
// here we are using the above function to take
// a short break every time we load an image
function init() {
incrementallyProcess(function(element) {
imageBuf[element] = new Image();
imageBuf[element].onload = function(){count()};
imageBuf[element].src = "thumbs/"+element;
}, imgList, 1, 250, function() {
alert("done loading");
});
}
You may want to modify the chunk size parameter as well as the length of the timeout to get it to behave just like you want it to. I am not 100% sure this will work for you, but it is worth a try...
Related
I'm building a quiz that uses audio and is marked on the response time of the user so I want all the audio html elements to be fully loaded before the user can start the quiz.
I just need some sort of loop that checks if ALL the audio elements have all the song/audio loaded completely.
I've looked at readyState etc but I cant find its use within a loop of all the audio on the page. I would like a javascript or jQuery solution if possible.
I thought something like below would do it but nope:
var ready_count = 0;
var x = document.getElementsByClassName("question-audio");
for (var i = 0; i < x.length; ++i) {
var item = x[i];
item.addEventListener("readystatechange", function() {
if (item.readyState == 4) {
ready_count++;
}
}
if(ready_count == x.length){
//do something
}
Any help appreciated.
Here's a jQuery version:
function audioReady(){
return $.when.apply($, $('audio').map(function(){
var ready = new $.Deferred();
$(this).one('canplay', ready.resolve);
return ready.promise();
}));
}
If you don't have jQuery and/or don't care about older browsers:
function audioReady(){
var audio = [].slice.call(document.getElementsByTagName('audio'));
return Promise.all(audio.map(function(el){
return new Promise(function(resolve, reject){
el.addEventListener('canplay', resolve);
});
}));
}
Either way, you'll be able to use the function as such:
audioReady().then(function(){
// do something
});
I'm currently building a layout that animates using jQuery, and I am finding out the width of a div using .width(). However, sometimes it is getting the .width() before TypeKit has been activated (thus giving an incorrect width).
Is there a way to check when TypeKit has loaded by using an if statement?
Yes, there is.
Instead of calling the usual try{Typekit.load();}catch(e){} in your head tag, you can use Typekit.load with callbacks (docs):
try {
Typekit.load({
loading: function() {
// JavaScript to execute when fonts start loading
},
active: function() {
// JavaScript to execute when fonts become active
// this is where you want to init your animation stuff
},
inactive: function() {
// JavaScript to execute when fonts become inactive
}
})
} catch(e) {}
I've literally just done this for my own project, where I don't have the ability to change that code. So If you're in the same situation, try this:
// configure these
var check_interval = 100; // how many ms to leave before checking again
var give_up_after_ms = 2000; // how many ms before we consider the page loaded anyway.
// caches etc
var count = 0;
var count_limit = give_up_after_ms / check_interval;
var html = $("html");
var font_loaded_check_interval;
var check_load_status = function(callback) {
if(html.hasClass("wf-active") || count >= count_limit) {
// fonts are loaded or give_up_after_ms was reached
if(font_loaded_check_interval) {
clearInterval(font_loaded_check_interval);
font_loaded_check_interval = null;
}
// call the callback
callback.call(this);
return true;
}
count++;
return false;
};
function doneCallback() {
// code to run when fonts are loaded or timeout reached
alert("Done");
}
// check on initial run of JS, and if not ready, start checking at regular intervals.
if( ! check_load_status(doneCallback)) {
font_loaded_check_interval = setInterval(function() {
check_load_status(doneCallback);
}, check_interval);
}
I'm in the process of creating a site that preloads several large gifs. Due to the size of the images. I need them all to be loaded before displayed to the user. In the past I have done this numerous times using something basic like this:
var image = new Image();
image.onload = function () { document.appendChild(image); }
image.src = '/myimage.jpg';
However, i'm loading a group of images from an array, which contains the image source url. It should show a loading message and once they have all loaded it show perform a callback and hide the loading message etc.
The code I've been using is below:
var images = ['image1.gif', 'image2.gif', 'image3.gif'];
function preload_images (target, callback) {
// get feedback container
var feedback = document.getElementById('feedback');
// show feedback (loading message)
feedback.style.display = 'block';
// set target
var target = document.getElementById(target);
// clear html of target incase they refresh (tmp fix)
target.innerHTML = '';
// internal counter var
var counter = 0;
// image containers attach to window
var img = new Array();
// loop images
if (images.length > 0) {
for (var i in images) {
// new image object
img[i] = new Image();
// when ready peform certain actions.
img[i].onload = (function (value) {
// append to container
target.appendChild(img[value]);
// hide all images apart from the first image
if (value > 0) {
hide(img[value]);
}
// increment counter
++counter;
// on counter at correct value use callback!
if (counter == images.length) {
// hide feedback (loading message)
feedback.style.display = 'none';
if (callback) {
callback(); // when ready do callback!
}
}
})(i);
// give image alt name
img[i].alt = 'My Image ' + i;
// give image id
img[i].id = 'my_image_' + i
// preload src
img[i].src = images[i];
}//end loop
}//endif length
}//end preload image
It's really weird, I'm sure it should just work, but it doesn't even show my loading message. It just goes straight to the callback.. I'm sure it must be something simple, I've been busy and looking at it for ages and finding it a tad hard to narrow down.
I've been looking over stackoverflow and people have had similar problems and I've tried the solutions without much luck.
Any help would be greatly appreciated! I'll post more code if needed.
Cheers!
If I'm not totally wrong the problem is with you assignment to
// when ready peform certain actions.
img[i].onload = (function (value) {...})(i);
here you instantly call and execute the function and return undefined to the onload attribute, what can not be called when the image is loaded.
What you can do to have access to the value 'i' when the image is loaded you can try something like the following:
onload = (function(val){
var temp = val;
return function(){
i = temp;
//your code here
}
})(i);
this should store the value in temp and will return a callable function which should be able to access this value.
I did not test that if it is working and there maybe a better solution, but this one came to my mind :)
Try this for your onload callback:
img[i].onload = function(event) {
target.appendChild(this);
if (img.indexOf(this) > 0) {
hide(this);
}
// ...
};
Hope you can get it working! It's bed time for me though.
Edit: You'll probably have to do something about img.indexOf(this)... just realized you are using associative array for img. In your original code, I don't think comparing value to 0 is logical in that case, since value is a string. Perhaps you shouldn't use an associative array?
I have the following JS code for a canvas based game.
var EXPLOSION = "sounds/explosion.wav";
function playSound(str, vol) {
var snd = new Audio();
snd.src = str;
snd.volume = vol;
snd.play();
}
function createExplosion() {
playSound(EXPLOSION, 0.5);
}
This works, however it sends a server request to download the sound file every time it is called. Alternatively, if I declare the Audio object beforehand:
var snd = new Audio();
snd.src = EXPLOSION;
snd.volume = 0.5;
function createExplosion() {
snd.play();
}
This works, however if the createExplosion function is called before the sound is finished playing, it does not play the sound at all. This means that only a single playthrough of the sound file is allowed at a time - and in scenarios that multiple explosions are taking place it doesn't work at all.
Is there any way to properly play an audio file multiple times overlapping with itself?
I was looking for this for ages in a tetris game i'm building and I think this solution is the best.
function playSoundMove() {
var sound = document.getElementById("move");
sound.load();
sound.play();
}
just have it loaded and ready to go.
You could just duplicate the node with cloneNode() and play() that duplicate node.
My audio element looks like this:
<audio id="knight-audio" src="knight.ogg" preload="auto"></audio>
and I have an onClick listener that does just that:
function click() {
const origAudio = document.getElementById("knight-audio");
const newAudio = origAudio.cloneNode()
newAudio.play()
}
And since the audio element isn't going to be displayed, you don't actually have to attach the node to anything.
I verified client-side and server-side that Chrome only tries to download the audio file once.
Caveats: I'm not sure about performance impacts, since this on my site this clip doesn't get played more than ~40x maximum for a page. You might have to clean up the audio nodes if you're doing something much larger than that?
Try this:
(function() {
var snds = {};
window.playSound(str,vol) {
if( !snds[str]) (snds[str] = new Audio()).src = str;
snds[str].volume = vol;
snds[str].play();
}
})();
Then the first time you call it it will fetch the sound, but every time after that it will reuse the same sound object.
EDIT: You can also preload with duplicates to allow the sound to play more than once at a time:
(function() {
var snds = {}
window.playSound = function(str,vol) {
if( !snds[str]) {
snds[str] = [new Audio()];
snds[str][0].src = str;
}
var snd = snds[str], pointer = 0;
while( snd[pointer].playing) {
pointer++;
if( pointer >= snd.length) {
snd.push(new Audio());
snd[pointer].src = str;
}
}
snd[pointer].volume = vol;
snd[pointer].play();
};
})();
Note that this will send multiple requests if you play the sound overlapping itself too much, but it should return Not Modified very quickly and will only do so if you play it more times than you have previously.
In my game i'm using preoading but after the sound is initiated (its not so smart to not preload at all or preload everything on page load, some sound hasn't played in some gameplay at all, why to load them)
const audio {};
audio.dataload = {'entity':false,'entityes':[],'n':0};
audio.dataload.ordernum = function() {
audio.dataload.n = (audio.dataload.n + 1)%10;
return audio.dataload.n;
}
audio.dataload.play = function() {
audio.dataload.entity = new Audio('/some.mp3');
for (let i = 0; i<10;i++) {
audio.dataload.entityes.push(audio.dataload.entity.cloneNode());
}
audio.dataload.entityes[audio.dataload.ordernum()].play();
}
audio.dataload.play() // plays sound and preload sounds to memory when it isn't
I've created a class that allows for layered audio. This is very similar to other answers where it creates another node with the same src, but this class will only do that if necessary. If it has created a node already that has been completed, it will replay that existing node.
Another tweak to this is that initially fetch the audio and use the URL of the blob. I do this for efficiency; so the src doesn't have to be fetched externally every single time a new node is created.
class LayeredAudio {
url;
samples = [];
constructor(src){
fetch(src)
.then(response => response.blob())
.then((blob) => {
this.url = URL.createObjectURL(blob);
this.samples[0] = new Audio(this.url);
});
}
play(){
if(!this.samples.find(e => e.paused)?.play()){
this.samples.push(new Audio(this.url))
this.samples[this.samples.length - 1].play()
}
}
}
const aud = new LayeredAudio("URL");
aud.play()
Relying more on memory than process time, we can make an array of multiple clones of the Audio and then play them by order:
function gameSnd() {
tick_wav = new Audio('sounds/tick.wav');
victory_wav = new Audio('sounds/victory.wav');
counter = 0;
ticks = [];
for (var i = 0; i<10;i++)
ticks.push(tick_wav.cloneNode());
tick = function(){
counter = (counter + 1)%10;
ticks[counter].play();
}
victory = function(){
victory_wav.play();
}
}
When I tried some of the other solutions there was some delay, but I may have found a better alternative. This will plow through a good chunk of memory if you make the audio array's length high. I doubt you will need to play the same audio more than 10 times at the same time, but if you do just make the array length longer.
var audio = new Array(10);
// The length of the audio array is how many times
// the audio can overlap
for (var i = 0; i < audio.length; i++) {
audio[i] = new Audio("your audio");
}
function PlayAudio() {
// Whenever you want to play it call this function
audio[audioIndex].play();
audioIndex++;
if(audioIndex > audio.length - 1) {
audioIndex = 0;
}
}
I have found this to be the simples way to overlap the same audio over itself
<button id="btn" onclick="clickMe()">ding</button>
<script>
function clickMe() {
const newAudio = new Audio("./ding.mp3")
newAudio.play()
}
Im trying to load in jpeg images, frame by frame to create an sequence animation of jpeg images. I'm attempting to load them in a recursive loop using javascript. I need to load images in linearly to achieve progressive playback of the animation. (start playback before all frames are loaded) I get a Stack overflow at line: 0 error from IE due to the natural recursion of the function. (My real code loads in over 60+ frames)
Here is a basic example of how I'm doing this:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg']; //real code has 60+ frames
var images = [];
var load_index = 0;
var load = function(){
var img = new Image();
img.onload = function(){
if(load_index<=paths.length){
load_index++;
load();
}else{
alert('done loading');
}
}
img.src = paths[load_index];
images.push(img);
}
It seems I can avoid this error by using a setTimeout with an interval of 1 when calling the next step of the load. This seems to let IE "breathe" before loading the next image, but decreases the speed at which the images load dramatically.
Any one know how to avoid this stack overflow error?
http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/
The above link suggests that wrapping the function to remove it from the window object will help avoid stack overflow errors. But I then see strangeness with it only getting about 15 frames through the sequence and just dies.
Put simply, don't use a recursive function for this situation, there isn't any need:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = [];
var loads = [];
/// all complete function, probably should be renamed to something with a
/// unique namespace unless you are working within your own function scope.
var done = function(){
alert('all loaded');
}
var loaded = function(e,t){
/// fallbacks for old IE
e = e||Event; t = e.target||e.srcElement;
/// keep a list of the loaded images, you can delete this later if wanted
loads.push( t.src );
if ( loads.length >= paths.length ) {
done();
}
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
images.push(img = new Image());
img.onload = loaded;
img.src = paths[i];
}
}
In fact, as you are finding, the method you are using currently is quite intensive. Instead, the above version doesn't create a new function for each onload listener (saves memory) and will trigger off as many concurrent loads as your browser will allow (rather than waiting for each image load).
(the above has been manually typed and not tested, as of yet)
update
Ah, then it makes more sense as to why you are doing things this way :) In that case then your first approach using the setTimeout would probably be the best solution (you should be able to use a timeout of 0). There is still room for rearranging things to see if you can avoid that though. The following may get around the problem...
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = []; /// will contain the image objects
var loads = []; /// will contain loaded paths
var buffer = []; /// temporary buffer
var done = function(){ alert('all loaded'); }
var loaded = function(e,t){
e = e||Event; t = e.target||e.srcElement; loads.push( t.src );
/// you can do your "timing/start animation" calculation here...
/// check to see if we are complete
if ( loads.length >= paths.length ) { done(); }
/// if not fire off the next image load
else { next(); }
}
var next = function(){
/// current will be the next image
var current = buffer.shift();
/// set the load going for the current image
if ( current ) { current.img.src = current.path; }
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
img = new Image();
img.onload = loaded;
/// build up a list of images and paths to load
buffer.push({ img: img, path: paths[i] });
}
/// set everything going
next();
}
If the above doesn't do it, another way of getting around the issue would be to step through your list of paths, one at a time, and append a string of image markup (that would render off-screen) to the DOM with it's own onload="next()" handler... next() would be responsible for inserting the next image. By doing this it would hand off the triggering of the load and the subsequent load event to outside of your code, and should get around stacking calls.