Preloading background images - javascript
I'm building a page that loops through 3 different backgrounds, changing every 750ms. To do this, I'm adding a class to the body with the relevent background image, changing with JS. For the first loop through, they flash because the image has to load, so it's not there instantly. Therefore is there any methods I can use to preload the images?
CSS:
&.backgrounds{
background-position: center bottom;
background-repeat: no-repeat;
background-size: 130%;
&.freddy{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_freddy.jpg);
}
&.irene{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_irene.jpg);
}
&.joe{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_joe.jpg);
}
}
JS:
setInterval(function() {
if ( $('.backgrounds').hasClass('freddy') ){
$('.backgrounds').removeClass('freddy').addClass('irene');
} else if ( $('.backgrounds').hasClass('irene') ){
$('.backgrounds').removeClass('irene').addClass('joe');
} else if ( $('.backgrounds').hasClass('joe') ){
$('.backgrounds').removeClass('joe').addClass('freddy');
}
}, 750);
I would do something like this. loadImages returns a Promise that will resolve once all of the images are loaded. The .then attached to it calls cycleImages, which starts up the interval. Since you will need the URLs in the JS anyway to do the pre-loading, instead of class switching I'm directly manipulating the background-image, that way you can remove the image URLs from the CSS and save a few redundant bytes. This also makes it easier to expand the list of images in the future too, you only need to add an item to the array of images instead of maintaining a complicated if statement.
function loadImages (images) {
// each image will be loaded by this function.
// it returns a Promise that will resolve once
// the image has finished loading
let loader = function (src) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
// resolve the promise with our url so it is
// returned in the result of Promise.all
resolve(src);
};
img.onerror = function (err) {
reject(err);
};
img.src = src;
});
};
// create an image loader for each url
let loaders = [];
images.forEach(function (image) {
loaders.push(loader(image));
});
// Promise.all will return a promise that will resolve once all of of our
// image loader promises resolve
return Promise.all(loaders);
}
// the images we are going to display
let myImages = [
'http://www.gifpng.com/400x200',
'http://www.gifpng.com/400x200/ffffff/000000',
'http://www.gifpng.com/400x200/000000/ffffff'
];
// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {
// after the images are loaded this will be called with an array of the loaded images
function cycleImages (images) {
let index = 0;
setInterval(function() {
// since we need an array of the image names to preload them anyway,
// just load them via JS instead of class switching so you can cut them
// out of the CSS and save some space by not being redundant
$('#backgrounds').css('backgroundImage', 'url("' + images[index] + '")');
// increment, roll over to 0 if at length after increment
index = (index + 1) % images.length;
}, 750);
}
// load the images and start cycling through them after they are loaded
loadImages(myImages).then(cycleImages).catch(function (err) {
console.error(err);
});
});
#backgrounds {
height: 200px;
width: 400px;
border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>
Edit:
Taking Just a student's comments into account, here is a version that will only switch the image once it is loaded, but do it right away if it is loaded. It also skips images that failed to load. Since cycleImages is no longer called via .then, I also changed it so it accepts a target element (as a jQuery object) along with an array of images promises. That way you can easily use this on multiple places on a page with different images sets if you wanted to.
function loadImages (images) {
// each image will be loaded by this function.
// it returns a Promise that will resolve once
// the image has finished loading
let loader = function (src) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
// resolve the promise with our url
resolve(src);
};
img.onerror = function (err) {
reject(err);
};
img.src = src;
});
};
// return an array of image-loading promises
return images.map(function (image) {
return loader(image);
});
}
// the images we are going to display
let myImages = [
'http://www.gifpng.com/400x200',
'http://www.invalid-domain-name.foo/this-url-certainly-does-not-exist.jpg',
'http://www.gifpng.com/400x200/ffffff/000000',
'http://www.gifpng.com/400x200/000000/ffffff'
];
// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {
// this receives an array of the promises for each image
function cycleImages ($target, images) {
let index = 0,
interval = 750; // how many ms to wait before attempting to switch images
function nextImage () {
// p is the promise for the current image
let p = images[index],
next = function (wait) {
// increment our counter and wait to display the next one
index = (index + 1) % images.length;
setTimeout(nextImage, wait);
};
// wait for this image to load or fail to load
p.then(function (src) {
// it loaded, display it
$target.css('backgroundImage', 'url("' + src + '")');
next(interval);
}).catch(function (err) {
// this one failed to load, skip it
next(0);
});
}
// start cycling
nextImage();
}
// load the images and start cycling through them as they are loaded
cycleImages($('#backgrounds'), loadImages(myImages));
});
#backgrounds {
height: 200px;
width: 400px;
border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>
Instead of hard-coding the interval between images changes you could also pass that in as a parameter. At that point though, I would refactor it to use a config object to pass everything but the image promise array: cycleImages(myImages, {target: $('#backgrounds'), interval: 1000});
You can load them in javascript. Like:
(new Image()).src = url1;
(new Image()).src = url2;
(new Image()).src = url3;
Change the "url1", "url2", "url3" with you own image urls. Browser will load the image but they wont be visible anywhere.
Interesting idea.
Could you potentially load them in the background so like:
<div class="hidden-images">
<img src="img1.jpg" />
<img src="img2.jpg" />
<img src="img3.jpg" />
</div>
Then in CSS
.hidden-images {
position: relative;
z-index: 1;
}
Then on whatever you sort of main container div is, slap the following on it?
.main-container {
position: relative;
z-index: 2; // Or higher, whatever is needed
}
You can do also do this with different html and css structure like this.
html
<div class="backgrounds">
<div class="background freddy"></div>
</div>
css
.backgrounds{
background-color: transparent;
background-position: center bottom;
background-repeat: no-repeat;
background-size: 130%;
height: 250px;
width; 100%;
}
.backgrounds .freddy{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/hero.jpg');
}
.backgrounds .irene{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/projects-blur.png');
}
.backgrounds .joe{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/contacts-blur.png');
}
JS
$(document).ready(function(){
setInterval(function() {
if ( $('.background').hasClass('freddy') ){
$('.background').removeClass('freddy').addClass('irene');
} else if ( $('.background').hasClass('irene') ){
$('.background').removeClass('irene').addClass('joe');
} else if ( $('.background').hasClass('joe') ){
$('.background').removeClass('joe').addClass('freddy');
}
}, 750);
});
Maybe you can add transition/animation to make it look fade in and out.
Sample codepen below:
http://codepen.io/adrianrios/pen/yMEpEv
Related
How to update screen immediately after changing an image's source with Javascript?
I'm wanting to show a new image in an existing <img> tag. To accomplish this, I'm updating the src attribute via Javascript. However, after changing the src, the screen doesn't change to show the new image loading. Instead, it waits until the new image is fully downloaded, then swaps it in. In reality, I'm setting the background-image of the <img> tag to the URL of the thumbnail of the image being loaded. The goal being to have a poor quality image in place that gets gradually replaced with the good quality version. However, since the new image doesn't appear while loading, all I get is the old photo sitting there until the new one suddenly appears. A simplified example is below. To really see the effect, open your DevTools > Network and set throttling to something slow. Click on the image, then wait until it suddenly changes. This is happening in Firefox and Chrome, so I'm guessing it's by design. Any ideas how to bypass this behaviour? const images = [ "https://picsum.photos/id/1/200/300", "https://picsum.photos/id/2/200/300", "https://picsum.photos/id/3/200/300", "https://picsum.photos/id/4/200/300", "https://picsum.photos/id/5/200/300" ]; let index = 0; const length = images.length; const img = document.querySelector("img"); document.addEventListener("click", e => { index++; img.src = images[index % length]; }); <img src = "https://picsum.photos/id/1/200/300" />
To make the current image disappear while the new image is loading, you need to remove its src attribute. However for your <img> to have a size, you need to set it explicitly (here I've done it through CSS). const images = [ "https://picsum.photos/id/1/200/300", "https://picsum.photos/id/2/200/300", "https://picsum.photos/id/3/200/300", "https://picsum.photos/id/4/200/300", "https://picsum.photos/id/5/200/300" ]; let index = 0; const length = images.length; const img = document.querySelector("img"); document.addEventListener("click", e => { index++; img.removeAttribute("src"); img.src = images[index % length]; }); img { background: red; width: 200px; height: 300px } <img src = "https://picsum.photos/id/1/200/300" />
You can alternatively do this: Add a Loader/Loading text for the image. Remove the source attribute when you click on the image Show the loader while the source is being replaced. const images = [ "https://picsum.photos/id/1/200/300", "https://picsum.photos/id/2/200/300", "https://picsum.photos/id/3/200/300", "https://picsum.photos/id/4/200/300", "https://picsum.photos/id/5/200/300" ]; let index = 0; const length = images.length; const img = document.querySelector("img"); const div = document.querySelector("div"); document.addEventListener("click", e => { img.removeAttribute("src"); index++; img.src = images[index % length]; }); body { margin: 0; } body { margin: 0; } img { position: relative; z-index: 10; } .div { position: absolute; top: 0; z-index: 5; } <img src="https://picsum.photos/id/1/200/300" /> <div class="div">Loading...</div> Working Fiddle
Replaying a gif from start with createElement in React
I'm trying to create a function in my React project that will create a new element that plays one loop of a gif, then deletes the element. So far I have been semi-successful using this: function playGif() { var gif = document.createElement('img') gif.className = "gif-play" document.body.appendChild(gif) setTimeout(() => { gif.parentNode.removeChild(gif); }, 600) } With the following CSS: .gif-play { width: 256px; height: 256px; position: absolute; background-image: url(./assets/images/animations/atk1.gif); background-size: cover; } However, no matter what I set the timeout to delete the element (even the precise duration of the gif in milliseconds) the gif will still start clipping and playing from the middle after several button clicks. I have also tried setting the image src directly like this: function playGif() { var gif = document.createElement('img') gif.src = require('./assets/images/animations/atk1.gif') document.body.appendChild(gif) setTimeout(() => { gif.parentNode.removeChild(gif); }, 600) } But with this the gif never shows up at all, just a broken image. I thought using require() in React was supposed to fix that, but it still doesn't work for me. So any tips on how to get my gif to play from the beginning when I call the function would be great. Thanks!
not sure exactly what you want but I have prepared one animation for you. You can check it. Hope it helps you. PlayGifExample Component import React, {useEffect} from "react"; import PlayGif from './PlayGif.css' function PlayGifExample() { function animate(i) { if(i > 1000) return; let gif = document.createElement('img'); gif.className = "gif-play"; gif.style.cssText = `left: ${i}px`; console.log(i); document.body.appendChild(gif); setTimeout(() => { gif.parentNode.removeChild(gif); animate(i+1); }, 10) } function playGif() { let gif = document.createElement('img'); gif.className = "gif-play"; gif.style.cssText = `left: 1px`; document.body.appendChild(gif); setTimeout(() => { gif.parentNode.removeChild(gif); animate(2); }, 10) } return ( <> <button onClick={playGif}>Click me</button> </> ); } export default PlayGifExample; PlayGif.css .gif-play { width: 256px; height: 256px; position: absolute; background-image: url(./images/4.png); background-size: cover; }
How to load an initial set of images, then animate between them randomly without jQuery
On my page I have a gallery (just a div) with several images on it. I want to show the first 9 images immediately, then load more images and use CSS transitions to animate between the existing images. Loading the initial images is easy but I do not know the best way to load the next set of images and then start animating (using the CSS Transform property). So far this is what I have: HTML (abbreviated): <div id="mainContainer"> <div class="imageHolder"><img class="homeImages" src="test.png"></div> <div class="imageHolder"><img class="homeImages" src="test1.png"></div> <div class="imageHolder"><img class="homeImages" src="test3.png"></div> </div> CSS (abbreviated): img { display: inline; position: relative; margin: 0; padding: 0; width: 30%; } .changed.opaque { opacity: 0; border: 2px solid red; } I am looking to do a variety of effects, the most simple one would be to change the opacity and fade one image over the other. To load the next set of images I have this: Javascript: var imageArray = [ 'test2.png', 'test3.png', 'test4.png', 'test5.png', 'test6.png', ]; var imageNodeArray = []; for(var i = imageArray.length - 1; i >= 0; i -= 1) { var img = new Image(); img.onload = function() { imageNodeArray.push(this); }; img.src = imageArray[i]; } document.onclick = function() { imageNodeArray[0].setAttribute('class', 'changed.opaque'); divs[0].appendChild(imageNodeArray[0]) } This does add an image to my mainContainer however, even though I can tell from devTools that it has the changed.opaque class applied to it, no opacity is shown on the added image. I am curious about this. I would also like to know the best way to "stack" images to have a bunch to animate through. I am not sure that appending child is right.... Thank you
function animate() { var index = Math.floor((Math.random() * document.querySelectorAll('#mainContainer > .imageHolder').length + 1)); var current = document.querySelector('.top'); var next = document.querySelector('.imageHolder:nth-of-type(' + index + ')'); current.className = "imageHolder"; next.className += "top"; } Should be able to handle and switch between any dynamically inserted images. Currently using: .imageHolder { display: none; } .top { display: inherit; } to switch the image is just a simple implementation. Here's the working fiddle: http://jsfiddle.net/e9dxN/1/ Alternative implementation: http://jsfiddle.net/e9dxN/6/
Why jQuery .width is returning 0 instead of 1 [duplicate]
I want to do: $("img").bind('load', function() { // do stuff }); But the load event doesn't fire when the image is loaded from cache. The jQuery docs suggest a plugin to fix this, but it doesn't work
If the src is already set, then the event is firing in the cached case, before you even get the event handler bound. To fix this, you can loop through checking and triggering the event based off .complete, like this: $("img").one("load", function() { // do stuff }).each(function() { if(this.complete) { $(this).load(); // For jQuery < 3.0 // $(this).trigger('load'); // For jQuery >= 3.0 } }); Note the change from .bind() to .one() so the event handler doesn't run twice.
Can I suggest that you reload it into a non-DOM image object? If it's cached, this will take no time at all, and the onload will still fire. If it isn't cached, it will fire the onload when the image is loaded, which should be the same time as the DOM version of the image finishes loading. Javascript: $(document).ready(function() { var tmpImg = new Image() ; tmpImg.src = $('#img').attr('src') ; tmpImg.onload = function() { // Run onload code. } ; }) ; Updated (to handle multiple images and with correctly ordered onload attachment): $(document).ready(function() { var imageLoaded = function() { // Run onload code. } $('#img').each(function() { var tmpImg = new Image() ; tmpImg.onload = imageLoaded ; tmpImg.src = $(this).attr('src') ; }) ; }) ;
My simple solution, it doesn't need any external plugin and for common cases should be enough: /** * Trigger a callback when the selected images are loaded: * #param {String} selector * #param {Function} callback */ var onImgLoad = function(selector, callback){ $(selector).each(function(){ if (this.complete || /*for IE 10-*/ $(this).height() > 0) { callback.apply(this); } else { $(this).on('load', function(){ callback.apply(this); }); } }); }; use it like this: onImgLoad('img', function(){ // do stuff }); for example, to fade in your images on load you can do: $('img').hide(); onImgLoad('img', function(){ $(this).fadeIn(700); }); Or as alternative, if you prefer a jquery plugin-like approach: /** * Trigger a callback when 'this' image is loaded: * #param {Function} callback */ (function($){ $.fn.imgLoad = function(callback) { return this.each(function() { if (callback) { if (this.complete || /*for IE 10-*/ $(this).height() > 0) { callback.apply(this); } else { $(this).on('load', function(){ callback.apply(this); }); } } }); }; })(jQuery); and use it in this way: $('img').imgLoad(function(){ // do stuff }); for example: $('img').hide().imgLoad(function(){ $(this).fadeIn(700); });
Do you really have to do it with jQuery? You can attach the onload event directly to your image as well; <img src="/path/to/image.jpg" onload="doStuff(this);" /> It will fire every time the image has loaded, from cache or not.
You can also use this code with support for loading error: $("img").on('load', function() { // do stuff on success }) .on('error', function() { // do stuff on smth wrong (error 404, etc.) }) .each(function() { if(this.complete) { $(this).load(); } else if(this.error) { $(this).error(); } });
I just had this problem myself, searched everywhere for a solution that didn't involve killing my cache or downloading a plugin. I didn't see this thread immediately so I found something else instead which is an interesting fix and (I think) worthy of posting here: $('.image').load(function(){ // stuff }).attr('src', 'new_src'); I actually got this idea from the comments here: http://www.witheringtree.com/2009/05/image-load-event-binding-with-ie-using-jquery/ I have no idea why it works but I have tested this on IE7 and where it broke before it now works. Hope it helps, Edit The accepted answer actually explains why: If the src is already set then the event is firing in the cache cased before you get the event handler bound.
By using jQuery to generate a new image with the image's src, and assigning the load method directly to that, the load method is successfully called when jQuery finishes generating the new image. This is working for me in IE 8, 9 and 10 $('<img />', { "src": $("#img").attr("src") }).load(function(){ // Do something });
A solution I found https://bugs.chromium.org/p/chromium/issues/detail?id=7731#c12 (This code taken directly from the comment) var photo = document.getElementById('image_id'); var img = new Image(); img.addEventListener('load', myFunction, false); img.src = 'http://newimgsource.jpg'; photo.src = img.src;
A modification to GUS's example: $(document).ready(function() { var tmpImg = new Image() ; tmpImg.onload = function() { // Run onload code. } ; tmpImg.src = $('#img').attr('src'); }) Set the source before and after the onload.
Just re-add the src argument on a separate line after the img oject is defined. This will trick IE into triggering the lad-event. It is ugly, but it is the simplest workaround I've found so far. jQuery('<img/>', { src: url, id: 'whatever' }) .load(function() { }) .appendTo('#someelement'); $('#whatever').attr('src', url); // trigger .load on IE
I can give you a little tip if you want do like this: <div style="position:relative;width:100px;height:100px"> <img src="loading.jpg" style='position:absolute;width:100px;height:100px;z-index:0'/> <img onLoad="$(this).fadeIn('normal').siblings('img').fadeOut('normal')" src="picture.jpg" style="display:none;position:absolute;width:100px;height:100px;z-index:1"/> </div> If you do that when the browser caches pictures, it's no problem always img shown but loading img under real picture.
I had this problem with IE where the e.target.width would be undefined. The load event would fire but I couldn't get the dimensions of the image in IE (chrome + FF worked). Turns out you need to look for e.currentTarget.naturalWidth & e.currentTarget.naturalHeight. Once again, IE does things it's own (more complicated) way.
You can solve your problem using JAIL plugin that also allows you to lazy load images (improving the page performance) and passing the callback as parameter $('img').asynchImageLoader({callback : function(){...}}); The HTML should look like <img name="/global/images/sample1.jpg" src="/global/images/blank.gif" width="width" height="height" />
If you want a pure CSS solution, this trick works very well - use the transform object. This also works with images when they're cached or not: CSS: .main_container{ position: relative; width: 500px; height: 300px; background-color: #cccccc; } .center_horizontally{ position: absolute; width: 100px; height: 100px; background-color: green; left: 50%; top: 0; transform: translate(-50%,0); } .center_vertically{ position: absolute; top: 50%; left: 0; width: 100px; height: 100px; background-color: blue; transform: translate(0,-50%); } .center{ position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; background-color: red; transform: translate(-50%,-50%); } HTML: <div class="main_container"> <div class="center_horizontally"></div> <div class="center_vertically"></div> <div class="center"></div> </div> </div Codepen example Codepen LESS example
Javascript - Browser skips back to top of page on image change
I have some simple code to replace an image src. It is working correctly but everytime the image is updated, the browser skips right back to the top of the page. I have several image tags in my page. All of which hidden, except for the first one. The script just iterates through them and uses the src attribute to update the first image. Here is the code I am using: var j = jQuery.noConflict(); var count = 1; var img; function update_main_image() { count++; if (j('#main_image_picture_'+count).length > 0) { img = j('#main_image_picture_'+count).attr('src'); } else { count = 1; img = j('#main_image_picture_'+count).attr('src'); } j(".main_image_picture_auto").fadeOut(1500, function() { j(this).fadeIn(); j(this).attr("src", img); }); } j(document).ready(function() { setInterval(update_main_image, 6000); }); Any ideas what might be causing it? Any advice appreciated. Thanks.
Try to add DIV around your IMG.main_image_picture_auto with width and height style properties setted to maximum posible image size, for example: <div style='width:400px; height:400px; border: 0px; background: transparent; '> <img class='main_image_picture_auto' src=''/> </div> <!-- Where width:400px and height:400px is maximum allowed image size --> And I think, that is better to use setTimeout instead of setInterval function update_main_image() { // .... setTimeout(update_main_image, 6000); } j(document).ready(function() { setTimeout(update_main_image, 6000); }); http://jsfiddle.net/UBEWS/